zopefoundation / RestrictedPython

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

Get error when defining a class in script: "name '__name__' is not defined" #204

Closed aliasmac closed 3 years ago

aliasmac commented 4 years ago

What I did:

I am using restrictedPython to let users build classes in their scripts. However when the script is executed I get the following error:

NameError: name '__name__' is not defined".

I'm using AccessControl.ZopeGuards which already has the the __metaclass__ property set via get_safe_globals so not entirely sure why it's happening. An extract of my code:


    from RestrictedPython import compile_restricted
    from RestrictedPython.Eval import default_guarded_getiter
    from AccessControl.ZopeGuards import get_safe_globals, safe_builtins

    ......

    variable_dict = build_dict_map()
    script = build_script()

    safe_locals = {}
    safe_globals = get_safe_globals()
    safe_globals['__builtins__'] = safe_builtins
    safe_globals['_getiter_'] = default_guarded_getiter
    safe_globals['input'] = variable_dict

    # Compile
    compiled = None
    try:
        compiled = compile_restricted(script, "script", 'exec')
    except SyntaxError as err:
        err_response = build_err_response(parsed_event, sys.exc_info(), err)
        return {
            "body": json.dumps(err_response, indent=2)
        }
    except Exception as err:
        err_response = build_err_response(
            parsed_event, sys.exc_info(), err, True)
        return {
            "body": json.dumps(err_response, indent=2)
        }

    # Execute
    try:
        exec(compiled, safe_globals, safe_locals)
        body = {
            "id": parsed_event["id"],
            "header": {},
            "body": {
                "result": safe_locals['result']
            }
        }
        return {
            "body": json.dumps(body, indent=2)
        }
    except Exception as err:
        err_response = build_err_response(
            parsed_event, sys.exc_info(), err, True)
        return {
            "body": json.dumps(err_response, indent=2)
        }

What I expect to happen:

A user should be able to define a class, the script that causes the error to be raised:


def executeScript(input): 

    class BankAccount(object=None):
        def __init__(self, initial_balance=0):
            self.balance = initial_balance

        def deposit(self, amount):
            self.balance = balance + amount

        def withdraw(self, amount):
            self.balance = balance - amount

        def overdrawn(self):
            return self.balance < 0

    my_account = BankAccount(15)
    my_account.withdraw(50)

    return {
        'balance': my_account.balance,
        'overdrawn': my_account.overdrawn()
    }
"""

What actually happened:


"stack": [
      "  File \"/var/task/sandbox.py\", line 114, in exec_untrusted\n    exec(compiled, safe_globals, safe_locals)\n",
      "  File \"script\", line 25, in <module>\n",
      "  File \"script\", line 1, in executeScript\n",
      "  File \"script\", line 1, in BankAccount\n"
    ]

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

I'm using:

icemac commented 4 years ago

Could you please provide a complete traceback? So it is possible to see where the name 'name' is not defined.

aliasmac commented 4 years ago

Hey @icemac so that is the full stack trace I'm getting back, I'm using traceback.format_exc() to capture it like so:


try:
        exec(compiled, globals, safe_locals)
        body = {
            "id": "the workflow ID from the event input",
            "header": {},
            "body": {
                "result": safe_locals['result']
            }
        }
        return {
            "statusCode": 200,
            "body": json.dumps(body, indent=2)
        }
    except Exception as err:
        print(traceback.print_tb(err.__traceback__))
        err_response = build_err_response(sys.exc_info())
        return {
            "statusCode": 400,
            "body": json.dumps(err_response, indent=2)
        }

And it gives me:


COMPILED <code object <module> at 0x7f544244b240, file "script", line 2>
traceback Traceback (most recent call last):
File "/var/task/sandbox.py", line 139, in exec_untrusted
exec(compiled, globals, safe_locals)
File "script", line 25, in <module>
File "script", line 1, in executeScript
File "script", line 1, in BankAccount
NameError: name '__name__' is not defined

is there a better way to get the stack trace?

icemac commented 4 years ago

Why do you use object=None as argument to your BankAccount class definition. What happens if you use class BankAccount:?

If I use your BankAccout class definition in a plain Python 3.8 I get:

Python 3.8.6 (default, Sep 28 2020, 04:41:02)
[Clang 11.0.3 (clang-1103.0.32.62)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> class BankAccount(object=None):
...     def __init__(self, initial_balance=0):
...         self.balance = initial_balance
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: __init_subclass__() takes no keyword arguments
>>>

So it seems that your Python code is not valid in the first place. The next problem will be that __init__ is not allowed because its name starts with an underscore. You will have to do it this way:


class BankAccount:
    balance = 0
    def deposit(self, amount):
    ...

my_account = BankAccount()
my_account.deposit(15)  # replacement for __init__
my_account.withdraw(50)
aliasmac commented 3 years ago

Ah yeah there were some errors in the code I shared, I was chopping and changing different elements to try get it to work, including testing different class definitions. The problem here is that I'm relatively new to Python so not exactly sure how to go about debugging this.

I've tried the simple class definition suggested i.e. class BankAccount: but no luck.

Even when I just try something simple like:

def executeScript(input):
    class MyClass:
        pass

I still get name '__name__' is not defined". Do your reckon it's something to do with the way I've setup the sandbox?

d-maurer commented 3 years ago

Aliasgar Makda wrote at 2020-11-9 03:42 -0800:

... I still get name '__name__' is not defined". Do your reckon it's something to do with the way I've setup the sandbox?

This might indeed be the case: RestrictedPython only provides the base infrastructure. All things which have to do with "policy" are external to RestrictedPython.

For Zope, the policy is implemented by AccessControl.ZopeGuards. For local class definitions, it makes available a safe global __metaclass__. I suppose that this is used in the restricted compilation of local class definitions.

Honneamise commented 3 years ago

Hello, i have a similar problem regarding undefined name. have created a sandbox and a test case like this ( thanks in advance for any help or some example ) :


from RestrictedPython import compile_restricted,safe_globals
from RestrictedPython.Eval import default_guarded_getiter,default_guarded_getitem
from RestrictedPython.Guards import guarded_iter_unpack_sequence,safer_getattr

safe_globals['__metaclass__'] = type
safe_globals['_getiter_'] = default_guarded_getiter
safe_globals['_getitem_'] = default_guarded_getitem
safe_globals['_iter_unpack_sequence_'] = guarded_iter_unpack_sequence
safe_globals['getattr'] = safer_getattr`

src='''
def a():
    class b:
        pass

        def test(self):
            return 4

    c = b()

    return c.test()
'''

loc = {}

byte_code = compile_restricted(src, '<string>', 'exec')

exec(byte_code, safe_globals, loc)

res = loc['a']()

print(res)

----------
Traceback (most recent call last):
  File "d:/DATA/PROGETTI/2021/PYTHON_TEST_CASE/main.py", line 30, in <module>
    res = loc['a']()
  File "<string>", line 1, in a
  File "<string>", line 1, in b
NameError: name '__name__' is not defined
icemac commented 3 years ago

Thank you for the reminder for this open problem: Classes need a __name__, which they use as the namespace. Even without RestrictedPython this is the case:

$ python3.7
Python 3.7.9 (default, Sep  6 2020, 13:20:25)
Type "help", "copyright", "credits" or "license" for more information.
>>> class A:
...     pass
...
>>> A
<class '__main__.A'>
>>> __name__
'__main__'
>>>

So you have to add a __name__ to your safe_globals like this:

safe_globals['__name__'] = 'my_module'

Running the code of @Honneamise than results in:

4

I updated the documentation to include this fact in https://github.com/zopefoundation/RestrictedPython/commit/ed13a18de6143580e9c010bd88ca24bfc058c875.