python / python/cpython

Heap buffer overflow in ctypes array assignment via `__class__` swap

Open
#143,005 1 comment 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

extension-modules topic-ctypes type-crash
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

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:

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:

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:

=================================================================
==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
Linked PRs
  • gh-143907

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with Array_ass_item_lock_held in Modules/_ctypes/_ctypes.c and reproduce the provided class swap under ASAN. Done means the ctypes array assignment no longer writes beyond the original allocation; check linked PR gh-143907 before beginning.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, python
Domain
security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.