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__" モジュールで実行されます。

RExec.r_execfile(filename)

   ファイル *filename* 内の 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" に
単純化しなければなりません。そのためには、ファイル名を分割して、それに
さまざまな操作を行う必要があります。セキュリティが重大な場合には、より
複雑で微妙なセキュリティホールを抱え込むかもしれない一般性のあるコード
よりも、制限が余りにあり過ぎるとしても単純なコードを書く方が、望ましい
でしょう。
