30.1. rexec — 受限执行框架 — Python 文档

来自菜鸟教程
Python/docs/2.7/library/rexec
跳转至:导航、​搜索

30.1. 执行 — 受限执行框架

自 2.6 版起已弃用:rexec 模块已在 Python 3 中删除。


在 2.3 版更改:禁用模块。


警告

文档已保留,以帮助阅读使用该模块的旧代码。


该模块包含 RExec 类,该类支持 r_eval()r_execfile()r_exec()r_import() 方法,这些方法是标准 Python 函数 eval()execfile()execimport 语句。 在这个受限环境中执行的代码只能访问被认为安全的模块和功能; 您可以子类化 RExec 以根据需要添加或删除功能。

警告

虽然 rexec 模块的设计如下所述,但它确实有一些已知的漏洞,可以通过精心编写的代码加以利用。 因此,在需要“生产就绪”安全性的情况下不应依赖它。 在这种情况下,可能需要通过子进程执行或非常小心地“清理”要处理的代码和数据。 或者,欢迎帮助修补已知的 rexec 漏洞。


笔记

RExec 类可以防止代码执行不安全的操作,例如读取或写入磁盘文件,或使用 TCP/IP 套接字。 但是,它不能防止代码使用大量内存或处理器时间。


class rexec.RExec([hooks[, verbose]])

返回 RExec 类的实例。

hooksRHooks 类或其子类的实例。 如果省略或 None,则实例化默认的 RHooks 类。 每当 rexec 模块搜索模块(甚至是内置模块)或读取模块代码时,它实际上并没有转到文件系统本身。 相反,它调用传递给其构造函数或由其构造函数创建的 RHooks 实例的方法。 (实际上, RExec 对象不会进行这些调用——它们是由作为 RExec 对象一部分的模块加载器对象进行的。 这允许另一个级别的灵活性,这在受限环境中更改 import 的机制时非常有用。)

通过提供一个替代的 RHooks 对象,我们可以控制对导入模块进行的文件系统访问,而无需更改控制这些访问顺序的实际算法。 例如,我们可以替换一个 RHooks 对象,该对象通过一些 RPC 机制(如 ILU)将所有文件系统请求传递到其他地方的文件服务器。 Grail 的小程序加载器使用它来支持从目录的 URL 导入小程序。

如果 verbose 为真,额外的调试输出可能会发送到标准输出。

请务必注意,在受限环境中运行的代码仍然可以调用 sys.exit() 函数。 要禁止受限代码退出解释器,请始终保护导致受限代码运行的调用,并使用捕获 SystemExit 异常的 try/except 语句。 从受限环境中删除 sys.exit() 函数是不够的——受限代码仍然可以使用 raise SystemExit。 删除 SystemExit 不是一个合理的选择; 一些库代码利用了这一点,如果它不可用就会中断。

也可以看看

圣杯主页
Grail 是一个完全用 Python 编写的 Web 浏览器。 它使用 rexec 模块作为支持 Python 小程序的基础,并且可以用作该模块的示例用法。


30.1.1. RExec 对象

RExec 实例支持以下方法:

RExec.r_eval(code)
code 必须是包含 Python 表达式的字符串,或者是编译后的代码对象,它将在受限环境的 __main__ 模块中进行评估。 将返回表达式或代码对象的值。
RExec.r_exec(code)
code 必须是包含一行或多行 Python 代码的字符串,或者是编译后的代码对象,它将在受限环境的 __main__ 模块中执行。
RExec.r_execfile(filename)
在受限环境的 __main__ 模块中执行文件 filename 中包含的 Python 代码。

名称以 s_ 开头的方法类似于以 r_ 开头的函数,但代码将被授予访问标准 I/O 流 sys.stdin、[ X194X] 和 sys.stdout

RExec.s_eval(code)
code 必须是包含 Python 表达式的字符串,它将在受限环境中进行计算。
RExec.s_exec(code)
code 必须是包含一行或多行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_COMPILEDC_EXTENSIONRExec 的值为 (C_EXTENSION, PY_SOURCE)。 不建议在子类中添加PY_COMPILED; 攻击者可以通过将伪造的字节编译文件 (.pyc) 放在文件系统中的任何位置来退出受限执行模式,例如将其写入 /tmp 或将其上传到 /incoming 公共 FTP 服务器的目录。


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,这需要拆分文件名并对其执行各种操作。 在安全受到威胁的情况下,最好编写有时过于严格的简单代码,而不是更复杂且可能包含微妙安全漏洞的更通用的代码。