LINUX.ORG.RU

О Лиспе.


0

0

Я не знаток сабжа, но мне интересно, есть ли там такая возможность. Допустим, я пишу некую программу, которая eval'ит введённые пользователем данные, может только слегка подправляет их чем-то типа регэкспов. Понятно, что тут автоматом возникает что-то типа sql injection, только хуже, выполнение произвольного кода. Если по честному проверять данные перед eval'ом - проще сделать традиционную обработку и не париться.

Но приходит в голову ещё одна мысль. А что, если eval'ить пользовательские данные в специальном контексте, из которого убрано всё, кроме того, что непосредственно нужно? Так возможно?

> Допустим, я пишу некую программу, которая eval'ит введённые пользователем данные, может только слегка подправляет их чем-то типа регэкспов

ну типа входной язык -- это DSL, который reader macro преобразуется в программу на лиспе и выполняется eval'ом

> Понятно, что тут автоматом возникает что-то типа sql injection, только хуже, выполнение произвольного кода.


не факт. SQL injection возникает когда и где? когда пользовательский ввод используется "как есть", не фильтруется. С учётом того, что входной язык трактуется как строка, и в строку можно засунуть всё, что угодно, комментарии, кавычку, код OR 1=1, и получается новая строка, которая потом целиком интерпретируется как одна строка.

С другой стороны, инъекции довольно просто избежать, если пользоваться SQL prepare statement с типизированными placeholders, и возможно проверять на валидность эти строки в placeholders, фильтруя эти символы.

В лиспе же мы работаем не со строками а с AST-деревьями. У них есть семантика (неявная, заданная самой формой, а не синтаксисом), возможны типы. Возможен более строгий статический pattern matching как в Хаскелле или ML, если не просто тупо переводить reader macro'ми строки в элементы списков, а проверять тип элементов. То есть, практически повторить те же соображения, что и с SQL prepare statement. Можно погуглить по pattern matching lisp. То есть, сделать что-то вроде Хаскеллевского maybe типа: строка входного языка преобразуется или в форму лиспа или в пустую, если некорректна.

> Если по честному проверять данные перед eval'ом - проще сделать традиционную обработку и не париться.


идея в том, что сначала обработка делается 1 раз, а потом много раз выполняется отprepareный запрос.

> А что, если eval'ить пользовательские данные в специальном контексте, из которого убрано всё, кроме того, что непосредственно нужно? Так возможно?


да возможно наверно в чем-то вроде того же ECL. Символы определяются в заданных пространствах имён, в CL-USER по умолчанию. Когда лисп встраивается в Си приложение, там неявно присутсвует ссылка на эти таблицы символов, их и переключать.

Или написать свой вариант eval, который переводит формы в формы из указанного нового sandbox'а "специального контекста". То есть eval выполняется как замыкание в этом новом контексте. Всякие злобные бяки в этом контексте просто не отображены.

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

anonymous
()

> Понятно, что тут автоматом возникает что-то типа sql injection, только хуже, выполнение произвольного кода.

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

anonymous
()

(defun break-loop (fn quit &rest args)
  (format *query-io* "Entering break-loop.~%")
  (loop
    (let ((in (apply #'prompt args)))
      (if (funcall quit in)
          (return)
          (format *query-io* "~A~%" (funcall fn in))))))


> (break-loop #'eval #'(lambda (x) (eq x :q)) ">> ")
Entering break-loop.
>> (+ 2 3)
5
>> :q
:Q

КУ: П. Грэхам. "On Lisp" 

P.S. Втыкаем, по необходимости, фильтр, что можно тут вычислять/вызывать
 и вперёд.

marsijanin ★★
()

> А что, если eval'ить пользовательские данные в специальном контексте, из которого убрано всё, кроме того, что непосредственно нужно? Так возможно?

Возможно. Кури тему packages.

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

> А что, если eval'ить пользовательские данные в специальном контексте, из которого убрано всё, кроме того, что непосредственно нужно? Так возможно?

Возможно, но разрушение будет необратимым. Зачитывать код Вы будете всё равно с помощью read. А read может считать любой символ лиспа, если его задать как package::symbol. Значит, нужно убить все пакеты, кроме Вашего пакета. После этого, естественно, будет уже невозможно разрабатывать Ваше приложение "на лету". Также будут проблемы с выводом. Да и вообще это может не получиться из-за особенностей реализации (например, пакет lisp может быть защищён от записи и удаления). Если идти этим путём, лучше уж сделать свой вариант read, который будет содержать встроенный фильтр, отличающий допустимые символы.

Можно поступить и по-другому, написав свой интерпретатор лиспа. Вообще, интерпретатор лиспа - это цикл read-eval-print (repl)

Т.е., если написать (loop (print (eval (read)))) то получится простейший интерпретатор лиспа.

Можно сделать так: (loop (print (eval (filter (read)))))

где Ваша функция filter обходит дерево, задаваемое своим единственным аргументом и проверяет все атомы на принадлежность к допустимому множеству. Тут, правда, нужно учитывать все тонкости функции read. Поэтому, может быть, что написать свой read с нуля будет более выгодно.

В любом из этих двух вариантов, решение получится абсолютно переносимым. И, конечно, на самом деле, интерпретатор лиспа несколько сложнее - он включает отладчик, обработку ошибок и т.п. Но для Вашей задачи всё равно нужен специализированный интерпретатор.

Вообще, надо отметить, что анонимусы написали в теме немало бреда. Не знают лиспа.

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