requireの処理の中で、セーフレベルを一時的に 0 に下げる部分があって、この範囲を広げることで、SecurityErrorが出ないようになっていた。(3日前)
画像は、tDiaryの amazon.rbが、リポジトリから持ってきただけの素の Ruby-1.9.2dev(2009-02-03)上で動いているところ。
既に正式版がリリースされて*しまって*いる Ruby-1.9.1との違いを、先月の日記に書いた例で見ていくと、
irb192> RUBY_DESCRIPTION => "ruby 1.9.2dev (2009-02-03) [i386-mswin32_90]" irb192> $SAFE=1 => 1 irb192> require "a" SecurityError: cannot load from insecure path - Y:/.../Desktop/a.rb from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ruby/bin/irb192.bat:20:in `<main>' irb192> require "a.rb" SecurityError: cannot load from insecure path - Y:/.../Desktop/a.rb from (irb):4:in `require' from (irb):4 from C:/Program Files (x86)/ruby/bin/irb192.bat:20:in `<main>' irb192>
2009-02-03版の ruby-1.9.2devでは、$SAFE=1のとき、カレントディレクトリのスクリプトを requireできない。これは $:($LOAD_PATH)に汚染されていない "." が含まれていようと、require の引数の文字列が汚染されていなかろうと、requireできない。見つけた抜け道は、絶対パスで requireするか、$:($LOAD_PATH)にカレントディレクトリを絶対パスで追加すること("."が含まれる場合はそれより前に追加する必要もある)。
ruby-1.9.1の結果は Release Candidateのときと変わらず、
irb191> RUBY_DESCRIPTION => "ruby 1.9.1p0 (2009-01-30 revision 21907) [i386-mswin32]" irb191> $SAFE=1 => 1 irb191> require "a" SecurityError: Insecure operation - require from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191> require "a.rb" a.rb required. => true irb191>
ruby-1.9.1では、拡張子を付けてやるとカレントディレクトリのスクリプトも requireできる。拡張子を付けないときに requireできないのは ruby-1.9.2devも同じだが 、ruby-1.9.1には ruby-1.9.2devにない爆弾がある。$SAFE=1のときの拡張子を付けない requireは、添付ライブラリの requireであっても失敗したりする。
irb191> $SAFE=1 => 1 irb191> require "stringio" SecurityError: Insecure operation - require from (irb):2:in `require' from (irb):2 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191> require "stringio.so" => true irb191>
これが原因で、tDiaryを ruby-1.9.1で動かすのは絶望的だと思っている*。(ruby-1.9.1はリリースされちゃったし、スクリプトで対応できる範囲を超えているから)
この部分は SecurityErrorが出ない方向に修正されると思っていたから、ruby-1.9.2devで両方のパターンが SecurityErrorになったのは意外。
ruby-1.9.2devでは、汚染されたパスが $:($LOAD_PATH)のどの位置にあるかが重要。
irb192> $SAFE=1 => 1 irb192> $:.unshift "!tainted!".taint => ["!tainted!", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2/i386-mswin32_90", "."] irb192> require "cgi" SecurityError: cannot load from insecure path - Y:/.../Desktop/!tainted!/cgi.rb from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ruby/bin/irb192.bat:20:in `<main>' irb192> $:.push $:.shift => ["C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/site_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby/1.9.2/i386-msvcr90", "C:/Program Files (x86)/ruby/lib/ruby192/vendor_ruby", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2", "C:/Program Files (x86)/ruby/lib/ruby192/1.9.2/i386-mswin32_90", ".", "!tainted!"] irb192> require "cgi" => true irb192>
ruby-1.9.2devでは、$SAFE=1で、汚染された LOAD_PATHからスクリプトを requireすることはできないが、汚染されていない LOAD_PATHからスクリプトを先に見つけた場合は、requireに成功する。ruby-1.9.1(とruby-1.8.7-p72)ではどうだったかというと、より厳しくて、
irb191> $SAFE=1 => 1 irb191> $:.unshift "!tainted!".taint => ["!tainted!", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/i386-mswin32", "."] irb191> require "cgi" SecurityError: Insecure operation - require from (irb):3:in `require' from (irb):3 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191> $:.push $:.shift => ["C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/site_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby/1.9.1/i386-msvcrt", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/vendor_ruby", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1", "C:/Program Files (x86)/ActiveScriptRuby-1.9.1/lib/ruby/1.9.1/i386-mswin32", ".", "!tainted!"] irb191> require "cgi" SecurityError: Insecure operation - require from (irb):5:in `require' from (irb):5 from C:/Program Files (x86)/ActiveScriptRuby-1.9.1/bin/irb.bat:20:in `<main>' irb191>
ruby-1.9.1では、汚染されたパスが一つでも $:($LOAD_PATH)に含まれると、(相対パスでの) requireはできない。絶対パスならできるが、そんな書き方はしないので $:($LOAD_PATH)に追加するパスは常に untaintしなければならない。
requireしたときのカレントディレクトリの扱いに、不満が残る。
多分、従来の Ruby、ruby-1.9.1や ruby-1.8.7は $:($LOAD_PATH)の要素の汚染状況なんて気にしていなかった。$:に含まれるか含まれないかだけが重要。2005年から現在まで変わっていない、リファレンスマニュアルのこの記述がそれを物語っていないか。
Level 1以上では起動時に以下の違いがある
- 環境変数 RUBYLIB を $: に加えない
- カレントディレクトリを $: に加えない
- (以下略)
その方針に異議を唱えるものではないけれど、この方式には、セーフレベルによる分岐が起動時に限定されるという制限がある。コマンドラインオプション -T を指定したときにしか有効にならず、$SAFEを使ってセーフレベルをコントロールするシナリオでは機能しない。
ruby-1.9.2devは、$:($LOAD_PATH) の要素の汚染状況に注目することで、$SAFEを使ってセールレベルをコントロールする場合でも「環境変数 RUBYLIB を $: に加えない」「カレントディレクトリを $: に加えない」のと同等の制限を課せる可能性を持っている。つまり、「環境変数RUBYLIBとカレントディレクトリは常に $:($LOAD_PATH)に追加される。ただし汚染された状態で。」ということ。
それなのに現在の ruby-1.9.2devの、$SAFE=1の下での requireに対するカレントディレクトリの扱いは、ruby-1.8.7より、むしろ退化している。
ruby-1.8.7では $:($LOAD_PATH)に "." を追加したり取り除いたりすることでカレントディレクトリの扱いを、セーフレベルによらず、スクリプトがコントロールできた。
拡張子の有無で結果が変わる、動作に筋の通らない ruby-1.9.1の requireは論外として、
ruby-1.9.2devでは、カレントディレクトリのスクリプトの requireは $:($LOAD_PATH)によらず、SecurityErrorになる。カレントディレクトリを絶対パスで $:($LOAD_PATH)に追加することでコントロール可能だが、既に含まれているかもしれない "." が邪魔をする。ruby-1.9.2devは、$:($LOAD_PATH)に含まれる "." の汚染状況(あるいは "."が $:に含まれないこと)によってのみカレントディレクトリの扱いを変えるべきで、カレントディレクトリが特別に SecurityErrorになる、現在の ruby-1.9.2devには同意できない。
セールレベル1ではまだスクリプトを信用しているのだから、少なくとも ruby-1.8.7と同等のコントロールをスクリプトに渡して欲しい。
requireが利用する File.expand_pathの仕様によりカレントディレクトリからの相対パスが汚染されるのだろうが、File.expand_pathは requireが $:($LOAD_PATH)に依拠していることを知らないのだから requireが何とかすべき。
* その後の変更で、ruby-1.9.1らしき挙動の requireを検出したときはセーフレベルを 1から 0に下げてプラグインを実行することになっている。セーフレベル0なら今回のことは関係ないから。セーフレベルを下げるという発想は全く頭になかった。なんか、こう、負けた気がするからだろう。ruby-1.9.0だとセキュアモード(セーフレベル4)で動かないという話もあったけどどうなったんだろう。