Документация Python неофициальный перевод
Содержание страницы

30.1. rexec – среда ограниченного выполненияrexec – Restricted execution framework

Устарело с версии 2.6: Модуль rexec удалён в Python 3.

Изменено в версии 2.3: Модуль отключён.

Предупреждение

Документация оставлена на месте, чтобы помочь при чтении старого кода, который использует этот модуль.

Этот модуль содержит класс RExec, который поддерживает методы r_eval(), r_execfile(), r_exec() и r_import() – ограниченные версии стандартных функций Python eval(), execfile(), а также инструкций exec и import. Код, выполняемый в этой ограниченной среде, будет иметь доступ только к модулям и функциям, которые считаются безопасными; при необходимости можно создать подкласс RExec, чтобы добавить или убрать возможности.

Предупреждение

Хотя модуль rexec спроектирован так, чтобы работать как описано ниже, у него есть несколько известных уязвимостей, которые можно использовать с помощью тщательно написанного кода. Поэтому на него не стоит полагаться в ситуациях, требующих «промышленной» безопасности. В таких случаях может потребоваться выполнение через подпроцессы или очень тщательная «очистка» как кода, так и обрабатываемых данных. Альтернативно, помощь в исправлении известных уязвимостей rexec приветствуется.

Примечание

Класс RExec может предотвратить выполнение кодом небезопасных операций, таких как чтение или запись файлов на диске, или использование TCP/IP-сокетов. Однако он не защищает от кода, использующего чрезмерно большие объёмы памяти или процессорного времени.

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

Возвращает экземпляр класса RExec.

hooks – это экземпляр класса RHooks или его подкласса. Если он опущен или равен None, создаётся экземпляр класса RHooks по умолчанию. Когда модуль rexec ищет модуль (даже встроенный) или читает код модуля, он на самом деле не обращается к файловой системе самостоятельно. Вместо этого он вызывает методы экземпляра RHooks, который был передан конструктору или создан им. (На самом деле объект RExec не делает эти вызовы – их выполняет объект загрузчика модулей, входящий в состав объекта RExec. Это добавляет дополнительный уровень гибкости, что может быть полезно при изменении механики import в рамках ограниченной среды.)

Предоставляя альтернативный объект RHooks, можно контролировать доступы к файловой системе, необходимые для импорта модуля, не меняя сам алгоритм, определяющий порядок этих доступов. Например, можно подставить объект RHooks, который перенаправляет все файловые запросы на удалённый файловый сервер через какой-нибудь RPC-механизм, вроде ILU. Загрузчик апплетов Grail использует это для поддержки импорта апплетов из URL-адреса каталога.

Если verbose равен true, дополнительная отладочная информация может выводиться на стандартный вывод.

Важно понимать, что код, выполняемый в ограниченной среде, всё равно может вызвать функцию sys.exit(). Чтобы запретить ограниченному коду завершать интерпретатор, всегда защищайте вызовы, приводящие к выполнению такого кода, с помощью инструкции try/except, перехватывающей исключение SystemExit. Удаление функции sys.exit() из ограниченной среды недостаточно – ограниченный код всё равно может использовать raise SystemExit. Удаление SystemExit – неразумный вариант; некоторые библиотеки используют её, и без неё они сломаются.

См. также

Домашняя страница Grail

Grail – это веб-браузер, полностью написанный на Python. Он использует модуль rexec как основу для поддержки апплетов Python и может служить примером использования этого модуля.

30.1.1. Объекты RExecRExec Objects

Экземпляры RExec поддерживают следующие методы:

RExec.r_eval(code)

code должен быть либо строкой, содержащей выражение Python, либо скомпилированным объектом кода, который будет вычислен в модуле __main__ ограниченной среды. Будет возвращено значение выражения или объекта кода.

RExec.r_exec(code)

code должен быть либо строкой, содержащей одну или несколько строк кода Python, либо скомпилированным объектом кода, который будет выполнен в модуле __main__ ограниченной среды.

RExec.r_execfile(filename)

Выполнить код Python из файла filename в модуле __main__ ограниченной среды.

Методы, чьи имена начинаются с s_, аналогичны функциям, начинающимся с r_, но код будет иметь доступ к ограниченным версиям стандартных потоков ввода-вывода sys.stdin, sys.stderr и sys.stdout.

RExec.s_eval(code)

code должен быть строкой, содержащей выражение Python, которое будет вычислено в ограниченной среде.

RExec.s_exec(code)

code должен быть строкой, содержащей одну или несколько строк кода Python, который будет выполнен в ограниченной среде.

RExec.s_execfile(code)

Выполнить код Python из файла filename в ограниченной среде.

Объекты 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 ограниченной среды).

И их эквиваленты с доступом к ограниченным стандартным потокам ввода-вывода:

RExec.s_import(modulename[, globals[, locals[, fromlist]]])

Импортирует модуль modulename, вызывая исключение ImportError, если модуль считается небезопасным.

RExec.s_reload(module)

Перезагружает объект модуля module, повторно разбирая и инициализируя его.

RExec.s_unload(module)

Выгрузить объект модуля module.

30.1.2. Определение ограниченных сред выполненияDefining restricted environments

Класс 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) в любом месте вашей файловой системы, например записав его в /tmp или загрузив в каталог /incoming вашего общедоступного FTP-сервера.

30.1.3. ПримерAn example

Допустим, мы хотим немного более мягкую политику, чем стандартный класс 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'):
            # проверить имя файла: должно начинаться с /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, что потребовало бы разбиения имени файла и выполнения различных операций. В случаях, когда безопасность стоит на кону, может быть предпочтительнее написать простой код, который иногда чрезмерно ограничивает, вместо более общего кода, который также более сложен и может содержать скрытые уязвимости.