LINUX.ORG.RU

CL: безопасный eval


0

0

Добрый день,

Есть необходимость в программе на Common Lisp загружать из внешнего файла потенциально небезопасный код и исполнять его.

Хочется сделать это в некоем sandbox'е, предоставляя доступ к очень ограниченному подмножеству стандартных и не очень библиотек.

Вопрос: как это сделать?

Текущий аналог в Python выглядит примерно так:

val = eval(f.read(), {"__builtins__": None}, {"range": range})

Готового решения не знаю, могу предложить следующую схему:

  1. Создать список разрешённых символов
  2. Создать отдельный пакет, не импортировать в него ничего кроме имен из этого списка
  3. Прочитать выражения из файла, предварительно связав (например, через let) *package* с созданным пакетом
  4. Проверить считанные выражения, там должны быть символы только из созданного пакета или из списка разрешённых символов, иначе - ошибка
  5. Вызвать eval
  6. Оформить решение в виде библиотеки и опубликовать на каком-нибудь хостинге (мне нравится github)
archimag ★★★
()
Ответ на: комментарий от archimag

А зачем пункт 4, ведь есть пункт 3 и кроме того это не спасет от eval(generated_sexp)? Или подразумевается что в песочнице недоступен eval? Мне кажется достаточно пунктов 1-3,5-6 + отключить в лексере обращения к другим пакетам и отключить изменение лексера.

anonymous
()
Ответ на: комментарий от anonymous

> Или подразумевается что в песочнице недоступен eval?

Песочница с eval? Ничего себе песочница... Впрочем, в неё под видом eval можно экспортировать «безопасный eval», тогда не страшно.

А зачем пункт 4


Что бы защититься от выражений типа (cl:eval ...)

отключить в лексере обращения к другим пакетам

и отключить изменение лексера.



Не понял, что имеется в виду, как это сделать?

archimag ★★★
()
Ответ на: комментарий от archimag

Ну наверное как-нибудь с помошью *readtable*. Понятия не имею как, просто как вариант. Ок, с eval все понятно.

anonymous
()
Ответ на: комментарий от anonymous

> Ну наверное как-нибудь с помошью *readtable*. Понятия не имею как, просто как вариант. Ок, с eval все понятно.

я конечно сам не спец по CL, но заметил такую вещь в LispWors:

проверка на наличия символа в списке експорта пакета, а также проверка на наличие пакета с заданным именем происходит в read-time при вводе символа #\: после непробельного символа, однако 1) sbcl не проводит такой проверки в read-time; 2) с символом #\: не асоциированы ни macro-character, ни dispatch-macro-character, при том, что ACL не предоставляет (вроде как) других средств для модификации read-table. вывод из этого: реакция на #\: «вшита» в реализацию. правда я главу Reader HyperSpec'а не досконально изучил, так что... =)

korvin_ ★★★★★
()
Ответ на: комментарий от korvin_

> sbcl не проводит такой проверки в read-time

точнее скорее проводит в read-time, просто реакция и «reader-terminal» отличается от LW'шного

korvin_ ★★★★★
()

В общем, если после выполнения кода можно определить его небезопасность (а иначе задача не имеет смысла), то можно просто форкнуться, выполнить в чайлде код, выйти с 0 (хорошо) или с не-0 (плохо, sigsegv, etc). Предок по waitpid смотрит, как вышел чайлд и принимает решение об исполнении кода в своём образе.

Как-то так:

(defun rep (expr)
  (eval (read-from-string expr)))

(defun safe-eval (expr)
  (let ((pid (sb-posix:fork)))
    (cond
      ((plusp pid)
       (multiple-value-bind (pid status)
         (sb-posix:waitpid pid 0)
        (declare (ignore pid))
        (when (zerop status)
         (rep expr))))
      ((zerop pid)
       (handler-case
         (rep expr)
        (error ()
          (sb-ext:quit :unix-status 1)))
       (sb-ext:quit :unix-status 0))
      (t
       (error "fork failed~%")))))

mv ★★★★★
()
Ответ на: комментарий от mv

От загружаемого кода ожидается, что он формирует/описывает некую сложную структуру данных.

Опять-таки, пример из python:

{"numbers" : [0, 1, 2, 3, 42],
 ("greek", "letters") : ("alpha", "beta", "gamma"),
 "prime_lt_50" : [x for x in range(2, 50) if x not in
                  [j for i in range(2, 8) for j in range(i*2, 50, i)]]}

Всё, что не нужно для создания такой структуры, должно быть запрещено: доступ к ФС, к сети, к ресурсам ОС (создание потоков/процессов), выделение миллионов памяти и т.п.

При этом доступ к некоторым средствам языка и библиотекам всё-таки нужно разрешить, чтобы упростить создание структуры.

redbonnet
() автор топика
Ответ на: комментарий от archimag

> Что бы защититься от выражений типа (cl:eval ...)

П.2 не обеспечивает невидимость cl:?

За общую идею спасибо, пойду пробовать.

redbonnet
() автор топика

гугл на запрос eval untrusted lisp code выдаёт много интересного.

В let over lambda предлагается функция safe-read-from-string

http://letoverlambda.com/index.cl/guest/chap4.html#sec_6 :

«Safe-read-from-string is a partial answer to the problem of reader security. This function is less ready for production use than most of the other code in this book. You are advised to think carefully about the security requirements of your application and adapt (or even re-write) this code for your application. Safe-read-from-string is a very locked down version of read-from-string. It has its own copy of the default lisp readtable. This copy has had most of the interesting read macros removed, including the # dispatching macro. That means that vectors, bit-vectors, gensyms, circular references, #., and all the rest are out. Safe-read-from-string will not even allow keywords or foreign package symbols. It will, however, allow any cons structure, not just well formed lists. It also allows numbers15 Exercise: What is the one class of numbers not allowed? and strings.»

Далее выхлоп safe-read-from-string можно скармливать eval'у.

gorilych ★★
()
Ответ на: комментарий от redbonnet

Всё, что не нужно для создания такой структуры, должно быть запрещено: доступ к ФС, к сети, к ресурсам ОС (создание потоков/процессов), выделение миллионов памяти и т.п.

Да, но при чём тут тогда язык? Если вам нужно ограничить процесс на уровне операционной системы, органичивайте его на уровне операционной системы (selinux, например). Или пишите безопасный DSL.

mv ★★★★★
()
Ответ на: комментарий от mv

> Да, но при чём тут тогда язык? Если вам нужно ограничить процесс на уровне операционной системы, органичивайте его на уровне операционной системы (selinux, например). Или пишите безопасный DSL.

а PLT Scheme поддерживает ограничение ресурсов (sandbox, custodians) искаробки... =)

korvin_ ★★★★★
()
Ответ на: комментарий от mv

Так и хочется получить безопасный a la DSL с минимумом усилий.

redbonnet
() автор топика
Ответ на: комментарий от Divius

А что, в Python уже есть макросы, ридер макросы, аналог CLOS, сигнальный протокол, вменяемые варианты с мультитредингом, и, главное, вменяемая скорость исполнения?

Нет? Тогда ты перданул в лужу.

Love5an
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.