Skip to content

Null pointer dereference in II_setitem via re-entrant __index__ during array.extend #142882

@jackfromeast

Description

@jackfromeast

What happened?

During array.extend, _PyNumber_Index can invoke a user __index__ that clears the array, so II_setitem resumes with ap->ob_item already NULL and the next write through it dereferences null.

Proof of Concept:

from array import array

buf = array("I", range(15))

class ReentrantIndex:
    seen = False

    def __index__(self):
        if ReentrantIndex.seen:
            buf.clear()
            return 2
        ReentrantIndex.seen = True
        return 1

buf.extend([ReentrantIndex()])

Affected Versions:

Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 27 2025, 21:34:13) Exception 1
Python 3.10.19+ (heads/3.10:014261980b1, Oct 27 2025, 21:19:00) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 27 2025, 21:20:35) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 27 2025, 21:27:07) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 27 2025, 21:28:49) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.14.0+ (heads/3.14:2e216728038, Oct 27 2025, 21:30:55) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 27 2025, 21:32:37) [Clang 18.1.3 (1ubuntu1)] ASAN 1

Vulnerable Code:

static int
II_setitem(arrayobject *ap, Py_ssize_t i, PyObject *v)
{
    unsigned long x;
    int do_decref = 0; /* if nb_int was called */

    if (!PyLong_Check(v)) {
        // Bug: Trigger the __index__ of the value which clears the array's buffer
        v = _PyNumber_Index(v);
        if (NULL == v) {
            return -1;
        }
        do_decref = 1;
    }
    x = PyLong_AsUnsignedLong(v);
    if (x == (unsigned long)-1 && PyErr_Occurred()) {
        if (do_decref) {
            Py_DECREF(v);
        }
        return -1;
    }
    if (x > UINT_MAX) {
        PyErr_SetString(PyExc_OverflowError,
                        "unsigned int is greater than maximum");
        if (do_decref) {
            Py_DECREF(v);
        }
        return -1;
    }
    if (i >= 0)
        // Crash: ap->ob_item has been set to null
        ((unsigned int *)ap->ob_item)[i] = (unsigned int)x;

    if (do_decref) {
        Py_DECREF(v);
    }
    return 0;
}

Sanitizer Output:

=================================================================
==1084586==ERROR: AddressSanitizer: SEGV on unknown address 0x00000000003c (pc 0x742b7c1e6f14 bp 0x7fffe1ed5ec0 sp 0x7fffe1ed5e90 T0)
==1084586==The signal is caused by a WRITE memory access.
==1084586==Hint: address points to the zero page.
    #0 0x742b7c1e6f14 in II_setitem Modules/arraymodule.c:433
    #1 0x742b7c1e8e29 in ins1 Modules/arraymodule.c:713
    #2 0x742b7c1eb22f in array_iter_extend Modules/arraymodule.c:1061
    #3 0x742b7c1eb3d5 in array_do_extend Modules/arraymodule.c:1080
    #4 0x742b7c1eb67f in array_array_extend_impl Modules/arraymodule.c:1346
    #5 0x742b7c1ed191 in array_array_extend Modules/clinic/arraymodule.c.h:242
    #6 0x56323d0fd3c9 in method_vectorcall_FASTCALL_KEYWORDS_METHOD Objects/descrobject.c:381
    #7 0x56323d0dde7f in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #8 0x56323d0ddf72 in PyObject_Vectorcall Objects/call.c:327
    #9 0x56323d35c056 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #10 0x56323d39fe54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #11 0x56323d3a0148 in _PyEval_Vector Python/ceval.c:2001
    #12 0x56323d3a03f8 in PyEval_EvalCode Python/ceval.c:884
    #13 0x56323d497507 in run_eval_code_obj Python/pythonrun.c:1365
    #14 0x56323d497723 in run_mod Python/pythonrun.c:1459
    #15 0x56323d49857a in pyrun_file Python/pythonrun.c:1293
    #16 0x56323d49b220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #17 0x56323d49b4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #18 0x56323d4ec74d in pymain_run_file_obj Modules/main.c:410
    #19 0x56323d4ec9b4 in pymain_run_file Modules/main.c:429
    #20 0x56323d4ee1b2 in pymain_run_python Modules/main.c:691
    #21 0x56323d4ee842 in Py_RunMain Modules/main.c:772
    #22 0x56323d4eea2e in pymain_main Modules/main.c:802
    #23 0x56323d4eedb3 in Py_BytesMain Modules/main.c:826
    #24 0x56323cf72645 in main Programs/python.c:15
    #25 0x742b7ca2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #26 0x742b7ca2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #27 0x56323cf72574 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x2dd574) (BuildId: 202d5dbb945f6d5f5a66ad50e2688d56affd6ecb)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV Modules/arraymodule.c:433 in II_setitem
==1084586==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

    extension-modulesC modules in the Modules dirtype-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