Skip to content

Use-after-free in _Py_typing_type_repr via re-entrant __origin__ lookup during GenericAlias repr #143635

@jackfromeast

Description

@jackfromeast

What happened?

types.GenericAlias.__repr__ walks its type arguments by borrowed pointers. A crafted argument object implements __getattr__ to clear the shared list when _Py_typing_type_repr probes __origin__/__args__, dropping the last reference to the same object mid-repr. The second attribute probe then dereferences the freed object inside PyObject_HasAttrWithError, producing a heap use-after-free during repr().

Proof of Concept:

import types


class Zap:
    __slots__ = ("container",)

    def __init__(self, container):
        self.container = container

    def __getattr__(self, name):
        if name == "__origin__":
            self.container.clear()
            return None
        if name == "__args__":
            return ()
        raise AttributeError


params = []
params.append(Zap(params))
alias = types.GenericAlias(list, (params,))
repr(alias)

Vulnerable Code Snippet:

Click to expand
/* Buggy Re-entrant Path */
static int
ga_repr_items_list(PyUnicodeWriter *writer, PyObject *p)
{
    Py_ssize_t len = PyList_GET_SIZE(p);
    ...
    PyObject *item = PyList_GET_ITEM(p, i);  /* crashing pointer derived */
    if (_Py_typing_type_repr(writer, item) < 0) {
        return -1;
    }
}

int
_Py_typing_type_repr(PyUnicodeWriter *writer, PyObject *p)
{
    ...
    if ((rc = PyObject_HasAttrWithError(p, &_Py_ID(__origin__))) > 0 &&  /* Reentrant call site */
        (rc = PyObject_HasAttrWithError(p, &_Py_ID(__args__))) > 0) {
        /* user __getattr__ can clear the list and free 'p' */
        goto use_repr;
    }
    ...
}

int
PyObject_GetOptionalAttr(PyObject *v, PyObject *name, PyObject **result)
{
    PyTypeObject *tp = Py_TYPE(v);  /* Crash site */
    ...
}

/* Clobbering Path */
static void
list_clear_impl(PyListObject *a, bool is_resize)
{
    PyObject **items = a->ob_item;
    /* ... */

    Py_ssize_t i = Py_SIZE(a);
    Py_SET_SIZE(a, 0);
    FT_ATOMIC_STORE_PTR_RELEASE(a->ob_item, NULL);
    a->allocated = 0;
    while (--i >= 0) {
        Py_XDECREF(items[i]);  /* state mutate site */
    }
    free_list_items(items, use_qsbr);
}

Sanitizer Output:

Click to expand
=================================================================
==440644==ERROR: AddressSanitizer: heap-use-after-free on address 0x504000054aa8 at pc 0x58f73fa51496 bp 0x7fff368835e0 sp 0x7fff368835d0
READ of size 8 at 0x504000054aa8 thread T0
    #0 0x58f73fa51495 in _Py_TYPE Include/object.h:277
    #1 0x58f73fa51495 in PyObject_GetOptionalAttr Objects/object.c:1337
    #2 0x58f73fa51495 in PyObject_HasAttrWithError Objects/object.c:1431
    #3 0x58f73fb1d83e in _Py_typing_type_repr Objects/typevarobject.c:280
    #4 0x58f73f97890b in ga_repr_items_list Objects/genericaliasobject.c:72
    #5 0x58f73f97890b in ga_repr Objects/genericaliasobject.c:118
    #6 0x58f73fa4d04e in PyObject_Repr Objects/object.c:779
    #7 0x58f73f92f3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #8 0x58f73f92f3e7 in PyObject_Vectorcall Objects/call.c:327
    #9 0x58f73f7e35a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x58f73fcadad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x58f73fcadad6 in _PyEval_Vector Python/ceval.c:2001
    #12 0x58f73fcadad6 in PyEval_EvalCode Python/ceval.c:884
    #13 0x58f73fdf316e in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x58f73fdf316e in run_mod Python/pythonrun.c:1459
    #15 0x58f73fdf7e17 in pyrun_file Python/pythonrun.c:1293
    #16 0x58f73fdf7e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x58f73fdf893c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x58f73fe6be3c in pymain_run_file_obj Modules/main.c:410
    #19 0x58f73fe6be3c in pymain_run_file Modules/main.c:429
    #20 0x58f73fe6be3c in pymain_run_python Modules/main.c:691
    #21 0x58f73fe6d71e in Py_RunMain Modules/main.c:772
    #22 0x58f73fe6d71e in pymain_main Modules/main.c:802
    #23 0x58f73fe6d71e in Py_BytesMain Modules/main.c:826
    #24 0x7adc66e2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #25 0x7adc66e2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #26 0x58f73f807634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

0x504000054aa8 is located 24 bytes inside of 40-byte region [0x504000054a90,0x504000054ab8)
freed by thread T0 here:
    #0 0x7adc672fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x58f73fac08f3 in subtype_dealloc Objects/typeobject.c:2852
    #2 0x58f73fa4c1d8 in _Py_Dealloc Objects/object.c:3200
    #3 0x58f73fd37a49 in Py_DECREF_MORTAL Include/internal/pycore_object.h:482
    #4 0x58f73fd37a49 in PyStackRef_XCLOSE Include/internal/pycore_stackref.h:736
    #5 0x58f73fd37a49 in _PyFrame_ClearLocals Python/frame.c:101
    #6 0x58f73fd37a49 in _PyFrame_ClearExceptCode Python/frame.c:126
    #7 0x58f73fca3052 in clear_thread_frame Python/ceval.c:1826
    #8 0x58f73fca3052 in _PyEval_FrameClearAndPop Python/ceval.c:1850
    #9 0x58f73f7e7f4c in _PyEval_EvalFrameDefault Python/generated_cases.c.h:10403
    #10 0x58f73fcae2a5 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x58f73fcae2a5 in _PyEval_Vector Python/ceval.c:2001
    #12 0x58f73f92f3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #13 0x58f73f92f3e7 in PyObject_Vectorcall Objects/call.c:327
    #14 0x58f73faef275 in call_attribute Objects/typeobject.c:10627
    #15 0x58f73faef275 in call_attribute Objects/typeobject.c:10621
    #16 0x58f73faef275 in _Py_slot_tp_getattr_hook Objects/typeobject.c:10688
    #17 0x58f73fa51218 in PyObject_GetOptionalAttr Objects/object.c:1377
    #18 0x58f73fa51218 in PyObject_HasAttrWithError Objects/object.c:1431
    #19 0x58f73fb1d6f6 in _Py_typing_type_repr Objects/typevarobject.c:279
    #20 0x58f73f97890b in ga_repr_items_list Objects/genericaliasobject.c:72
    #21 0x58f73f97890b in ga_repr Objects/genericaliasobject.c:118
    #22 0x58f73fa4d04e in PyObject_Repr Objects/object.c:779
    #23 0x58f73f92f3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #24 0x58f73f92f3e7 in PyObject_Vectorcall Objects/call.c:327
    #25 0x58f73f7e35a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #26 0x58f73fcadad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #27 0x58f73fcadad6 in _PyEval_Vector Python/ceval.c:2001
    #28 0x58f73fcadad6 in PyEval_EvalCode Python/ceval.c:884
    #29 0x58f73fdf316e in run_eval_code_obj Python/pythonrun.c:1365
    #30 0x58f73fdf316e in run_mod Python/pythonrun.c:1459
    #31 0x58f73fdf7e17 in pyrun_file Python/pythonrun.c:1293
    #32 0x58f73fdf7e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #33 0x58f73fdf893c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #34 0x58f73fe6be3c in pymain_run_file_obj Modules/main.c:410
    #35 0x58f73fe6be3c in pymain_run_file Modules/main.c:429
    #36 0x58f73fe6be3c in pymain_run_python Modules/main.c:691
    #37 0x58f73fe6d71e in Py_RunMain Modules/main.c:772
    #38 0x58f73fe6d71e in pymain_main Modules/main.c:802
    #39 0x58f73fe6d71e in Py_BytesMain Modules/main.c:826
    #40 0x7adc66e2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #41 0x7adc66e2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #42 0x58f73f807634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

previously allocated by thread T0 here:
    #0 0x7adc672fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x58f73fad488e in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
    #2 0x58f73fad488e in _PyType_AllocNoTrack Objects/typeobject.c:2504
    #3 0x58f73fad4af4 in PyType_GenericAlloc Objects/typeobject.c:2535
    #4 0x58f73facc118 in type_call Objects/typeobject.c:2448
    #5 0x58f73f92d9cd in _PyObject_MakeTpCall Objects/call.c:242
    #6 0x58f73f7e35a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #7 0x58f73fcadad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #8 0x58f73fcadad6 in _PyEval_Vector Python/ceval.c:2001
    #9 0x58f73fcadad6 in PyEval_EvalCode Python/ceval.c:884
    #10 0x58f73fdf316e in run_eval_code_obj Python/pythonrun.c:1365
    #11 0x58f73fdf316e in run_mod Python/pythonrun.c:1459
    #12 0x58f73fdf7e17 in pyrun_file Python/pythonrun.c:1293
    #13 0x58f73fdf7e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #14 0x58f73fdf893c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #15 0x58f73fe6be3c in pymain_run_file_obj Modules/main.c:410
    #16 0x58f73fe6be3c in pymain_run_file Modules/main.c:429
    #17 0x58f73fe6be3c in pymain_run_python Modules/main.c:691
    #18 0x58f73fe6d71e in Py_RunMain Modules/main.c:772
    #19 0x58f73fe6d71e in pymain_main Modules/main.c:802
    #20 0x58f73fe6d71e in Py_BytesMain Modules/main.c:826
    #21 0x7adc66e2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #22 0x7adc66e2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #23 0x58f73f807634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)

SUMMARY: AddressSanitizer: heap-use-after-free Include/object.h:277 in _Py_TYPE
Shadow bytes around the buggy address:
  0x504000054800: fa fa fd fd fd fd fd fd fa fa fd fd fd fd fd fd
  0x504000054880: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fd
  0x504000054900: fa fa 00 00 00 00 00 05 fa fa fd fd fd fd fd fa
  0x504000054980: fa fa 00 00 00 00 00 05 fa fa fd fd fd fd fd fa
  0x504000054a00: fa fa fd fd fd fd fd fa fa fa fd fd fd fd fd fa
=>0x504000054a80: fa fa fd fd fd[fd]fd fa fa fa fd fd fd fd fd fd
  0x504000054b00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x504000054b80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x504000054c00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x504000054c80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x504000054d00: 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
==440644==ABORTING

CPython versions tested on:

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)] ASAN 1
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

Operating systems tested on:

Linux

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

Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]

Linked PRs

Metadata

Metadata

Assignees

Labels

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