Skip to content

JIT: Segfaults from binary ops in code abusing descriptors #143020

@devdanzin

Description

@devdanzin

Crash report

What happened?

It's possible to segfault a non-debug JIT build by running the code below. Mutating the marked line to the options below changes the segfault:

            index = self.count + len(type_options)  # Use +, segfault
            index = self.count % len(type_options)  # Use %, different segfault
            index = 1 - len(type_options)  # Use a literal int an -, different segfault

Please let me know what a good title would be for this issue.

MRE:

def f1():

    class EvilDescriptor:
        count = 0
        def __get__(self, obj, owner):
            for _ in range(5):
                self.count += 1
            type_options = (42,)
            index = self.count + len(type_options)  # Line to mutate

    class TargetClass:
        chaos_attr = EvilDescriptor()

    target_obj_chaos_9754 = TargetClass()
    for i in range(5000):
            _ = target_obj_chaos_9754.chaos_attr

f1()

Segfault backtrace for +:

Program received signal SIGSEGV, Segmentation fault.
_PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:179
179                     if (!_PyLong_CheckExactAndCompact(value_o)) {

#0  _PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:179
#1  0x00005555557700f6 in _PyEval_EvalFrame (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=0x7ffff7fa7118, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#2  _PyEval_Vector (tstate=0x555555b290f0 <_PyRuntime+342848>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>)
    at Python/ceval.c:2482
#3  0x0000555555645c23 in _PyObject_VectorcallTstate (tstate=0x555555b290f0 <_PyRuntime+342848>, callable=0x7ffff6c26fb0, args=<optimized out>, nargsf=<optimized out>,
    kwnames=<optimized out>) at ./Include/internal/pycore_call.h:136
#4  PyObject_Vectorcall (callable=callable@entry=0x7ffff6c26fb0, args=0x0, args@entry=0x7fffffffcf00, nargsf=130, nargsf@entry=3, kwnames=0x7ffff7fa7198, kwnames@entry=0x0)
    at Objects/call.c:327
#5  0x00005555556d8896 in slot_tp_descr_get (self=0x7ffff6c206e0, obj=0x7ffff6c20830, type=0x555555cb5e20) at Objects/typeobject.c:10873
#6  0x000055555569d2e0 in _PyObject_GenericGetAttrWithDict (obj=0x7ffff6c20830, name=0x7ffff6c38a70, dict=0x0, suppress=0) at Objects/object.c:1896
#7  0x000055555569c9f8 in PyObject_GetAttr (v=0x7ffff6c206e1, name=0x0) at Objects/object.c:1316
#8  0x000055555578e3a1 in _PyTier2Interpreter (current_executor=0x555555c1c010, frame=0x7ffff7fa7080, stack_pointer=0x7ffff7fa7110, tstate=0x555555b290f0 <_PyRuntime+342848>)
    at Python/executor_cases.c.h:7726
#9  0x0000555555777bbb in _PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:5348
#10 0x000055555576fe26 in _PyEval_EvalFrame (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=0x0, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#11 _PyEval_Vector (tstate=0x555555b290f0 <_PyRuntime+342848>, func=0x7ffff6bbb060, locals=0x7ffff6bc5d40, args=0x0, argcount=0, kwnames=0x0) at Python/ceval.c:2482
#12 PyEval_EvalCode (co=co@entry=0x7ffff6c006c0, globals=globals@entry=0x7ffff6bc5d40, locals=locals@entry=0x7ffff6bc5d40) at Python/ceval.c:1008
#13 0x0000555555804b96 in run_eval_code_obj (tstate=0x555555b290f0 <_PyRuntime+342848>, co=0x7ffff6c006c0, globals=0x7ffff6bc5d40, locals=0x7ffff6bc5d40) at Python/pythonrun.c:1366

Segfault backtrace for %:

Program received signal SIGSEGV, Segmentation fault.
binary_op1 (v=0x7ffff6ba5fc0, w=0x0, op_slot=op_slot@entry=24) at Objects/abstract.c:947
947         if (!Py_IS_TYPE(w, Py_TYPE(v)) && Py_TYPE(w)->tp_as_number != NULL) {

#0  binary_op1 (v=0x7ffff6ba5fc0, w=0x0, op_slot=op_slot@entry=24) at Objects/abstract.c:947
#1  0x00005555556296d4 in binary_op (v=0x7ffff6ba5fc0, w=0x555555ab0138 <PyTuple_Type>, op_slot=24, op_name=<optimized out>) at Objects/abstract.c:1005
#2  PyNumber_Remainder (v=0x7ffff6ba5fc0, w=0x555555ab0138 <PyTuple_Type>) at Objects/abstract.c:1189
#3  0x000055555578b7e4 in _PyTier2Interpreter (current_executor=0x555555cb69d0, frame=0x7ffff7fa7118, stack_pointer=<optimized out>, tstate=0x555555b290f0 <_PyRuntime+342848>)
    at Python/executor_cases.c.h:15134
#4  0x0000555555777bbb in _PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:5348
#5  0x00005555557700f6 in _PyEval_EvalFrame (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=0x7ffff7fa7118, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#6  _PyEval_Vector (tstate=0x555555b290f0 <_PyRuntime+342848>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>)
    at Python/ceval.c:2482
#7  0x0000555555645c23 in _PyObject_VectorcallTstate (tstate=0x555555b290f0 <_PyRuntime+342848>, callable=0x7ffff6c26fb0, args=<optimized out>, nargsf=<optimized out>,
    kwnames=<optimized out>) at ./Include/internal/pycore_call.h:136
#8  PyObject_Vectorcall (callable=callable@entry=0x7ffff6c26fb0, args=0x555555ab0138 <PyTuple_Type>, args@entry=0x7fffffffcf00, nargsf=24, nargsf@entry=3, kwnames=0x555555cb6c40,
    kwnames@entry=0x0) at Objects/call.c:327
#9  0x00005555556d8896 in slot_tp_descr_get (self=0x7ffff6c206e0, obj=0x7ffff6c20830, type=0x555555cb5e20) at Objects/typeobject.c:10873
#10 0x000055555569d2e0 in _PyObject_GenericGetAttrWithDict (obj=0x7ffff6c20830, name=0x7ffff6c38a70, dict=0x0, suppress=0) at Objects/object.c:1896
#11 0x000055555569c9f8 in PyObject_GetAttr (v=0x7ffff6ba5fc0, name=0x555555ab0138 <PyTuple_Type>) at Objects/object.c:1316
#12 0x000055555578e3a1 in _PyTier2Interpreter (current_executor=0x555555c1c010, frame=0x7ffff7fa7080, stack_pointer=0x7ffff7fa7110, tstate=0x555555b290f0 <_PyRuntime+342848>)
    at Python/executor_cases.c.h:7726
#13 0x0000555555777bbb in _PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:5348
#14 0x000055555576fe26 in _PyEval_EvalFrame (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=0x555555ab0138 <PyTuple_Type>, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#15 _PyEval_Vector (tstate=0x555555b290f0 <_PyRuntime+342848>, func=0x7ffff6bbb060, locals=0x7ffff6bc5d40, args=0x0, argcount=0, kwnames=0x0) at Python/ceval.c:2482
#16 PyEval_EvalCode (co=co@entry=0x7ffff6c006c0, globals=globals@entry=0x7ffff6bc5d40, locals=locals@entry=0x7ffff6bc5d40) at Python/ceval.c:1008

Segfault backtrace for 1 - len(type_options):

Program received signal SIGSEGV, Segmentation fault.
_PyCompactLong_Subtract (a=<optimized out>, b=0x0) at Objects/longobject.c:3916
3916        stwodigits v = medium_value(a) - medium_value(b);

#0  _PyCompactLong_Subtract (a=<optimized out>, b=0x0) at Objects/longobject.c:3916
#1  0x000055555578c4f6 in _PyTier2Interpreter (current_executor=0x555555c1dc70, frame=0x7ffff7fa7118, stack_pointer=0x7ffff7fa7198, tstate=0x555555b290f0 <_PyRuntime+342848>)
    at Python/executor_cases.c.h:3584
#2  0x0000555555777bbb in _PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:5348
#3  0x00005555557700f6 in _PyEval_EvalFrame (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=0x7ffff7fa7118, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#4  _PyEval_Vector (tstate=0x555555b290f0 <_PyRuntime+342848>, func=<optimized out>, locals=<optimized out>, args=<optimized out>, argcount=<optimized out>, kwnames=<optimized out>)
    at Python/ceval.c:2482
#5  0x0000555555645c23 in _PyObject_VectorcallTstate (tstate=0x555555b290f0 <_PyRuntime+342848>, callable=0x7ffff6c26fb0, args=<optimized out>, nargsf=<optimized out>,
    kwnames=<optimized out>) at ./Include/internal/pycore_call.h:136
#6  PyObject_Vectorcall (callable=callable@entry=0x7ffff6c26fb0, args=0x0, args@entry=0x7fffffffcf00, nargsf=1, nargsf@entry=3, kwnames=0x555555c1de50, kwnames@entry=0x0)
    at Objects/call.c:327
#7  0x00005555556d8896 in slot_tp_descr_get (self=0x7ffff6c206e0, obj=0x7ffff6c20830, type=0x555555cb5e20) at Objects/typeobject.c:10873
#8  0x000055555569d2e0 in _PyObject_GenericGetAttrWithDict (obj=0x7ffff6c20830, name=0x7ffff6c38a70, dict=0x0, suppress=0) at Objects/object.c:1896
#9  0x000055555569c9f8 in PyObject_GetAttr (v=0x590a9474, name=0x0) at Objects/object.c:1316
#10 0x000055555578e3a1 in _PyTier2Interpreter (current_executor=0x555555c1c010, frame=0x7ffff7fa7080, stack_pointer=0x7ffff7fa7110, tstate=0x555555b290f0 <_PyRuntime+342848>)
    at Python/executor_cases.c.h:7726
#11 0x0000555555777bbb in _PyEval_EvalFrameDefault (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=<optimized out>, throwflag=<optimized out>) at Python/generated_cases.c.h:5348
#12 0x000055555576fe26 in _PyEval_EvalFrame (tstate=0x555555b290f0 <_PyRuntime+342848>, frame=0x0, throwflag=0) at ./Include/internal/pycore_ceval.h:119
#13 _PyEval_Vector (tstate=0x555555b290f0 <_PyRuntime+342848>, func=0x7ffff6bbb110, locals=0x7ffff6bc5d40, args=0x0, argcount=0, kwnames=0x0) at Python/ceval.c:2482
#14 PyEval_EvalCode (co=co@entry=0x7ffff6c006c0, globals=globals@entry=0x7ffff6bc5d40, locals=locals@entry=0x7ffff6bc5d40) at Python/ceval.c:1008

Abort backtrace on patched debug build:

Stack underflow (depth = -2) at _SPILL_OR_RELOAD_r02.c:165

Program received signal SIGABRT, Aborted.

#0  __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3  0x00007ffff7c45e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7c28888 in __GI_abort () at ./stdlib/abort.c:77
#5  0x0000555555e70d74 in _Py_assert_within_stack_bounds (frame=<optimized out>, stack_pointer=<optimized out>, filename=<optimized out>, lineno=<optimized out>) at Python/ceval.c:1304
#6  0x00007ffff730422f in ?? ()
#7  0x00007fffffffce40 in ?? ()
#8  0x00007ffff770401f in ?? ()
#9  0x00007d2ff6ffda90 in ?? ()
#10 0x00007e8ff6fe5318 in ?? ()
#11 0x00007d9ff6ffaaa0 in ?? ()
#12 0x0000555556e86b70 in _PyRuntime ()
#13 0x0000000000000000 in ?? ()

Abort backtrace for unpatched debug build:

python: _POP_TOP_NOP_r10.c:165: _Py_CODEUNIT *_JIT_ENTRY(_PyInterpreterFrame *, _PyStackRef *, PyThreadState *, _PyStackRef, _PyStackRef, _PyStackRef): Assertion `PyStackRef_IsNull(value) || (!PyStackRef_RefcountOnObject(value)) || _Py_IsImmortal((PyStackRef_AsPyObjectBorrow(value)))' failed.

Program received signal SIGABRT, Aborted.

#0  __pthread_kill_implementation (threadid=<optimized out>, signo=6, no_tid=0) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (threadid=<optimized out>, signo=6) at ./nptl/pthread_kill.c:89
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) at ./nptl/pthread_kill.c:100
#3  0x00007ffff7445e2e in __GI_raise (sig=sig@entry=6) at ../sysdeps/posix/raise.c:26
#4  0x00007ffff7428888 in __GI_abort () at ./stdlib/abort.c:77
#5  0x00007ffff74287f0 in __assert_fail_base (fmt=<optimized out>, assertion=<optimized out>, file=<optimized out>, line=<optimized out>, function=<optimized out>) at ./assert/assert.c:118
#6  0x00007ffff6b26088 in ?? ()
#7  0x00007d2ff65fcc90 in ?? ()
#8  0x00007ffff7efc01f in ?? ()
#9  0x0000000000000052 in ?? ()
#10 0x00007d2ff65fd031 in ?? ()
#11 0x00005555567bb3b0 in _PyRuntime ()
#12 0x00007d2ff65fcdb0 in ?? ()
#13 0x00007e8ff65e5318 in ?? ()
#14 0x0000555555c6f19b in _PyEval_EvalFrameDefault (tstate=<optimized out>, frame=0x7c5ff6635830, throwflag=<optimized out>) at Python/generated_cases.c.h:5348
#15 0x0000555555c9568a in _PyEval_EvalFrame (tstate=tstate@entry=0x5555567fbad0 <_PyRuntime+358864>, frame=frame@entry=0x7e8ff65e5318, throwflag=throwflag@entry=0)
    at ./Include/internal/pycore_ceval.h:119

Running with PYTHON_LLTRACE=4 PYTHON_OPT_DEBUG=4 results in a 164MB file, so I won't attach it this time.

Found using lafleur.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

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

Python 3.15.0a3+ (heads/main:1391ee664c8, Dec 20 2025, 09:28:36) [Clang 21.1.2 (2ubuntu6)]

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)topic-JITtype-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions