Bug report
Bug description:
Summary
When a subclass of ctypes.c_double_complex (or c_float_complex, c_longdouble_complex) is created, the PyCSimpleType_init function in Modules/_ctypes/_ctypes.c allocates only sizeof(ffi_type *) bytes for stginfo->ffi_type_pointer.elements and copies a single pointer. This produces a non-NULL-terminated array, violating libffi's invariant that ffi_type.elements must be a NULL-terminated array of ffi_type *.
With a debug build of libffi, this triggers an assertion failure in ffi_prep_cif.
Reproduction
Build libffi with --enable-debug, then run:
LD_PRELOAD=/path/to/libffi_debug.so ./python -c "
import ctypes
@ctypes.CFUNCTYPE(ctypes.c_double_complex)
def cb():
return 1 + 2j
"
Result:
ASSERTION FAILURE: a->type != FFI_TYPE_COMPLEX || (a->elements != NULL && a->elements[0] != NULL && a->elements[1] == NULL) at ../src/prep_cif.c:150
Aborted (core dumped)
Root Cause
In Modules/_ctypes/_ctypes.c, PyCSimpleType_init:
if (!fmt->pffi_type->elements) {
stginfo->ffi_type_pointer = *fmt->pffi_type;
}
else {
const size_t els_size = sizeof(fmt->pffi_type->elements);
stginfo->ffi_type_pointer.size = fmt->pffi_type->size;
stginfo->ffi_type_pointer.alignment = fmt->pffi_type->alignment;
stginfo->ffi_type_pointer.type = fmt->pffi_type->type;
stginfo->ffi_type_pointer.elements = PyMem_Malloc(els_size);
memcpy(stginfo->ffi_type_pointer.elements,
fmt->pffi_type->elements, els_size);
}
For most simple types, pffi_type->elements is NULL, so the if branch is taken and everything is fine. However, C complex types (ffi_type_complex_double, ffi_type_complex_float, etc.) have a non-NULL elements array:
static ffi_type *ffi_elements_complex_double[2] = { &ffi_type_double, NULL };
ffi_type ffi_type_complex_double = { sizeof(double complex), ..., FFI_TYPE_COMPLEX, ffi_elements_complex_double };
When the else branch runs for these types:
els_size is sizeof(ffi_type *) (8 bytes on 64-bit).
- Only the first pointer (
&ffi_type_double) is copied.
- The second element (which should be
NULL) is left as uninitialized heap memory.
This breaks libffi's invariant. libffi explicitly checks this in debug builds:
Impact
Any code path that passes the broken ffi_type to ffi_prep_cif or ffi_prep_cif_var is affected. This includes:
- Calling a C function with a subclassed complex type as return type or argument type.
- Creating a
CFUNCTYPE callback with a subclassed complex return type.
- Embedding a subclassed complex type inside a
Structure or Union and using it in a function call.
While current release builds of libffi may not crash immediately (because they happen not to read elements[1] for FFI_TYPE_COMPLEX), relying on this is depending on an implementation detail that is explicitly asserted in debug builds and may change in future libffi versions.
Affected Versions
CPython main branch (and likely all versions that support C complex types in _ctypes).
CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs
Bug report
Bug description:
Summary
When a subclass of
ctypes.c_double_complex(orc_float_complex,c_longdouble_complex) is created, thePyCSimpleType_initfunction inModules/_ctypes/_ctypes.callocates onlysizeof(ffi_type *)bytes forstginfo->ffi_type_pointer.elementsand copies a single pointer. This produces a non-NULL-terminated array, violating libffi's invariant thatffi_type.elementsmust be a NULL-terminated array offfi_type *.With a debug build of libffi, this triggers an assertion failure in
ffi_prep_cif.Reproduction
Build libffi with
--enable-debug, then run:Result:
Root Cause
In
Modules/_ctypes/_ctypes.c,PyCSimpleType_init:For most simple types,
pffi_type->elementsisNULL, so theifbranch is taken and everything is fine. However, C complex types (ffi_type_complex_double,ffi_type_complex_float, etc.) have a non-NULLelementsarray:When the
elsebranch runs for these types:els_sizeissizeof(ffi_type *)(8 bytes on 64-bit).&ffi_type_double) is copied.NULL) is left as uninitialized heap memory.This breaks libffi's invariant. libffi explicitly checks this in debug builds:
Impact
Any code path that passes the broken
ffi_typetoffi_prep_ciforffi_prep_cif_varis affected. This includes:CFUNCTYPEcallback with a subclassed complex return type.StructureorUnionand using it in a function call.While current release builds of libffi may not crash immediately (because they happen not to read
elements[1]forFFI_TYPE_COMPLEX), relying on this is depending on an implementation detail that is explicitly asserted in debug builds and may change in future libffi versions.Affected Versions
CPython main branch (and likely all versions that support C complex types in
_ctypes).CPython versions tested on:
CPython main branch
Operating systems tested on:
No response
Linked PRs