Repository navigation
ctypes: out-of-bounds read/write in bool() and argument passing after resize() #160708
Description
Crash report
After ctypes.resize(), the buffer of a simple ctypes object is larger than the type itself. Two places in _ctypes.c use the buffer size (b_size) instead of the type size and go out of bounds.
Found with a fuzzer. Tested on main (c436e8d).
1. Reading out of bounds in bool()
import ctypes
x = ctypes.c_bool()
ctypes.resize(x, 190)
bool(x)==94599==ERROR: AddressSanitizer: global-buffer-overflow on address 0x7f54a2fc1b71 at pc 0x563480cae666 bp 0x7ffc5c95adf0 sp 0x7ffc5c95a598
READ of size 190 at 0x7f54a2fc1b71 thread T0
#0 in MemcmpInterceptorCommon(void*, int (*)(void const*, void const*, unsigned long), void const*, void const*, unsigned long) /usr/src/RPM/BUILD/llvm-project-18/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:814:7
#1 in memcmp /usr/src/RPM/BUILD/llvm-project-18/compiler-rt/lib/asan/../sanitizer_common/sanitizer_common_interceptors.inc:845:10
#2 in PyObject_IsTrue Objects/object.c:2163:15
#3 in bool_vectorcall Objects/boolobject.c:60:14
#4 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143:11
...
0x7f54a2fc1b71 is located 0 bytes after global variable '.str.245' defined in '/host/triage/cpython/Modules/_ctypes/_ctypes.c:5525' (0x7f54a2fc1b60) of size 17
SUMMARY: AddressSanitizer: global-buffer-overflow Objects/object.c:2163:15 in PyObject_IsTrue
Simple_bool_impl compares the object with a 16-byte string literal (_ctypes.c:5525) using b_size as the length.
Without ASAN, a debug build aborts with:
Fatal Python error: _Py_CheckFunctionResult: a function returned NULL without setting an exception
Python runtime state: initialized
SystemError: <class 'bool'> returned NULL without setting an exception
2. Writing out of bounds when calling a function
import ctypes
x = ctypes.c_bool()
ctypes.resize(x, 128)
abs_ = ctypes.CDLL(None).abs
abs_.argtypes = (ctypes.c_bool,)
abs_(x)==94604==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x508000021000 at pc 0x55a5906863d4 bp 0x7fff29dbc9b0 sp 0x7fff29dbc170
WRITE of size 128 at 0x508000021000 thread T0
#0 in __asan_memcpy /usr/src/RPM/BUILD/llvm-project-18/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:63:3
#1 in PyCSimpleType_paramfunc Modules/_ctypes/_ctypes.c:2320:5
#2 in ConvParam Modules/_ctypes/callproc.c:728:16
#3 in _ctypes_callproc Modules/_ctypes/callproc.c:1308:19
#4 in PyCFuncPtr_call Modules/_ctypes/_ctypes.c:4698:14
...
0x508000021000 is located 0 bytes after 96-byte region [0x508000020fa0,0x508000021000)
allocated by thread T0 here:
#0 in malloc /usr/src/RPM/BUILD/llvm-project-18/compiler-rt/lib/asan/asan_malloc_linux.cpp:68:3
#1 in _PyObject_MallocWithType Include/internal/pycore_object_alloc.h:46:17
#2 in gc_alloc Python/gc.c:2034:17
#3 in _PyObject_GC_New Python/gc.c:2054:20
#4 in PyCArgObject_new Modules/_ctypes/callproc.c:520:9
SUMMARY: AddressSanitizer: heap-buffer-overflow Modules/_ctypes/_ctypes.c:2320:5 in PyCSimpleType_paramfunc
PyCSimpleType_paramfunc copies b_size bytes into the value union of PyCArgObject. The union is 32 bytes on x86-64 (its largest member is long double G[2]), so the copy goes past the end of the object.
Without ASAN, a debug build aborts with:
Fatal Python error: _PyMem_DebugRawFree: bad trailing pad byte
Python runtime state: initialized
I think both places should use info->size, as gh-159767 did in _ctypes_Simple_value_get_impl. I'm going to open a PR.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Output from running 'python -VV' on the command line:
Python 3.16.0a1+dev (heads/main:c436e8d8359, Oct 26 2026, 12:26:29) [Clang 18.1.8 ]
Activity