Safe
СОДЕРЖАНИЕ
ИМЯ
Safe — Компиляция и выполнение кода в ограниченных отсеках
СИНТАКСИС
use Safe;
$compartment = new Safe;
$compartment->permit(qw(time sort :browse));
$result = $compartment->reval($unsafe_code); ОПИСАНИЕ
Модуль расширения Safe позволяет создавать отсеки, в которых можно оценивать код Perl. Каждый отсек имеет
- новый namespace
-
«Корень» namespace (т. е. «main::») изменяется на другой пакет, и код, выполняемый в отсеке, не может ссылаться на переменные за пределами этого namespace, даже с помощью динамических поиска глобальных переменных и других уловок.
Код, скомпилированный вне отсека, может помещать переменные в (или «разделять» переменные с) namespace отсека, и только эти данные будут видны коду, выполняемому в отсеке.
По умолчанию, с отсеками совместно используются только переменные «подчёркивание» $_ и @_ (и, технически, реже используемые %_, файловый дескриптор _ и т. д.). Это связано с тем, что в противном случае операторы Perl, по умолчанию использующие $_, не будут работать, и также не будет работать присваивание аргументов @_ при входе в подпрограмму.
- маску операторов
-
Каждый отсек имеет связанную «маску операторов». Вспомните, что код Perl компилируется во внутренний формат перед выполнением. Выполнение кода Perl (например, с помощью «eval» или «do 'file'») приводит к компиляции кода во внутренний формат, а затем, при отсутствии ошибок компиляции, к его выполнению. Код, выполняемый в отсеке, компилируется с учётом маски операторов отсека. Попытка выполнить код в отсеке, содержащем замаскированный оператор, приведёт к ошибке компиляции. Код не будет выполнен.
По умолчанию маска операторов для вновь созданного отсека — ':default' optag.
Важно прочитать документацию модуля Opcode, особенно для получения подробных определений opname, optag и opset.
Поскольку маска операторов применяется только на стадии компиляции, контролируемый доступ к потенциально небезопасным операциям можно получить, поместив в отсек обработчик подпрограммы (написанный вне отсека). Например,
$cpt = new Safe; sub wrapper { # vet arguments and perform potentially unsafe operations } $cpt->share('&wrapper');
ПРЕДУПРЕЖДЕНИЕ
Модуль Safe не реализует эффективную песочницу для выполнения недоверенного кода с помощью интерпретатора Perl.
Ошибки в интерпретаторе Perl, которые можно использовать для обхода ограничений Safe, не рассматриваются как уязвимости. См. perlsecpolicy для дополнительной информации.
Авторы не дают никаких гарантий, явных или подразумеваемых, о пригодности этого программного обеспечения для целей безопасности.
Авторы ни в коем случае не несут ответственности за особые, случайные, косвенные, побочные или другие аналогичные убытки, возникающие в результате использования этого программного обеспечения.
Результат может отличаться. В случае сомнений не используйте его.
МЕТОДЫ
Для создания нового отсека используйте
$cpt = new Safe; Необязательный аргумент — (NAMESPACE), где NAMESPACE — корневой namespace, который следует использовать для отсека (по умолчанию «Safe::Root0», инкрементируется для каждого нового отсека).
Обратите внимание, что версия 1.00 модуля Safe поддерживала второй необязательный параметр MASK. Эта функциональность была удалена до дальнейшего рассмотрения. Используйте методы permit и deny, описанные ниже.
Затем на объект отсека, возвращаемый вышеуказанным конструктором, можно использовать следующие методы. Объект аргумент неявный в каждом случае.
permit (OP, ...)
Разрешить перечисленные операторы для использования при компиляции кода в отсеке (в дополнение к уже разрешённым операторам).
Вы можете указать op-коды по именам или использовать имя тега; см. "Предопределённые теги op-кода" в Opcode.
permit_only (OP, ...)
Разрешить только перечисленные операторы для использования при компиляции кода в отсеке (никакие другие операторы не разрешены).
deny (OP, ...)
Запретить перечисленные операторы при компиляции кода в отсеке (другие операторы могут быть всё ещё разрешены).
deny_only (OP, ...)
Запретить только перечисленные операторы для использования при компиляции кода в отсеке (все остальные операторы разрешены, поэтому вы, вероятно, не захотите использовать этот метод).
trap (OP, ...), untrap (OP, ...)
Методы trap и untrap являются синонимами для deny и permit соответственно.
share (NAME, ...)
Это разделяет переменную(ые) в списке аргументов с отсеком. Это почти идентично экспорту переменных с помощью модуля Exporter.
Каждое NAME должно быть именем нелексической переменной, обычно с включённым идентификатором типа. Слово без привязки обрабатывается как имя функции.
Примеры допустимых имён — '$foo' для скаляра, '@foo' для массива, '%foo' для хэша, '&foo' или 'foo' для подпрограммы и '*foo' для глобальной переменной (т. е. все записи таблицы символов, связанные с «foo», включая скаляр, массив, хэш, подпрограмму и файловый дескриптор).
Каждое NAME предполагается в вызывающем пакете. См. share_from для альтернативного метода (который share использует).
share_from (PACKAGE, ARRAYREF)
Этот метод похож на share(), но позволяет явно указать пакет, символы которого следует разделить. Имена символов (включая символы типа) передаются как ссылка на массив.
$safe->share_from('main', [ '$foo', '%bar', 'func' ]); Имена могут включать имена пакетов, которые являются относительными по отношению к указанному PACKAGE. Следовательно, эти два вызова имеют одинаковый эффект:
$safe->share_from('Scalar::Util', [ 'reftype' ]);
$safe->share_from('main', [ 'Scalar::Util::reftype' ]); varglob (VARNAME)
Это возвращает ссылку на глобальную переменную для записи в таблице символов VARNAME в пакете отсека. VARNAME должен быть именем переменной без какого-либо ведущего маркера типа. Например:
${$cpt->varglob('foo')} = "Hello world"; имеет тот же эффект, что и:
$cpt = new Safe 'Root';
$Root::foo = "Hello world"; но избегает необходимости знать имя пакета $cpt.
reval (STRING, STRICT)
Это оценивает STRING как код Perl внутри отсека.
Код может видеть только namespace отсека (как возвращается методом root). Корневой пакет отсека выглядит как пакет main:: для кода внутри отсека.
Любая попытка кода в STRING использовать оператор, который не разрешён отсеком, вызовет ошибку (во время выполнения основной программы, но во время компиляции для кода в STRING). Ошибка имеет вид «'%s' trapped by operation mask…».
Если операция блокируется таким образом, то код в STRING не будет выполнен. Если такая заблокированная операция произойдёт или любая другая ошибка во время компиляции или возврата, то $@ будет установлена на сообщение об ошибке, как и в случае с eval().
Если ошибок нет, метод возвращает значение последнего вычисленного выражения или может быть использовано оператор возврата, аналогично подпрограммам и eval(). Контекст (список или скаляр) определяется вызывающим элементом, как обычно.
Если возвращаемое значение reval() является (или содержит) ссылкой на код, эти ссылки на код оборачиваются так, чтобы они всегда выполнялись в отсеке. См. "wrap_code_refs_within".
Прежде не документированный аргумент STRICT устанавливает строгость: если он имеет значение true, используется 'use strict;', в противном случае — 'no strict;'. Примечание: если STRICT опущен, по умолчанию используется 'no strict;'.
Некоторые моменты, на которые следует обратить внимание:
Если оператор entereval разрешён, то код может использовать eval "..." для «скрытия» кода, который может использовать запрещённые операторы. Это не является большой проблемой, так как при попытке выполнения кода eval он потерпит неудачу, потому что opmask всё ещё активен. Однако этот метод позволит хитрому, и, возможно, вредному коду «зондировать» границы возможностей.
Любой строковый eval, который выполняется кодом, выполняющимся в отсеке, или кодом, вызываемым кодом, выполняющимся в отсеке, будет оценен в namespace отсека. Это потенциально серьёзная проблема.
Рассмотрим функцию foo() в пакете pkg, скомпилированную вне отсека, но разделяемую с ним. Предположим, что отсек имеет корневой пакет «Root». Если foo() содержит оператор eval, подобный eval '$foo = 1', то, обычно, $pkg::foo будет установлен в 1. Если foo() вызывается из отсека (любым способом), то вместо установки $pkg::foo, eval фактически установит $Root::pkg::foo.
Это легко продемонстрировать, используя модуль, такой как модуль Socket, который использует eval "..." в качестве части функции AUTOLOAD. Вы можете «использовать» модуль вне отсека и разделить (автозагружаемую) функцию с отсеком. Если вызов AUTOLOAD инициируется кодом в отсеке или любым кодом где-либо, вызываемым любым способом из отсека, то eval в функции AUTOLOAD модуля Socket происходит в namespace отсека. Любые переменные, созданные или используемые кодом eval, теперь находятся под контролем кода в отсеке.
Аналогичный эффект имеет место при всех поисках символов во время выполнения в коде, вызываемом из отсека, но не скомпилированном в нём.
rdo (FILENAME)
Это оценивает содержимое файла FILENAME внутри отсека. Оно использует те же правила, что и встроенный Perl do для поиска файла, возможно, используя @INC.
См. документацию метода reval для получения дополнительной информации.
root (NAMESPACE)
Этот метод возвращает имя пакета, являющегося корнем namespace отсека.
Обратите внимание, что это поведение отличается от версии 1.00 модуля Safe, где корневой модуль мог использоваться для изменения пространства имён. Эта функциональность была удалена до дальнейшего рассмотрения.
mask (MASK)
Это метод получения или задания маски оператора отсека.
При отсутствии аргумента MASK он возвращает текущую маску оператора отсека.
При наличии аргумента MASK он устанавливает маску оператора для отсека (эквивалентно вызову метода deny_only).
wrap_code_ref (CODEREF)
Возвращает ссылку на анонимную подпрограмму, которая при выполнении вызовет CODEREF с отсеком Safe «в действии». Другими словами, с настроенным пространством имён пакета и включенной маской оператора.
Обратите внимание, что маска оператора не влияет на уже скомпилированный код, она влияет только на любую дальнейшую компиляцию, которую уже скомпилированный код может попытаться выполнить.
Это особенно полезно при применении к ссылкам на код, возвращаемым из reval().
(Это также обеспечивает своего рода обходной путь для RT#60374: «Safe.pm сортирует ошибку {} с -Dusethreads». См. https://rt.perl.org/rt3//Public/Bug/Display.html?id=60374 для более подробной информации.)
wrap_code_refs_within (...)
Обертывает все ссылки CODE, найденные в аргументах, заменяя каждую результатом вызова "wrap_code_ref" на ссылку CODE. Любые ссылки ARRAY или HASH в аргументах проверяются рекурсивно.
Не возвращает ничего.
ОПАСНОСТИ
Этот раздел представляет собой лишь обзор некоторых вещей, которые код в отсеке может делать (намеренно или непреднамеренно), что может повлиять за пределами отсека.
- Память
-
Потребление всей (или почти всей) доступной памяти.
- Процессор
-
Вызывать бесконечные циклы и т. п.
- Шпионирование
-
Копирование конфиденциальной информации из вашей системы. Даже что-то такое простое, как ваше имя пользователя, имеет ценность для других. Например, из ваших переменных окружения можно извлечь много полезной информации.
- Сигналы
-
Вызывать сигналы (особенно SIGFPE и SIGALARM) для воздействия на ваш процесс.
Настройка обработчика сигналов должна быть тщательно продумана и контролируема. Какая маска активна, когда вызывается обработчик сигнала? Если пользователь может заставить импортированную функцию получить исключение и вызвать обработчик сигнала пользователя, восстанавливается ли ограниченная маска пользователя перед вызовом обработчика? Вызывается ли импортированный обработчик со своей исходной маской или маской пользователя?
- Изменения состояния
-
Операции, такие как chdir, очевидно, влияют на весь процесс, а не только на код в отсеке. Операции, такие как rand и srand, имеют аналогичное, но более тонкое влияние.
АВТОР
Изначально разработан и реализован Malcolm Beattie.
Переработан для использования модуля Opcode и других изменений, добавленных Tim Bunce.
В настоящее время поддерживается Perl 5 Портерами, <perl5-porters@perl.org>.
© 1993–2023 Larry Wall and others
Licensed under the GNU General Public License version 1 or later, or the Artistic License.
The Perl logo is a trademark of the Perl Foundation.
https://perldoc.perl.org/5.38.0/Safe