Skip to content

Heap buffer overflow in ctypes array assignment via __class__ swap #143005

@jackfromeast

Description

@jackfromeast

What happened?

Swapping a ctypes array instance to a wider subclass through __class__ bypasses the length check in Array_ass_item_lock_held, so writing to the new tail positions stores past the original allocation and corrupts adjacent heap memory.

Proof of Concept:

from ctypes import c_long

Base = c_long * 3
Wide = c_long * 5

victim = Base(1, 2, 3)
victim.__class__ = Wide # Make the CPython think we have a array whose length is 5.
victim[4] = 42  # OOB write

Affected Versions:

Details
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 27 2025, 21:34:13) ASAN 1
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

Vulnerable Code:

Details
static int
Array_ass_item_lock_held(PyObject *myself, Py_ssize_t index, PyObject *value)
{
    _Py_CRITICAL_SECTION_ASSERT_OBJECT_LOCKED(myself);

    CDataObject *self = _CDataObject_CAST(myself);
    Py_ssize_t size, offset;
    char *ptr;

    if (value == NULL) {
        PyErr_SetString(PyExc_TypeError,
                        "Array does not support item deletion");
        return -1;
    }

    ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
    StgInfo *stginfo;
    if (PyStgInfo_FromObject(st, myself, &stginfo) < 0) {
        return -1;
    }
    assert(stginfo); /* Cannot be NULL for array object instances */

    if (index < 0 || index >= stginfo->length) {
        PyErr_SetString(PyExc_IndexError,
                        "invalid index");
        return -1;
    }
    size = stginfo->size / stginfo->length;
    offset = index * size;
    ptr = self->b_ptr + offset;

    return PyCData_set(st, myself, stginfo->proto, stginfo->setfunc, value,
                       index, size, ptr);
}

Sanitizer Output:

Details
=================================================================
==1353195==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x50300001fa10 at pc 0x7d86b13e09a7 bp 0x7ffdb7d72a30 sp 0x7ffdb7d72a28
WRITE of size 8 at 0x50300001fa10 thread T0
    #0 0x7d86b13e09a6 in i64_set /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/cfield.c:644:1
    #1 0x7d86b13cb6ff in PyCData_set /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/_ctypes.c:3542:14
    #2 0x7d86b13cb6ff in Array_ass_item_lock_held /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/_ctypes.c:5194:12
    #3 0x7d86b13caf37 in Array_ass_subscript_lock_held /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/_ctypes.c:5227:16
    #4 0x7d86b13caf37 in Array_ass_subscript /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/_ctypes.c:5271:14
    #5 0x57424f119d68 in _PyEval_EvalFrameDefault /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/generated_cases.c.h:11245:27
    #6 0x57424f10b3bb in _PyEval_EvalFrame /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Include/internal/pycore_ceval.h:121:16
    #7 0x57424f10b3bb in _PyEval_Vector /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/ceval.c:2005:12
    #8 0x57424f10b3bb in PyEval_EvalCode /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/ceval.c:888:21
    #9 0x57424f262370 in run_eval_code_obj /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1365:12
    #10 0x57424f262370 in run_mod /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1459:19
    #11 0x57424f25c43c in pyrun_file /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1293:15
    #12 0x57424f25c43c in _PyRun_SimpleFileObject /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:521:13
    #13 0x57424f25bb05 in _PyRun_AnyFileObject /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:81:15
    #14 0x57424f2c3fe5 in pymain_run_file_obj /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:410:15
    #15 0x57424f2c3fe5 in pymain_run_file /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:429:15
    #16 0x57424f2c299d in pymain_run_python /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:691:21
    #17 0x57424f2c299d in Py_RunMain /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:772:5
    #18 0x57424f2c3451 in pymain_main /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:802:12
    #19 0x57424f2c35c3 in Py_BytesMain /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:826:12
    #20 0x7d86b1c2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #21 0x7d86b1c2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #22 0x57424ec73104 in _start (/home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/python+0x1c7104) (BuildId: 5de9d2fcbcd44bfc1b0fe256566d49ad35ca1d56)

0x50300001fa10 is located 8 bytes after 24-byte region [0x50300001f9f0,0x50300001fa08)
allocated by thread T0 here:
    #0 0x57424ed0df53 in malloc (/home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/python+0x261f53) (BuildId: 5de9d2fcbcd44bfc1b0fe256566d49ad35ca1d56)
    #1 0x7d86b13bdad2 in PyCData_MallocBuffer /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/_ctypes.c:3271:30
    #2 0x7d86b13bdad2 in generic_pycdata_new /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/_ctypes.c:3589:15
    #3 0x57424ef91a9b in type_call /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Objects/typeobject.c:2449:11
    #4 0x57424ee427b2 in _PyObject_MakeTpCall /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Objects/call.c:242:18
    #5 0x57424f12a837 in _PyEval_EvalFrameDefault /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/generated_cases.c.h:1620:35
    #6 0x57424f10b3bb in _PyEval_EvalFrame /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Include/internal/pycore_ceval.h:121:16
    #7 0x57424f10b3bb in _PyEval_Vector /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/ceval.c:2005:12
    #8 0x57424f10b3bb in PyEval_EvalCode /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/ceval.c:888:21
    #9 0x57424f262370 in run_eval_code_obj /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1365:12
    #10 0x57424f262370 in run_mod /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1459:19
    #11 0x57424f25c43c in pyrun_file /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:1293:15
    #12 0x57424f25c43c in _PyRun_SimpleFileObject /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:521:13
    #13 0x57424f25bb05 in _PyRun_AnyFileObject /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Python/pythonrun.c:81:15
    #14 0x57424f2c3fe5 in pymain_run_file_obj /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:410:15
    #15 0x57424f2c3fe5 in pymain_run_file /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:429:15
    #16 0x57424f2c299d in pymain_run_python /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:691:21
    #17 0x57424f2c299d in Py_RunMain /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:772:5
    #18 0x57424f2c3451 in pymain_main /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:802:12
    #19 0x57424f2c35c3 in Py_BytesMain /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/Modules/main.c:826:12
    #20 0x7d86b1c2a1c9 in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #21 0x7d86b1c2a28a in __libc_start_main csu/../csu/libc-start.c:360:3
    #22 0x57424ec73104 in _start (/home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/python+0x1c7104) (BuildId: 5de9d2fcbcd44bfc1b0fe256566d49ad35ca1d56)

SUMMARY: AddressSanitizer: heap-buffer-overflow /home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/./Modules/_ctypes/cfield.c:644:1 in i64_set
Shadow bytes around the buggy address:
  0x50300001f780: 00 00 00 00 fa fa fd fd fd fd fa fa 00 00 00 00
  0x50300001f800: fa fa fd fd fd fd fa fa fd fd fd fd fa fa 00 00
  0x50300001f880: 00 00 fa fa fd fd fd fd fa fa 00 00 00 00 fa fa
  0x50300001f900: fd fd fd fd fa fa fd fd fd fd fa fa 00 00 00 00
  0x50300001f980: fa fa fd fd fd fd fa fa 00 00 00 00 fa fa 00 00
=>0x50300001fa00: 00 fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50300001fa80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50300001fb00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50300001fb80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50300001fc00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x50300001fc80: 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
==1353195==ABORTING

Metadata

Metadata

Assignees

No one assigned

    Labels

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