Skip to content Skip to sidebar Skip to footer

Python Eval(compile(...), Sandbox), Globals Go In Sandbox Unless In Def, Why?

Consider the following: def test(s): globals()['a'] = s sandbox = {'test': test} py_str = 'test('Setting A')\nglobals()['b'] = 'Setting B'' eval(compile(py_str, '

Solution 1:

When you call a function in Python, the global variables it sees are always the globals of the module it was defined in. (If this wasn't true, the function might not work -- it might actually need some global values, and you don't necessarily know which those are.) Specifying a dictionary of globals with exec or eval() only affects the globals that the code being exec'd or eval()'d sees.

If you want a function to see other globals, then, you do indeed have to include the function definition in the string you pass to exec or eval(). When you do, the function's "module" is the string it was compiled from, with its own globals (i.e., those you supplied).

You could get around this by creating a new function with the same code object as the one you're calling but a different func_globals attribute that points to your globals dict, but this is fairly advanced hackery and probably not worth it. Still, here's how you'd do it:

# 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

Solution 2:

Sandboxing code for exec by providing alternative globals/locals has lots of caveats:

  • The alternative globals/locals only apply for the code in the sandbox. They do not affect anything outside of it, they can't affect anything outside of it, and it wouldn't make sense if they could.

    To put it another way, your so-called "sandbox" passes the object test to the code ran by exec. To change the globals that test sees it would also have to modify the object, not pass it as it is. That's not really possible in any way that would keep it working, much less in a way that the object would continue to do something meaningful.

  • By using the alternative globals, anything in the sandbox would still see the builtins. If you want to hide some or all builtins from the code inside the sandbox you need to add a "__builtins__" key to your dictionary that points to either None (disables all the builtins) or to your version of them. This also restricts certain attributes of the objects, for example accessing func_globals attribute of a function will be disabled.

  • Even if you remove the builtins, the sandbox will still not be safe. Sandbox only code that you trust in the first place.

Here's a simple proof of concept:

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 indict(__builtins__=None)
# ...even though the following raisesexec"(lambda:None).func_globals"indict(__builtins__=None)

Solution 3:

External execution contexts are defined statically in Python (f.func_globals is read-only), so I would say that what you want is not possible. The reason is because the function could become invalid Python it its definition context is changed at runtime. If the language allowed it, it would be an extremely easy route for injection of malicious code into library calls.

defmycheck(s): 
    returnTrueexec priviledged_code in {'check_password':mycheck}

Post a Comment for "Python Eval(compile(...), Sandbox), Globals Go In Sandbox Unless In Def, Why?"