Skip to content

Null pointer dereference in array_repeat via re-entrant __index__ during array multiplication #142883

@jackfromeast

Description

@jackfromeast

What happened?

A custom __index__ can swap the MRO of an array.array subclass mid multiplication, so array_repeat calls find_array_state_by_type on a type without the array module and receives a NULL module state. The subsequent dereference of state->ArrayType then hits freed memory and triggers a null pointer read.

Proof of Concept:

import array

class Good(array.array):
    pass

class Hide(type):
    def mro(cls):
        return (cls, object)

class Bad(Good, metaclass=Hide):
    pass

arr = Good('b', b'x')

class Count:
    def __index__(self):
        arr.__class__ = Bad     # Drop array.array from the MRO mid-op
        return 2

arr * Count()

Affected Versions:

Details
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 27 2025, 21:34:13) OK 0
Python 3.10.19+ (heads/3.10:014261980b1, Oct 27 2025, 21:19:00) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 27 2025, 21:20:35) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 27 2025, 21:27:07) [Clang 18.1.3 (1ubuntu1)] ASAN 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

Related Code Snippet

Details
#define find_array_state_by_type(tp) \
      (get_array_state(PyType_GetModuleByDef(tp, &arraymodule)))   // requires array.array in the MRO

static array_state *
get_array_state(PyObject *module)
{
    return (array_state *)_PyModule_GetState(module);            // assumes module is non-NULL
}

static PyObject *
array_repeat(PyObject *op, Py_ssize_t n)
{
    arrayobject *a = arrayobject_CAST(op);
    array_state *state = find_array_state_by_type(Py_TYPE(a));

    if (n < 0)
        n = 0;
    const Py_ssize_t array_length = Py_SIZE(a);
    if ((array_length != 0) && (n > PY_SSIZE_T_MAX / array_length)) {
        return PyErr_NoMemory();
    }
    Py_ssize_t size = array_length * n;
    arrayobject* np = (arrayobject *) newarrayobject(state->ArrayType, size, a->ob_descr);
    if (np == NULL)
        return NULL;
    if (size == 0)
        return (PyObject *)np;

    const Py_ssize_t oldbytes = array_length * a->ob_descr->itemsize;
    const Py_ssize_t newbytes = oldbytes * n;
    _PyBytes_Repeat(np->ob_item, newbytes, a->ob_item, oldbytes);

    return (PyObject *)np;
}

Sanitizer Report

Details
=================================================================
==105120==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x77b5659592d8 bp 0x7ffe28c17cf0 sp 0x7ffe28c17cf0 T0)
==105120==The signal is caused by a READ memory access.
==105120==Hint: address points to the zero page.
    #0 0x77b5659592d8 in _Py_TYPE Include/object.h:277
    #1 0x77b5659592d8 in Py_IS_TYPE Include/object.h:302
    #2 0x77b5659592d8 in PyObject_TypeCheck Include/object.h:432
    #3 0x77b565959317 in _PyModule_GetState Include/internal/pycore_moduleobject.h:38
    #4 0x77b56595936b in get_array_state Modules/arraymodule.c:75
    #5 0x77b56595f09d in array_repeat Modules/arraymodule.c:970
    #6 0x584ac280512b in wrap_indexargfunc Objects/typeobject.c:9651
    #7 0x584ac270d606 in wrapperdescr_raw_call Objects/descrobject.c:532
    #8 0x584ac270df48 in wrapperdescr_call Objects/descrobject.c:570
    #9 0x584ac26eec71 in _PyObject_MakeTpCall Objects/call.c:242
    #10 0x584ac2801605 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:167
    #11 0x584ac2801680 in vectorcall_unbound Objects/typeobject.c:3033
    #12 0x584ac281def4 in vectorcall_maybe Objects/typeobject.c:3130
    #13 0x584ac2821819 in slot_nb_multiply Objects/typeobject.c:10397
    #14 0x584ac26b8613 in binary_op1 Objects/abstract.c:966
    #15 0x584ac26bcc64 in PyNumber_Multiply Objects/abstract.c:1170
    #16 0x584ac29638f8 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:62
    #17 0x584ac29b0e54 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #18 0x584ac29b1148 in _PyEval_Vector Python/ceval.c:2001
    #19 0x584ac29b13f8 in PyEval_EvalCode Python/ceval.c:884
    #20 0x584ac2aa8507 in run_eval_code_obj Python/pythonrun.c:1365
    #21 0x584ac2aa8723 in run_mod Python/pythonrun.c:1459
    #22 0x584ac2aa957a in pyrun_file Python/pythonrun.c:1293
    #23 0x584ac2aac220 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #24 0x584ac2aac4f6 in _PyRun_AnyFileObject Python/pythonrun.c:81
    #25 0x584ac2afd74d in pymain_run_file_obj Modules/main.c:410
    #26 0x584ac2afd9b4 in pymain_run_file Modules/main.c:429
    #27 0x584ac2aff1b2 in pymain_run_python Modules/main.c:691
    #28 0x584ac2aff842 in Py_RunMain Modules/main.c:772
    #29 0x584ac2affa2e in pymain_main Modules/main.c:802
    #30 0x584ac2affdb3 in Py_BytesMain Modules/main.c:826
    #31 0x584ac2583645 in main Programs/python.c:15
    #32 0x77b56622a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #33 0x77b56622a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #34 0x584ac2583574 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 Include/object.h:277 in _Py_TYPE
==105120==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