exec ignores custom `__builtins__` in globals

Issue #2653 wontfix
Michael Howitz
created an issue

Given the following code:

code = compile('import os', '<string>', 'exec')
glb = {'__builtins__': {'__import__': lambda *a: 42}}

exec(code, glb)
print(glb['os'])

PyPy behaves differently from CPython:

  • Python 2.7.13 prints 42
  • Python 3.6.2 prints 42
  • PyPy 5.8.0 prints <module 'os' from '/opt/local/lib/pypy/lib-python/2.7/os.py'>
  • PyPy3 5.5.0 prints <module 'os' from '/Users/mac/python/pypy3-v5.5.0-osx64/lib-python/3/os.py'>

This prevents using a custom __import__ function.

Comments (7)

  1. Michael Howitz reporter

    If __import__ does not exist in __builtins__ the import statement still can be used.

    Given this code:

    code = compile('import os', '<string>', 'exec')
    glb = {'__builtins__': {}}
    
    exec(code, glb)
    print(glb['os'])
    

    PyPy 2 and 3 can import os but CPython 2 and 3 raise an exception like this for the line containing the exec statement:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1, in <module>
    ImportError: __import__ not found
    

    This prevents forbidding to import using exec.

  2. Michael Howitz reporter

    Maybe the problem is actually in exec ignoring a custom __builtins__ of the given globals.

    Given this code:

    code = compile("open('/etc/passwd')", '<string>', 'exec')
    glb = {'__builtins__': {}}
    
    exec(code, glb)
    

    PyPy 2 and 3 run this code without an error whereas CPython 2 and 3 raise an exception like this:

    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1, in <module>
    NameError: name 'open' is not defined
    

    This prevents from restricting __builtins__ at all.

  3. Armin Rigo

    If you're playing with __builtins__ in an attempt to have security, this attempt is flawed in Python. That's right, PyPy ignores custom __builtins__, which is an incompatibility. Maybe we need to revisit that design decision at some point. Up to now, though, it has done a good job of telling people "don't do that, it only provides easy-to-workaround fake security".

  4. Armin Rigo

    I suspect (but I don't know) that RestrictedPython is based on other CPython-only tricks, too. For example, some attributes of built-in types are hidden in CPython when the current builtin dictionary doesn't match the default builtin. We have not implemented that in PyPy.

    Someone would need to do an in-depth review of what exactly are the security holes that PyPy leaves open. Until then, I would personally consider RestrictedPython unsafe on PyPy, even if a quick hack at __builtins__ is possible. In fact, you can (or could at some point) translate PyPy with the command line:

    rpython -Ojit targetpypystandalone.py --objspace-honor__builtins__
    

    but as I said there are probably other PyPy-specific holes.

  5. Armin Rigo

    There is no point if it's not also done with an in-depth review of all other related issues---for example, the RESTRICTED flag that CPython puts on some attributes of built-in objects.

    This issue is closed as "Won't fix" unless someone wants to seriously do that review. The reason is lacking dedicated manpower ourselves for security-sensible topics; it is the same reason for why sandboxing is in limbo.

  6. Log in to comment