zopefoundation / RestrictedPython

A restricted execution environment for Python to run untrusted code.
http://restrictedpython.readthedocs.io/
Other
456 stars 38 forks source link

Can't get function definition to work when overriding builtins #251

Closed manziman closed 1 year ago

manziman commented 1 year ago

BUG/PROBLEM REPORT / FEATURE REQUEST

Perhaps I don't understand how this is supposed to work, but I cannot get this code to work properly.

I am trying to use RestrictedPython to 'safely' (relatively) execute untrusted code in the form of functions defined in strings.

What I did:

I tried to run the example from the docs, the following code:

from RestrictedPython import compile_restricted

from RestrictedPython import safe_builtins
from RestrictedPython import limited_builtins
from RestrictedPython import utility_builtins

source_code = """
def do_something():
    pass
"""

try:
    byte_code = compile_restricted(
        source_code,
        filename='<inline code>',
        mode='exec'
    )
    exec(byte_code, {'__builtins__': safe_builtins}, None)
except SyntaxError as e:
    pass
do_something()

What I expect to happen:

do_something() is defined and executes

What actually happened:

do_something is not defined.

What version of Python and Zope/Addons I am using:

Python: 3.10 RestrictedPython: 6.0 WSL2 - ubuntu

icemac commented 1 year ago

do_something() has to be called inside your source_code. Calling exec does not push its definition to the outer (global) namespace.

manziman commented 1 year ago

do_something() has to be called inside your source_code. Calling exec does not push its definition to the outer (global) namespace.

That's not true for exec in general, do you mean for how it is being used here specifically? For instance, if I use regular compile and exec I can get it to define a function:

source_code = """
def do_something2():
    return 'foobar'
"""

try:
    byte_code = compile(
        source_code,
        filename='<inline code>',
        mode='exec'
    )
    exec(byte_code)
except SyntaxError as e:
    pass
print(do_something2())

Try it out, this will print foobar.

This also works as I would expect, using restricted compile the function is defined but is unable to make a call to print as it has been overridden during compilation:

source_code = """
def do_something():
    print('foobar')
"""

byte_code = compile_restricted(
    source_code,
    filename='<inline code>',
    mode='exec'
)
exec(byte_code)
print(type(do_something))
do_something()

This will output:

<class 'function'>
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[8], line 13
     11 exec(byte_code)
     12 print(type(do_something))
---> 13 do_something()

File <inline code>:2, in do_something()

NameError: name '_print_' is not defined

So, it is defined but does not work.

However if you take the same exact example and override globals in exec, the function is not defined (as shown in the example above).

I am thinking that I just don't quite understand how the restricted execution works. Does it prevent the executed code from defining functions by default?

bborn commented 1 year ago

@manziman Try assigning the function to a local variable and then access that outside the scope.

Something like this:

source_code = """
def do_something():
    return "Something"

result = do_something
"""

locals = {}

try:
    byte_code = compile_restricted(
        source_code,
        filename='<inline code>',
        mode='exec'
    )
    exec(byte_code, {'__builtins__': safe_builtins}, locals)
except SyntaxError as e:
    pass

locals['result']()
manziman commented 1 year ago

@bborn I was just wondering if something like that would work - thanks for the tip!