4

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, '<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

I'm not even sure how to ask, but I want the global scope for a function to be the environment I intend to run it in without having to compile the function during the eval. Is this possible?

Thanks for any input

Solution

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
Daniel Skinner
  • 210
  • 4
  • 10
  • What do you want to do, and in what way it differs from using a class and a method? Make `test` a method of a class, and make it use the namespace `self`. In what way does this not work for you? – Rosh Oxymoron Dec 29 '10 at 23:41
  • The only thing I am looking to do is dive deeper into the inner workings of python out of my personal interest in researching the language in depth. Like most things, this question stems from a real world problem, but one I've already solved. This is simply the pursuit of knowledge. – Daniel Skinner Dec 30 '10 at 18:47

3 Answers3

6

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
kindall
  • 178,883
  • 35
  • 278
  • 309
  • Thank you, this is enough to point me in the right direction for more research, in which I found this link. http://code.activestate.com/recipes/577283-decorator-to-expose-local-variables-of-a-function-/ As noted from the link, "I tried modifying f.func_globals with a custom dictionary [...] this does not work as the Python interpreter does not call the func_globals dictionary with Python calls but directly with PyDict_GetItem" I'm tired and not completely comprehensible, but it looks like its not possible with pure python hackery. I am going to have fun with byteplay though. Thanks Again – Daniel Skinner Dec 29 '10 at 23:16
  • Added example of creating a new version of the function that sees the sandbox. – kindall Dec 30 '10 at 17:47
  • very cool, I didn't know a type object was callable like that, the python docs dont particularly hint to such a thing as it redirects attention, mine at the least, to using it as a constructor for classes. – Daniel Skinner Dec 30 '10 at 18:45
  • 1
    Yeah, a type is basically a class, so once you have something's type, you have a constructor for making a new one. Calling a class or type *is* constructing. You can do `help(type(lambda: 0))` to get information on calling the `function` type as a constructor. You will see a similar technique used to convert a plain function to a bound method that can be attached to an instance (using the `instancemethod` constructor). – kindall Dec 30 '10 at 20:55
3

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 in dict(__builtins__=None)
# ...even though the following raises
exec "(lambda:None).func_globals" in dict(__builtins__=None)
Rosh Oxymoron
  • 20,355
  • 6
  • 41
  • 43
  • 1
    While this is informative, it doesn't deal with the topic at hand. I wasn't needing a lesson regarding the caveats/insecurities of eval. There's already plenty of other questions here on stackoverflow that cover this topic. – Daniel Skinner Dec 30 '10 at 16:16
2

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.

def mycheck(s): 
    return True

exec priviledged_code in {'check_password':mycheck}
Apalala
  • 9,017
  • 3
  • 30
  • 48