30.1. rexec --- 制限実行のフレームワーク¶
バージョン 2.6 で非推奨: rexec モジュールは Python 3 で削除されました。
バージョン 2.3 で変更: モジュールは無効化され、使えなくなりました。
警告
このドキュメントは、 rexec モジュールを使用している古いコードを読む際の参照用として残されています。
このモジュールには RExec クラスが含まれています。このクラスは、 r_eval() 、 r_execfile() 、 r_exec() および r_import() メソッドをサポートし、これらは標準の Python 関数 eval() 、 execfile() および exec と import 文の制限されたバージョンです。この制限された環境で実行されるコードは、安全であると見なされたモジュールや関数だけにアクセスします; RExec をサブクラス化すれば、望むように能力を追加および削除できます。
警告
rexec モジュールは、下記のように動作するべく設計されてはいますが、注意深く書かれたコードなら利用できてしまうかもしれない、既知の脆弱性がいくつかあります。従って、"製品レベル" のセキュリティを要する状況では、 rexec の動作をあてにするべきではありません。製品レベルのセキュリティを求めるなら、サブプロセスを介した実行や、あるいは処理するコードとデータの両方に対する非常に注意深い "浄化" が必要でしょう。上記の代わりに、 rexec の既知の脆弱性に対するパッチ当ての手伝いも歓迎します。
注釈
RExec クラスは、プログラムコードによるディスクファイルの読み書きや TCP/IP ソケットの利用といった、安全でない操作の実行を防ぐことができます。しかし、プログラムコードよる非常に大量のメモリや処理時間の消費に対して防御することはできません。
-
class
rexec.RExec([hooks[, verbose]])¶ RExecクラスのインスタンスを返します。hooks は、
RHooksクラスあるいはそのサブクラスのインスタンスです。 hooks が省略されているかNoneであれば、デフォルトのRHooksクラスがインスタンス化されます。rexecモジュールが (組み込みモジュールを含む) あるモジュールを探したり、あるモジュールのコードを読んだりする時は常に、rexecがじかにファイルシステムに出て行くことはありません。その代わり、あらかじめRHooksクラスに渡しておいたり、コンストラクタで生成されたRHooksインスタンスのメソッドを呼び出します。 (実際には、RExecオブジェクトはこれらを呼び出しません --- 呼び出しは、RExecオブジェクトの一部であるモジュールローダオブジェクトによって行われます。これによって別のレベルの柔軟性が実現されます。この柔軟性は、制限された環境内でimport機構を変更する時に役に立ちます。 )代替の
RHooksオブジェクトを提供することで、モジュールをインポートする際に行われるファイルシステムへのアクセスを制御することができます。このとき、各々のアクセスが行われる順番を制御する実際のアルゴリズムは変更されません。例えば、RHooksオブジェクトを置き換えて、ILU のようなある種の RPC メカニズムを介することで、全てのファイルシステムの要求をどこかにあるファイルサーバに渡すことができます。 Grail のアプレットローダは、アプレットを URL からディレクトリ上に import する際にこの機構を使っています。もし verbose が true であれば、追加のデバッグ出力が標準出力に送られます。
制限された環境で実行するコードも、やはり sys.exit() 関数を呼ぶことができることを知っておくことは大事なことです。制限されたコードがインタプリタから抜けだすことを許さないためには、いつでも、制限されたコードが、 SystemExit 例外をキャッチする try / except 文とともに実行するように、呼び出しを防御します。制限された環境から sys.exit() 関数を除去するだけでは不十分です -- 制限されたコードは、やはり raise SystemExit を使うことができてしまいます。 SystemExit を取り除くことも、合理的なオプションではありません; いくつかのライブラリコードはこれを使っていますし、これが利用できなくなると中断してしまうでしょう。
参考
- Grail Home Page
Grail はすべて Python で書かれた Web ブラウザです。これは、
rexecモジュールを、Python アプレットをサポートするのに使っていて、このモジュールの使用例として使うことができます。
30.1.1. RExec オブジェクト¶
RExec インスタンスには以下のメソッドがあります:
-
RExec.r_eval(code)¶ code は、Python の式を含む文字列か、あるいはコンパイルされたコードオブジェクトのどちらかでなければなりません。そしてこれらは制限された環境の
__main__モジュールで評価されます。式あるいはコードオブジェクトの値が返されます。
-
RExec.r_exec(code)¶ code は、1行以上の Python コードを含む文字列か、コンパイルされたコードオブジェクトのどちらかでなければなりません。そしてこれらは、制限された環境の
__main__モジュールで実行されます。
名前が s_ で始まるメソッドは、 r_ で始まる関数と同様ですが、そのコードは、標準 I/O ストリーム sys.stdin 、 sys.stderr および sys.stdout の制限されたバージョンへのアクセスが許されています。
-
RExec.s_eval(code)¶ code は、Python 式を含む文字列でなければなりません。そして制限された環境で評価されます。
-
RExec.s_exec(code)¶ code は、1 行以上の Python コードを含む文字列でなければなりません。そして制限された環境で実行されます。
-
RExec.s_execfile(code)¶ ファイル filename に含まれた Python コードを制限された環境で実行します。
RExec オブジェクトは、制限された環境で実行されるコードによって暗黙のうちに呼ばれる、さまざまなメソッドもサポートしなければなりません。これらのメソッドをサブクラス内でオーバライドすることによって、制限された環境が強制するポリシーを変更します。
-
RExec.r_import(modulename[, globals[, locals[, fromlist]]])¶ モジュール modulename をインポートし、もしそのモジュールが安全でないとみなされるなら、
ImportError例外を発生します。
-
RExec.r_open(filename[, mode[, bufsize]])¶ open()が制限された環境で呼ばれるとき、呼ばれるメソッドです。引数はopen()のものと同じであり、ファイルオブジェクト (あるいはファイルオブジェクトと互換性のあるクラスインスタンス) が返されます。RExecのデフォルトの動作は、任意のファイルを読み取り用にオープンすることを許可しますが、ファイルに書き込もうとすることは許しません。より制限の少ないr_open()の実装については、以下の例を見て下さい。
-
RExec.r_reload(module)¶ モジュールオブジェクト module を再ロードして、それを再解析し再初期化します。
-
RExec.r_unload(module)¶ モジュールオブジェクト module をアンロードします (それを制限された環境の
sys.modules辞書から取りのぞきます)。
および制限された標準 I/O ストリームへのアクセスが可能な同等のもの:
-
RExec.s_import(modulename[, globals[, locals[, fromlist]]])¶ モジュール modulename をインポートし、もしそのモジュールが安全でないとみなされるなら、
ImportError例外を発生します。
-
RExec.s_reload(module)¶ モジュールオブジェクト module を再ロードして、それを再解析し再初期化します。
-
RExec.s_unload(module)¶ モジュールオブジェクト module をアンロードします。
30.1.2. 制限された環境を定義する¶
RExec クラスには以下のクラス属性があります。それらは、 __init__() メソッドが使います。それらを既存のインスタンス上で変更しても何の効果もありません; そうする代わりに、 RExec のサブクラスを作成して、そのクラス定義でそれらに新しい値を割り当てます。そうすると、新しいクラスのインスタンスは、これらの新しい値を使用します。これらの属性のすべては、文字列のタプルです。
-
RExec.nok_builtin_names¶ 制限された環境で実行するプログラムでは利用 できない であろう、組み込み関数の名前を格納しています。
RExecに対する値は、('open', 'reload', '__import__')です。 (これは例外です。というのは、組み込み関数のほとんど大多数は無害だからです。この変数をオーバライドしたいサブクラスは、基本クラスからの値から始めて、追加した許されない関数を連結していかなければなりません -- 危険な関数が新しく Python に追加された時は、それらも、このモジュールに追加します。)
-
RExec.ok_builtin_modules¶ 安全にインポートできる組み込みモジュールの名前を格納しています。
RExecに対する値は、('audioop', 'array', 'binascii', 'cmath', 'errno', 'imageop', 'marshal', 'math', 'md5', 'operator', 'parser', 'regex', 'select', 'sha', '_sre', 'strop', 'struct', 'time')です。この変数をオーバーライドする場合も、同様な注意が適用されます -- 基本クラスからの値を使って始めます。
-
RExec.ok_path¶ importが制限された環境で実行される時に検索されるディレクトリーを格納しています。RExecに対する値は、(モジュールがロードされた時は) 制限されないコードのsys.pathと同一です。
-
RExec.ok_posix_names¶ 制限された環境で実行するプログラムで利用できる、
osモジュール内の関数の名前を格納しています。RExecに対する値は、('error', 'fstat', 'listdir', 'lstat', 'readlink', 'stat', 'times', 'uname', 'getpid', 'getppid', 'getcwd', 'getuid', 'getgid', 'geteuid', 'getegid')です。
-
RExec.ok_sys_names¶ 制限された環境で実行するプログラムで利用できる、
sysモジュール内の関数名と変数名を格納しています。RExecに対する値は、('ps1', 'ps2', 'copyright', 'version', 'platform', 'exit', 'maxint')です。
-
RExec.ok_file_types¶ モジュールがロードすることを許されているファイルタイプを格納しています。各ファイルタイプは、
impモジュールで定義された整数定数です。意味のある値は、PY_SOURCE、PY_COMPILEDおよびC_EXTENSIONです。RExecに対する値は、(C_EXTENSION, PY_SOURCE)です。サブクラスでPY_COMPILEDを追加することは推奨されません; 攻撃者が、バイトコンパイルしたでっちあげのファイル(.pyc)を、例えば、あなたの公開 FTP サーバの/tmpに書いたり、/incomingにアップロードしたりして、とにかくあなたのファイルシステム内に置くことで、制限された実行モードから抜け出ることができるかもしれないからです。
30.1.3. 例¶
標準の RExec クラスよりも、若干、もっと緩めたポリシーを望んでいるとしましょう。例えば、もし /tmp 内のファイルへの書き込みを喜んで許すならば、 RExec クラスを次のようにサブクラス化できます:
class TmpWriterRExec(rexec.RExec):
def r_open(self, file, mode='r', buf=-1):
if mode in ('r', 'rb'):
pass
elif mode in ('w', 'wb', 'a', 'ab'):
# check filename: must begin with /tmp/
if file[:5]!='/tmp/':
raise IOError("can't write outside /tmp")
elif (string.find(file, '/../') >= 0 or
file[:3] == '../' or file[-3:] == '/..'):
raise IOError("'..' in filename forbidden")
else: raise IOError("Illegal open() mode")
return open(file, mode, buf)
上のコードは、完全に正しいファイル名でも、時には禁止する場合があることに注意して下さい; 例えば、制限された環境でのコードでは、 /tmp/foo/../bar というファイルはオープンできないかもしれません。これを修正するには、 r_open() メソッドが、そのファイル名を /tmp/bar に単純化しなければなりません。そのためには、ファイル名を分割して、それにさまざまな操作を行う必要があります。セキュリティが重大な場合には、より複雑で微妙なセキュリティホールを抱え込むかもしれない一般性のあるコードよりも、制限が余りにあり過ぎるとしても単純なコードを書く方が、望ましいでしょう。
