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

SIGSEGV with generators after direct change in gi_frame #125038

Open
efimov-mikhail opened this issue Oct 7, 2024 · 8 comments
Open

SIGSEGV with generators after direct change in gi_frame #125038

efimov-mikhail opened this issue Oct 7, 2024 · 8 comments
Labels
3.13 bugs and security fixes 3.14 new features, bugs and security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-crash A hard crash of the interpreter, possibly with a core dump

Comments

@efimov-mikhail
Copy link
Contributor

efimov-mikhail commented Oct 7, 2024

Crash report

What happened?

There is a segmentation fault with simple code snippet:

g = (x for x in range(10))
g.gi_frame.f_locals['.0'] = range(20)
list(g)
print("No segfault")

There is a SIGSEGV on my linux machine (Debian GNU/Linux 10) with both main branch Python and 3.13 version.
Message "No segfault" is printed on Python 3.7.3 (default, Oct 31 2022, 14:04:00).

CPython versions tested on:

3.13, CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

Python 3.14.0a0 (heads/peg_parser_remove_redundant_functions:e1b62e5cf79, Oct 3 2024, 14:38:46) [GCC 8.3.0]

Linked PRs

@efimov-mikhail efimov-mikhail added the type-crash A hard crash of the interpreter, possibly with a core dump label Oct 7, 2024
@AA-Turner AA-Turner added interpreter-core (Objects, Python, Grammar, and Parser dirs) 3.13 bugs and security fixes 3.14 new features, bugs and security fixes labels Oct 7, 2024
@JelleZijlstra
Copy link
Member

Backtrace on MacOS:

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x0000000000000000
    frame #1: 0x00000001001d0c5c python.exe`_PyEval_EvalFrameDefault(tstate=<unavailable>, frame=0x0000000100bcf898, throwflag=0) at generated_cases.c.h:3628:36 [opt]
    frame #2: 0x00000001000a48e0 python.exe`gen_send_ex2 [inlined] _PyEval_EvalFrame(tstate=0x00000001005a8738, frame=<unavailable>, throwflag=0) at pycore_ceval.h:119:16 [opt]
    frame #3: 0x00000001000a48d4 python.exe`gen_send_ex2(gen=0x0000000100bcf850, arg=0x0000000000000000, presult=0x000000016fdfe378, exc=0, closing=<unavailable>) at genobject.c:245:24 [opt]
    frame #4: 0x00000001000a2f28 python.exe`gen_iternext(self=<unavailable>) at genobject.c:612:9 [opt]
    frame #5: 0x00000001000ba0a4 python.exe`list_extend_iter_lock_held(self=0x00000001010ca490, iterable=0x00000001010ca490) at listobject.c:1194:26 [opt]
    frame #6: 0x00000001000b6fb8 python.exe`_list_extend(self=<unavailable>, iterable=<unavailable>) at listobject.c:1367:15 [opt] [artificial]
    frame #7: 0x00000001000bd578 python.exe`list___init___impl(self=0x00000001010ca490, iterable=0x0000000100bcf850) at listobject.c:3391:13 [opt]
    frame #8: 0x00000001000b8ef0 python.exe`list_vectorcall(type=<unavailable>, args=0x00000001009dc690, nargsf=<unavailable>, kwnames=<unavailable>) at listobject.c:3415:13 [opt]
    frame #9: 0x00000001000855f8 python.exe`_PyObject_VectorcallTstate(tstate=0x00000001005a8738, callable=0x0000000100530f30, args=0x00000001009dc690, nargsf=9223372036854775809, kwnames=0x0000000000000000) at pycore_call.h:167:11 [opt]

The crash is in the FOR_ITER instruction; it blindly accesses tp_iternext without verifying that the object being iterated over has this attribute.

The repro case is a bit dubious as it's mutating f_locals, but probably we should indeed support this. The fix would be as simple as inserting a NULL check.

efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
SIGSEGV on generators in case of gi_frame.f_locals is fixed.
This applies to _FOR_ITER bytecode.
Similar checks are added to _FOR_ITER_TIER_TWO and INSTRUMENTED_FOR_ITER bytecode implementations.
@efimov-mikhail
Copy link
Contributor Author

Thank you for such a concrete comment!
It seems like I made fix by myself after it.

efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
SIGSEGV on generators in case of gi_frame.f_locals is fixed.
This applies to _FOR_ITER bytecode.
Similar checks are added to _FOR_ITER_TIER_TWO and INSTRUMENTED_FOR_ITER bytecode implementations.
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 7, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 9, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 9, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 9, 2024
…is fixed

Some iterator checks are added for _FOR_ITER,
_FOR_ITER_TIER_TWO and INSTRUMENTED_FOR_ITER bytecode implementations.
TypeError is raised in case of tp_iternext == NULL.
Tests on generator modifying through gi_frame.f_locals are added, both
to genexpr generators and function generators.
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 9, 2024
@markshannon
Copy link
Member

I think there are two reasonable approaches to fixing this:

  1. Strengthen the compiler/interpreter
  2. Prohibit setting non-identifier keys in the underlying frame

Given that setting an internal variable like .0 is obviously a suspect thing to do, I'd be fine with (2). However, it might break code that stores additional, possibly non-identifier, keys in the locals() dict.

@markshannon
Copy link
Member

I can segfault 3.12 and earlier with a variant of this.

g = (x for x in range(10))
gen_expr_func = types.FunctionType(g.gi_code, {})
list(gen_expr_func(range(20)))
print("No segfault")

The root cause is the same: the generator expression does not check that the value passed to it is an iterator.

@markshannon
Copy link
Member

So it looks like (1) is the correct fix. See #125178 (comment) for a way to do this.

@efimov-mikhail
Copy link
Contributor Author

efimov-mikhail commented Oct 9, 2024

IMHO, additional GET_ITER before FOR_ITER for genexpr is a best way to prevent all such crashes.
I will try to provide code changes by myself.

efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 15, 2024
New tests are moved back to test_generators.py.
Tests on generator creation via FunctionType from gi_code are added.
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 15, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 16, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 16, 2024
efimov-mikhail pushed a commit to efimov-mikhail/cpython that referenced this issue Oct 19, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 19, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 19, 2024
efimov-mikhail added a commit to efimov-mikhail/cpython that referenced this issue Oct 21, 2024
JelleZijlstra pushed a commit to JelleZijlstra/cpython that referenced this issue Oct 22, 2024
…ipulations (pythonGH-125178)

(cherry picked from commit 079875e)

Co-authored-by: Mikhail Efimov <[email protected]>
@JelleZijlstra
Copy link
Member

The initial fix is merged into main, though we'll want to eventually implement a different fix.

I thought it was worth investigating whether a similar crash was possible with async genexps. However, it seems that the GET_ANEXT impression is implemented more robustly and I couldn't find a crash by manipulating the locals of an asyncgen frame.

JelleZijlstra added a commit that referenced this issue Oct 23, 2024
…ions (GH-125178) (#125846)

(cherry picked from commit 079875e)

Co-authored-by: Mikhail Efimov <[email protected]>
@efimov-mikhail
Copy link
Contributor Author

efimov-mikhail commented Oct 23, 2024

However, it seems that the GET_ANEXT impression is implemented more robustly and I couldn't find a crash by manipulating the locals of an asyncgen frame.

Yes, it seems so.
Actually, function _PyEval_GetANext from Python/ceval.c contains NULL check for type->tp_as_async->am_anext method. TypeError will be raised if no such method presents.
This code looks very similar to one of the previous variants of fixing crash in my PR.

Maybe, we can remove NULL check from the GET_ANEXT bytecode implementation.
And place GET_AITER instruction before GET_ANEXT, in similarity with "sync" generators.
This change could possible improve perfomance a little bit.
But I'm not sure that it's worth it.

On the other side, TypeErrors in GET_AITER and GET_ANEXT slightly differs in case of am_anext == NULL.
Maybe we should use the same text in those functions.

cc @JelleZijlstra, @markshannon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3.13 bugs and security fixes 3.14 new features, bugs and security fixes interpreter-core (Objects, Python, Grammar, and Parser dirs) type-crash A hard crash of the interpreter, possibly with a core dump
Projects
None yet
Development

No branches or pull requests

4 participants