セキュリティと言ってもパスワードとか暗号化とかの話ではない。Rubyのセキュ リティ機能はCGIプログラムのような環境で「信用できない」ものを扱うため に存在する。
例えば数値を表現した文字列を手軽に整数に変換したいとき、その文字列を
evalするという方法がある。しかしevalというのは「文字列を
Rubyプログラムとして実行する」というメソッドであり、ネットワーク越しに
どこの誰かもわからない人から受け取った文字列をevalしてしまったりすると、
とても危険だ。しかしそういうことをプログラマが逐一管理して、これは安全、
これは危険……と分けていくのは非常にしんどい。めんどい。従って確実にい
つか間違える。だからそういうのは言語にやらせよう。というわけだ。
ではRubyはどういうふうにその危険から守ってくれるのだろうか。危険な操作、 例えば意図しないファイルを開いたりすること、の原因にはおおまかに言って 二種類ある。
前者では値を扱うプログラムは確実に自分が書いたもので、従って (わりと)安全である。後者ではプログラムのコード自体が全く 信用できない。
この二つの原因ではかなり対策が違うので、レベルを分ける必要がある。これ
をセキュリティレベルと言う。Rubyレベルでは$SAFEというグローバル変数と
して出てくる。値は最低の0から最高の4まである。変数に代入することでレベ
ルが上がり、一度上げたら絶対に下げられない。そしてそれぞれによって
操作が制限されるわけだ。
レベル1と3は略。 レベル0は通常のプログラム環境である。セキュリティシステムは全く働 かない。レベル2は危険な値に対処する。レベル4では危険なプログラムに対処 する。0はいいとして、2と4について仕組みを詳しく見ていこう。
危険な値に対処するためのレベル。通常のCGIなど。
レベル2の基礎をなすのはオブジェクト単位で記憶されている汚染マークであ
る。外部から読み込んだオブジェクト全部に汚染マークを付けておき、汚染さ
れたオブジェクトをevalしようとしたりFile.openしようとしたら例外を発生
させて止める。
それと汚染マークは「感染」する。例えば汚染した文字列の 一部を取ると、それもやはり汚染されている。
危険なプログラムに対処するためのレベル。 外部から持ってきた(素性の知れない)プログラムの実行など。
レベル2では操作とそれに使う値の両方でチェックしていたわけだが、
レベル4に
なると操作だけで禁止対象になる。例えばexit、ファイルI/O、スレッド
操作、メソッド定義の変更、などなど。もちろん汚染情報も多少は使うのだが、
基本的には操作が基準になる。
$SAFEは見ためはグローバル変数だが実はスレッドローカルである。つまり
Rubyのセキュリティシステムはスレッド単位で働く。Javaや.NETだとコンポー
ネント(オブジェクト)単位で権限が設定できるが、Rubyはそこまではやって
いない。想定するメインターゲットがCGIだからだろう。
だからプログラムの一部分だけセキュリティレベルを上げたい、 という場合は別スレッドにしてレ ベルを分離する。スレッドの作りかたなんてまだ説明していないわけだが、と りあえず以下の例だけで我慢しておいてほしい。
# 別スレッドでセキュリティレベルを上げる
p($SAFE) # デフォルトは0
Thread.fork { # 別スレッドを起動して
$SAFE = 4 # レベルを上げて
eval(str) # 危険なプログラムを実行
}
p($SAFE) # ブロックの外ではレベル0のまま
$SAFEの信頼性汚染マークの感染にしても操作の禁止にしても最終的には全て手作業で行われ ている。つまり使っている全ての組み込みライブラリと拡張ライブラリが抜け なく対処しないと途中で汚染が途切れ、安全ではなくなる。そして実際にそう いう穴はよく報告されている。だからとりあえず、筆者はあまり信用していな い。
もっとも、だからと言って全てのRubyプログラムが危険であるわけでは、もち
ろんない。$SAFE=0でも安全なプログラムは書けるし、$SAFE=4でも好き放
題できるプログラムは書ける。ただ$SAFEは(まだ)過信できないというだ
けだ。
そもそも「活発な機能追加」と「セキュリティ」が両立するわけがない。どん
どん新機能が付け加わっているのならそれに比例して穴も開きやすくなるとい
うのは常識である。ならば当然rubyも危険だろうと考えるべきだ。
ここからは実装に入るが、rubyのセキュリティシステムを完全に捉えるに
は仕組みよりもむしろ「どこをチェックしているのか」を見なければならない。
しかし今回それをやっているページはないし、いちいちリストアップするだけ
では面白くない。そこでとりあえずこの章ではセキュリティチェックに使わ
れる仕組みだけを解説して終えることにする。チェック用のAPIは主に以下の
二つだ。
SecurityErrorを発生するrb_secure(n)SafeStringValue()
SafeStringValue()はここでは読まない。
汚染マークとは具体的にはbasic->flagsに記憶される
フラグFL_TAINTで、
それを感染させるのはOBJ_INFECT()というマクロである。
このように使う。
OBJ_TAINT(obj) /* objにFL_TAINTを付ける */ OBJ_TAINTED(obj) /* objにFL_TAINTが付いているか調べる */ OBJ_INFECT(dest, src) /* srcからdestにFL_TAINTを伝染させる */
OBJ_TAINT()・OBJ_TAINTED()はどうでもいいとして、
OBJ_INFECT()だけさっと見よう。
▼OBJ_INFECT
441 #define OBJ_INFECT(x,s) do { \
if (FL_ABLE(x) && FL_ABLE(s)) \
RBASIC(x)->flags |= RBASIC(s)->flags & FL_TAINT; \
} while (0)
(ruby.h)
FL_ABLE()は引数のVALUEがポインタであるかどうか調べる。
両方のオブジェクトがポインタなら(つまりflagsメンバがあるなら)、
フラグを伝播する。
$SAFE▼ruby_safe_level
124 int ruby_safe_level = 0;
7401 static void
7402 safe_setter(val)
7403 VALUE val;
7404 {
7405 int level = NUM2INT(val);
7406
7407 if (level < ruby_safe_level) {
7408 rb_raise(rb_eSecurityError, "tried to downgrade safe level from %d to %d",
7409 ruby_safe_level, level);
7410 }
7411 ruby_safe_level = level;
7412 curr_thread->safe = level;
7413 }
(eval.c)
$SAFEの実体はeval.cのruby_safe_levelだ。先に書いたとおり
$SAFEは
スレッドに固有なので、スレッドの実装があるeval.cに書く必要があったからだ。
つまりC言語の都合でeval.cにあるだけで、本来は別の場所にあってよい。
safe_setter()はグローバル変数$SAFEのsetterである。
つまりRubyレベルからはこの関数経由でしかアクセスできないので
レベルを下げることはできなくなる。
ただし見てのとおりruby_safe_levelにはstaticが付いていないので
Cレベルからはインターフェイスを無視してセキュリティレベルを変更できる。
rb_secure()▼rb_secure()
136 void
137 rb_secure(level)
138 int level;
139 {
140 if (level <= ruby_safe_level) {
141 rb_raise(rb_eSecurityError, "Insecure operation `%s' at level %d",
142 rb_id2name(ruby_frame->last_func), ruby_safe_level);
143 }
144 }
(eval.c)
現在のセーフレベルがlevel以上だったら例外SecurityErrorを発生。簡単だ。
御意見・御感想・誤殖の指摘などは 青木峰郎 <aamine@loveruby.net> までお願いします。
『Rubyソースコード完全解説』 はインプレスダイレクトで御予約・御購入いただけます (書籍紹介ページへ飛びます)。
Copyright (c) 2002-2004 Minero Aoki, All rights reserved.