Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SystemError when builtins is not a dict + eval #112716

Closed
JelleZijlstra opened this issue Dec 4, 2023 · 9 comments
Closed

SystemError when builtins is not a dict + eval #112716

JelleZijlstra opened this issue Dec 4, 2023 · 9 comments
Assignees
Labels
3.11 only security fixes 3.12 bugs and security fixes 3.13 bugs and security fixes type-bug An unexpected behavior, bug, or error

Comments

@JelleZijlstra
Copy link
Member

JelleZijlstra commented Dec 4, 2023

Bug report

Bug description:

If __builtins__ is not a dict, you can get a SystemError:

>>> import types
>>> exec("import builtins; builtins.print(3)", {"__builtins__": types.MappingProxyType({})})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    exec("import builtins; builtins.print(3)", {"__builtins__": types.MappingProxyType({})})
  File "<string>", line 1, in <module>
SystemError: Objects/dictobject.c:1761: bad argument to internal function

Originally found this while playing with https://oskaerik.github.io/theevalgame/

CPython versions tested on:

3.11, CPython main branch

Operating systems tested on:

macOS

Linked PRs

@JelleZijlstra JelleZijlstra added the type-bug An unexpected behavior, bug, or error label Dec 4, 2023
@Eclips4 Eclips4 added 3.11 only security fixes 3.12 bugs and security fixes 3.13 bugs and security fixes labels Dec 4, 2023
@JelleZijlstra
Copy link
Member Author

Simpler repro case:

>>> exec("import whatever", {"__builtins__": 1})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    exec("import whatever", {"__builtins__": 1})
  File "<string>", line 1, in <module>
SystemError: Objects/dictobject.c:1761: bad argument to internal function

@JelleZijlstra
Copy link
Member Author

The original issue is because the import_name function in ceval.c blindly assumes that the builtins is a dict while looking up __import__. There are other code paths that make the same assumption:

>>> exec("pickle.dumps([].__iter__())", {"__builtins__": 1, "pickle": __import__("pickle")})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    exec("pickle.dumps([].__iter__())", {"__builtins__": 1, "pickle": __import__("pickle")})
  File "<string>", line 1, in <module>
SystemError: Objects/dictobject.c:1761: bad argument to internal function

(Pickling a list iterator involves looking up the iter builtin.)

However, other code (e.g., the implementation of _LOAD_GLOBAL in bytecodes.c) does explicitly support non-dict builtins, so the fact that these code paths don't should be considered a bug.

@serhiy-storchaka
Copy link
Member

There are tests (test_exec_globals_dict_subclass, test_exec_globals_error_on_get, test_exec_globals_frozen in test_builtin) that explicitly test non-dict __builtins__. Support of non-dicts was added in bpo-14385/#58593. @vstinner, is this feature used?

@vstinner
Copy link
Member

vstinner commented Dec 4, 2023

@vstinner, is this feature used?

I don't know. But for now, I would prefer to fix bugs. If we decide to remove the feature, I suppose that it should be deprecated first?

@JelleZijlstra
Copy link
Member Author

Worth noting that the use case that brought me here is real (if a little silly): the game wants to enforce that you don't use any builtins, and it enforces that by setting __builtins__ to a proxy that records access (and delegates lookups to the underlying real builtins). That seems like a reasonable thing to do, so I think we should fix rather than deprecate the feature.

@oskaerik
Copy link

oskaerik commented Dec 5, 2023

Worth noting that the use case that brought me here is real (if a little silly): the game wants to enforce that you don't use any builtins, and it enforces that by setting __builtins__ to a proxy that records access (and delegates lookups to the underlying real builtins). That seems like a reasonable thing to do, so I think we should fix rather than deprecate the feature.

Just to expand on this: My first implementation of the proxy inherited from dict (but I changed that so I didn't have to worry about other methods than __getitem__, e.g. pop). But I wouldn't have been surprised if there was a strict requirement to inherit from dict. 😊

@JelleZijlstra
Copy link
Member Author

Note that support for dict subclasses is also somewhat broken, as the code path used by import (and a few other places) bypasses the subclass's __getitem__:

>>> class fun(dict):
...     def __getitem__(self, key):
...             raise TypeError("no way")
... 
>>> import builtins
>>> b = fun(builtins.__dict__)
>>> exec("import a", {"__builtins__": b})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    exec("import a", {"__builtins__": b})
  File "<string>", line 1, in <module>
ModuleNotFoundError: No module named 'a'
>>> exec("__import__('a')", {"__builtins__": b})
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    exec("__import__('a')", {"__builtins__": b})
  File "<string>", line 1, in <module>
  File "<stdin>", line 3, in __getitem__
    raise TypeError("no way")
TypeError: no way

@serhiy-storchaka serhiy-storchaka self-assigned this Dec 5, 2023
@serhiy-storchaka
Copy link
Member

It's funny that this feature was originally added when trying to build a sandbox, and now it's used in a game that requires you to escape from a sandbox.

@JelleZijlstra
Copy link
Member Author

The best features are the ones that turn out to be useful in unexpected places!

serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this issue Dec 5, 2023
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this issue Dec 5, 2023
serhiy-storchaka added a commit that referenced this issue Dec 14, 2023
It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this issue Dec 14, 2023
…ct (pythonGH-112770)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.
(cherry picked from commit 1161c14)

Co-authored-by: Serhiy Storchaka <[email protected]>
serhiy-storchaka added a commit to serhiy-storchaka/cpython that referenced this issue Dec 14, 2023
…ct (pythonGH-112770)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.
(cherry picked from commit 1161c14)

Co-authored-by: Serhiy Storchaka <[email protected]>
serhiy-storchaka added a commit that referenced this issue Dec 14, 2023
…-112770) (GH-113103)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.

(cherry picked from commit 1161c14)
serhiy-storchaka added a commit that referenced this issue Dec 14, 2023
…-112770) (GH-113105)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.

(cherry picked from commit 1161c14)
corona10 pushed a commit to corona10/cpython that referenced this issue Dec 15, 2023
…honGH-112770)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.
aisk pushed a commit to aisk/cpython that referenced this issue Feb 11, 2024
…honGH-112770)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.
Glyphack pushed a commit to Glyphack/cpython that referenced this issue Sep 2, 2024
…honGH-112770)

It was raised in two cases:
* in the import statement when looking up __import__
* in pickling some builtin type when looking up built-ins iter, getattr, etc.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.11 only security fixes 3.12 bugs and security fixes 3.13 bugs and security fixes type-bug An unexpected behavior, bug, or error
Projects
None yet
Development

No branches or pull requests

5 participants