Python eval (компиляция (...), песочница), globals идут в песочнице, если только в def, почему?

Рассмотрим следующее:

def test(s):
    globals()['a'] = s

sandbox = {'test': test}
py_str = 'test("Setting A")\nglobals()["b"] = "Setting B"'
eval(compile(py_str, '<string>', 'exec'), sandbox)

'a' in sandbox # returns False, !What I dont want!
'b' in sandbox # returns True, What I want
'a' in globals() # returns True, !What I dont want!
'b' in globals() # returns False, What I want

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

Спасибо за любой ввод

Решение

def test(s):
    globals()['a'] = s

sandbox = {}

# create a new version of test() that uses the sandbox for its globals
newtest = type(test)(test.func_code, sandbox, test.func_name, test.func_defaults,
                     test.func_closure)

# add the sandboxed version of test() to the sandbox
sandbox["test"] = newtest

py_str = 'test("Setting A")\nglobals()["b"] = "Setting B"'
eval(compile(py_str, '<string>', 'exec'), sandbox)

'a' in sandbox # returns True
'b' in sandbox # returns True
'a' in globals() # returns False
'b' in globals() # returns False

Ответ 1

Когда вы вызываете функцию в Python, глобальные переменные, которые она видит, всегда являются глобальными значениями модуля, в котором он был определен. (Если это не так, функция может не работать - на самом деле, возможно, потребуются некоторые глобальные значения, и вы не обязательно знаете, что это такое.) Указание словаря глобальных символов с exec или eval() влияет только на глобальные переменные, которые видит код exec 'd или eval()' d.

Если вы хотите, чтобы функция увидела другие глобальные переменные, вам действительно нужно включить определение функции в строку, переданную в exec или eval(). Когда вы это делаете, функция "module" - это строка, из которой она была скомпилирована, с ее собственными глобальными (то есть теми, которые вы предоставили).

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

# create a sandbox globals dict
sandbox = {}

# create a new version of test() that uses the sandbox for its globals
newtest = type(test)(test.func_code, sandbox, test.func_name, test.func_defaults,
                     test.func_closure)

# add the sandboxed version of test() to the sandbox
sandbox["test"] = newtest

Ответ 2

Контексты внешнего исполнения определяются статически в Python (f.func_globals доступен только для чтения), поэтому я бы сказал, что то, что вы хотите, невозможно. Причина в том, что функция может стать недействительной Python, ее контекст определения изменяется во время выполнения. Если бы этот язык допустил это, это был бы очень простой путь для инъекции вредоносного кода в вызовы библиотеки.

def mycheck(s): 
    return True

exec priviledged_code in {'check_password':mycheck}

Ответ 3

Код песочницы для exec, предоставляя альтернативные глобальные/локальные ресурсы, имеет много предостережений:

  • Альтернативные глобальные переменные /locals применяются только для кода в песочнице. Они не влияют ни на что иное, они не могут повлиять на что-либо вне его, и это не имело бы смысла, если бы они могли.

    Другими словами, ваша так называемая "песочница" передает объект test в код, исполняемый exec. Чтобы изменить глобальные переменные, которые видит test, он также должен будет изменить объект, а не передавать его, как есть. Это никоим образом не может быть таким, чтобы это работало, а тем более в том, что объект будет продолжать делать что-то значимое.

  • Используя альтернативные глобальные переменные, все, что находится в sandbox, все равно будет видеть встроенные функции. Если вы хотите скрыть некоторые или все встроенные функции из кода внутри песочницы, вам нужно добавить ключ "__builtins__" в словарь, который указывает либо на None (отключает все встроенные функции), либо на вашу версию. Это также ограничивает определенные атрибуты объектов, например, доступ к атрибуту func_globals функции будет отключен.

  • Даже если вы удалите встроенные функции, песочница будет не безопасна. Песочница только код, которому вы доверяете в первую очередь.

Здесь простое доказательство понятия:

import subprocess
code = """[x for x in ().__class__.__bases__[0].__subclasses__() 
           if x.__name__ == 'Popen'][0](['ls', '-la']).wait()"""
# The following runs "ls"...
exec code in dict(__builtins__=None)
# ...even though the following raises
exec "(lambda:None).func_globals" in dict(__builtins__=None)