Skip to content

JIT: invalid memory read in _PyEval_EvalFrameDefault #141861

@devdanzin

Description

@devdanzin

Crash report

What happened?

ASan will detect a heap buffer overflow from running the code below in a JIT build. In a patched build, the error comes at iteration 175, while on an unpatched build it happens at around 12.000 iterations.

The necessary patch to speed up the overflow:

diff --git a/Include/internal/pycore_backoff.h b/Include/internal/pycore_backoff.h
index 7f60eb49508..fd80dedb27e 100644
--- a/Include/internal/pycore_backoff.h
+++ b/Include/internal/pycore_backoff.h
@@ -124,7 +124,7 @@ trigger_backoff_counter(void)
 // For example, 4095 does not work for the nqueens benchmark on pyperformance
 // as we always end up tracing the loop iteration's
 // exhaustion iteration. Which aborts our current tracer.
-#define JUMP_BACKWARD_INITIAL_VALUE 4000
+#define JUMP_BACKWARD_INITIAL_VALUE 63
 #define JUMP_BACKWARD_INITIAL_BACKOFF 6
 static inline _Py_BackoffCounter
 initial_jump_backoff_counter(void)
@@ -137,7 +137,7 @@ initial_jump_backoff_counter(void)
  * Must be larger than ADAPTIVE_COOLDOWN_VALUE,
  * otherwise when a side exit warms up we may construct
  * a new trace before the Tier 1 code has properly re-specialized. */
-#define SIDE_EXIT_INITIAL_VALUE 4000
+#define SIDE_EXIT_INITIAL_VALUE 63
 #define SIDE_EXIT_INITIAL_BACKOFF 6

 static inline _Py_BackoffCounter

The code that triggers the overflow:

str_v1 = ''
tuple_v2 = (None, None, None, None, None)
small_int_v3 = 4


def f1():
    class StatefulAbs:
        def __abs__(self):
            return 123
 
    evil_abs_obj = StatefulAbs()
    for _ in range(10):
        try:
            abs(evil_abs_obj)
        except Exception:
            pass

    tuple_v2[small_int_v3]
    tuple_v2[small_int_v3]
    tuple_v2[small_int_v3]

    def recursive_wrapper_4569():
        str_v1 > str_v1
        str_v1 <= str_v1
        str_v1 > str_v1
        recursive_wrapper_4569()

    try:
        recursive_wrapper_4569()
    except RecursionError:
        pass

# The number of iterations is this high because an unpatched build may need over 12.000 iterations to crash
for i_f1 in range(19000):
    print(i_f1)
    f1()

ASan output:

1
2
[...]
175
=================================================================
==92971==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6e88fd4a8738 at pc 0x5edcfcfffe9f bp 0x7fffe8f80370 sp 0x7fffe8f80368
READ of size 8 at 0x6e88fd4a8738 thread T0
    #0 0x5edcfcfffe9e in _PyEval_EvalFrameDefault /home/danzin/projects/jit_cpython/Python/generated_cases.c.h:5479:43
    #1 0x5edcfcf9a74a in _PyEval_EvalFrame /home/danzin/projects/jit_cpython/./Include/internal/pycore_ceval.h:121:16
    #2 0x5edcfcf9a74a in _PyEval_Vector /home/danzin/projects/jit_cpython/Python/ceval.c:2159:12
    #3 0x5edcfcf9a164 in PyEval_EvalCode /home/danzin/projects/jit_cpython/Python/ceval.c:995:21
    #4 0x5edcfd30beae in run_eval_code_obj /home/danzin/projects/jit_cpython/Python/pythonrun.c:1372:12
    #5 0x5edcfd30b07b in run_mod /home/danzin/projects/jit_cpython/Python/pythonrun.c:1475:19
    #6 0x5edcfd30567c in pyrun_file /home/danzin/projects/jit_cpython/Python/pythonrun.c:1300:15
    #7 0x5edcfd303212 in _PyRun_SimpleFileObject /home/danzin/projects/jit_cpython/Python/pythonrun.c:521:13
    #8 0x5edcfd30258d in _PyRun_AnyFileObject /home/danzin/projects/jit_cpython/Python/pythonrun.c:81:15
    #9 0x5edcfd37d5ba in pymain_run_file_obj /home/danzin/projects/jit_cpython/Modules/main.c:410:15
    #10 0x5edcfd37d5ba in pymain_run_file /home/danzin/projects/jit_cpython/Modules/main.c:429:15
    #11 0x5edcfd37b683 in pymain_run_python /home/danzin/projects/jit_cpython/Modules/main.c:691:21
    #12 0x5edcfd37b683 in Py_RunMain /home/danzin/projects/jit_cpython/Modules/main.c:772:5
    #13 0x5edcfd37c586 in pymain_main /home/danzin/projects/jit_cpython/Modules/main.c:802:12
    #14 0x5edcfd37c6f7 in Py_BytesMain /home/danzin/projects/jit_cpython/Modules/main.c:826:12
    #15 0x7228fe02a574 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #16 0x7228fe02a627 in __libc_start_main csu/../csu/libc-start.c:360:3
    #17 0x5edcfc95f4f4 in _start (/home/danzin/projects/jit_cpython/python+0x2bb4f4) (BuildId: 80666b118b34989342403a590b39bd333cc40fa4)

0x6e88fd4a8738 is located 1016 bytes after 64-byte region [0x6e88fd4a8300,0x6e88fd4a8340)
allocated by thread T0 here:
    #0 0x5edcfca04a98 in malloc (/home/danzin/projects/jit_cpython/python+0x360a98) (BuildId: 80666b118b34989342403a590b39bd333cc40fa4)
    #1 0x5edcfcd72f22 in _PyMem_DebugRawAlloc /home/danzin/projects/jit_cpython/Objects/obmalloc.c:2887:24
    #2 0x5edcfcd72f22 in _PyMem_DebugRawRealloc /home/danzin/projects/jit_cpython/Objects/obmalloc.c:2963:16
    #3 0x5edcfd275126 in get_index_for_executor /home/danzin/projects/jit_cpython/Python/optimizer.c:77:33
    #4 0x5edcfd275126 in _PyOptimizer_Optimize /home/danzin/projects/jit_cpython/Python/optimizer.c:171:21
    #5 0x5edcfd011410 in stop_tracing_and_jit /home/danzin/projects/jit_cpython/Python/ceval.c:1108:15
    #6 0x5edcfcfbe5c2 in _PyEval_EvalFrameDefault /home/danzin/projects/jit_cpython/Python/generated_cases.c.h:11712:27
    #7 0x5edcfcf9a74a in _PyEval_EvalFrame /home/danzin/projects/jit_cpython/./Include/internal/pycore_ceval.h:121:16
    #8 0x5edcfcf9a74a in _PyEval_Vector /home/danzin/projects/jit_cpython/Python/ceval.c:2159:12
    #9 0x5edcfcf9a164 in PyEval_EvalCode /home/danzin/projects/jit_cpython/Python/ceval.c:995:21
    #10 0x5edcfd30beae in run_eval_code_obj /home/danzin/projects/jit_cpython/Python/pythonrun.c:1372:12
    #11 0x5edcfd30b07b in run_mod /home/danzin/projects/jit_cpython/Python/pythonrun.c:1475:19
    #12 0x5edcfd30567c in pyrun_file /home/danzin/projects/jit_cpython/Python/pythonrun.c:1300:15
    #13 0x5edcfd303212 in _PyRun_SimpleFileObject /home/danzin/projects/jit_cpython/Python/pythonrun.c:521:13
    #14 0x5edcfd30258d in _PyRun_AnyFileObject /home/danzin/projects/jit_cpython/Python/pythonrun.c:81:15
    #15 0x5edcfd37d5ba in pymain_run_file_obj /home/danzin/projects/jit_cpython/Modules/main.c:410:15
    #16 0x5edcfd37d5ba in pymain_run_file /home/danzin/projects/jit_cpython/Modules/main.c:429:15
    #17 0x5edcfd37b683 in pymain_run_python /home/danzin/projects/jit_cpython/Modules/main.c:691:21
    #18 0x5edcfd37b683 in Py_RunMain /home/danzin/projects/jit_cpython/Modules/main.c:772:5
    #19 0x5edcfd37c586 in pymain_main /home/danzin/projects/jit_cpython/Modules/main.c:802:12
    #20 0x5edcfd37c6f7 in Py_BytesMain /home/danzin/projects/jit_cpython/Modules/main.c:826:12
    #21 0x7228fe02a574 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #22 0x7228fe02a627 in __libc_start_main csu/../csu/libc-start.c:360:3
    #23 0x5edcfc95f4f4 in _start (/home/danzin/projects/jit_cpython/python+0x2bb4f4) (BuildId: 80666b118b34989342403a590b39bd333cc40fa4)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/danzin/projects/jit_cpython/Python/generated_cases.c.h:5479:43 in _PyEval_EvalFrameDefault
Shadow bytes around the buggy address:
  0x6e88fd4a8480: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8500: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8580: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8600: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x6e88fd4a8700: fa fa fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa
  0x6e88fd4a8780: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8880: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8900: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x6e88fd4a8980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==92971==ABORTING

Output from running with PYTHON_LLTRACE=4:
139_overflow_lltrace.txt

Output from running with PYTHON_DEBUG=4:
139_overflow_opt_debug.txt

Interestingly, the original fuzzing code would trigger the overflow at iteration 127 instead of 175, something I cut out when reducing increased the necessary number of iterations.

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.0a2+ (heads/main-dirty:dc9d2eea587, Nov 22 2025, 19:28:52) [Clang 21.1.2 (2ubuntu6)]

Linked PRs

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