30.1. rexec --- 制限実行のフレームワーク

バージョン 2.6 で非推奨: rexec モジュールは Python 3 で削除されました。

バージョン 2.3 で変更: モジュールは無効化され、使えなくなりました。

警告

このドキュメントは、 rexec モジュールを使用している古いコードを読む際の参照用として残されています。

このモジュールには RExec クラスが含まれています。このクラスは、 r_eval()r_execfile()r_exec() および r_import() メソッドをサポートし、これらは標準の Python 関数 eval()execfile() および execimport 文の制限されたバージョンです。この制限された環境で実行されるコードは、安全であると見なされたモジュールや関数だけにアクセスします; 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__ モジュールで実行されます。

RExec.r_execfile(filename)

ファイル filename 内の Python コードを、制限された環境の __main__ モジュールで実行します。

名前が s_ で始まるメソッドは、 r_ で始まる関数と同様ですが、そのコードは、標準 I/O ストリーム sys.stdinsys.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_SOURCEPY_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 に単純化しなければなりません。そのためには、ファイル名を分割して、それにさまざまな操作を行う必要があります。セキュリティが重大な場合には、より複雑で微妙なセキュリティホールを抱え込むかもしれない一般性のあるコードよりも、制限が余りにあり過ぎるとしても単純なコードを書く方が、望ましいでしょう。