A structure whose _pack_ leaves a field under-aligned can be passed to foreign functions incorrectly on x86-64 System V platforms. The callee sees wrong field values, and the arguments after the structure are shifted, which crashes when one of them is a pointer:
// foo.c: cc -shared -fPIC -o foo.so foo.c
#include <stdint.h>
#include <string.h>
#pragma pack(2)
typedef struct __attribute__((ms_struct)) { uint16_t a; uint32_t b; } Foo;
#pragma pack()
void foo_dump(Foo foo, unsigned char *out) { memcpy(out, &foo, sizeof foo); }
(ms_struct matches _layout_ = "ms", which 3.14 requires for _pack_ on non-Windows; for these fields it does not change the layout.)
import ctypes
class Foo(ctypes.Structure):
_layout_ = "ms"
_pack_ = 2
_fields_ = [("a", ctypes.c_uint16), ("b", ctypes.c_uint32)]
lib = ctypes.CDLL("./foo.so")
lib.foo_dump.argtypes = [Foo, ctypes.c_void_p]
buf = (ctypes.c_ubyte * ctypes.sizeof(Foo))()
lib.foo_dump(Foo(), buf) # segmentation fault
b sits at offset 2, so under the x86-64 System V ABI the structure has an unaligned field and is passed in memory, and out goes in %rdi. ctypes describes the structure to libffi by its size, alignment and field types, but not the field offsets, and libffi's classification disagrees with the compiler's: in gdb, libffi has passed the structure's bytes in %rdi (all zero here) and out in %rsi, and the callee writes through %rdi. With non-zero field values that is a write to an arbitrary address. Returning such a structure by value goes wrong too, through the corresponding mismatch in the return convention (a structure returned in memory is written through a hidden pointer passed in %rdi).
To see how far this goes, I checked every two- and three-field structure made of uint8_t, uint16_t, uint32_t, uint64_t, float and double, with no _pack_ and with _pack_ 1, 2, 4 and 8, both as an argument (the C side returns a hash of the field bytes it received) and as a return value, each case in its own process. A case counts as wrong if the field bytes differ; most of these do not crash:
| platform |
C compiler |
libffi |
cases |
wrong |
| x86-64 Linux, CPython 3.14.7 |
clang 22.1.8 |
3.8.0 |
2520 |
528 |
| x86-64 Linux, CPython 3.14.7 |
GCC 16.2.1 |
3.8.0 |
2520 |
528 |
| arm64 macOS 27.2, CPython 3.14.7 |
Apple clang 1700.6.3.2 |
system |
2520 |
0 |
On x86-64 all 528 wrong cases, and none of the others, are structures of at most 16 bytes with a field under-aligned by _pack_, with both compilers and in both directions. The sweep did not include nested structures, arrays, _align_ or other field types. All the arm64 cases were correct. The crash also reproduces on 3.13 and on main. I have not tested Windows.
Armin Rigo noticed this in 2012, in a comment on gh-60780 ("The same misbehavior occurs if the structures are packed"), but that issue is about bit fields, and nothing since has dealt with packing. The checks added in gh-60780 and gh-60779 (bpo-16576, bpo-16575) covered bit fields and unions only; the union check was disabled after it rejected software that worked, and the bit-field check with it as a precaution. The documentation warns against passing unions and structures with bit fields by value, but does not mention _pack_, and nothing stops the call.
gh-66469 fixed a similar mismatch for arrays inside structures by describing them to libffi more precisely. I don't see the same fix here: libffi's structure description has no field offsets, and describing the under-aligned field as bytes gives the right offsets but still lets libffi pass the structure in registers, where the ABI requires memory (declaring b as c_uint8 * 4 crashes the same way). So at the least the documentation should mention _pack_ next to the existing warning. ctypes might also raise TypeError for these structures on x86-64 System V, since it knows their layout; the sweep suggests a condition (at most 16 bytes, a field under-aligned), but it would need wider coverage (nested structures, arrays, _align_) before a check could be trusted not to reject calls that work. The reproducer, the sweep and its raw results are on a branch of my fork.
A structure whose
_pack_leaves a field under-aligned can be passed to foreign functions incorrectly on x86-64 System V platforms. The callee sees wrong field values, and the arguments after the structure are shifted, which crashes when one of them is a pointer:(
ms_structmatches_layout_ = "ms", which 3.14 requires for_pack_on non-Windows; for these fields it does not change the layout.)bsits at offset 2, so under the x86-64 System V ABI the structure has an unaligned field and is passed in memory, andoutgoes in%rdi. ctypes describes the structure to libffi by its size, alignment and field types, but not the field offsets, and libffi's classification disagrees with the compiler's: in gdb, libffi has passed the structure's bytes in%rdi(all zero here) andoutin%rsi, and the callee writes through%rdi. With non-zero field values that is a write to an arbitrary address. Returning such a structure by value goes wrong too, through the corresponding mismatch in the return convention (a structure returned in memory is written through a hidden pointer passed in%rdi).To see how far this goes, I checked every two- and three-field structure made of
uint8_t,uint16_t,uint32_t,uint64_t,floatanddouble, with no_pack_and with_pack_1, 2, 4 and 8, both as an argument (the C side returns a hash of the field bytes it received) and as a return value, each case in its own process. A case counts as wrong if the field bytes differ; most of these do not crash:On x86-64 all 528 wrong cases, and none of the others, are structures of at most 16 bytes with a field under-aligned by
_pack_, with both compilers and in both directions. The sweep did not include nested structures, arrays,_align_or other field types. All the arm64 cases were correct. The crash also reproduces on 3.13 and on main. I have not tested Windows.Armin Rigo noticed this in 2012, in a comment on gh-60780 ("The same misbehavior occurs if the structures are packed"), but that issue is about bit fields, and nothing since has dealt with packing. The checks added in gh-60780 and gh-60779 (bpo-16576, bpo-16575) covered bit fields and unions only; the union check was disabled after it rejected software that worked, and the bit-field check with it as a precaution. The documentation warns against passing unions and structures with bit fields by value, but does not mention
_pack_, and nothing stops the call.gh-66469 fixed a similar mismatch for arrays inside structures by describing them to libffi more precisely. I don't see the same fix here: libffi's structure description has no field offsets, and describing the under-aligned field as bytes gives the right offsets but still lets libffi pass the structure in registers, where the ABI requires memory (declaring
basc_uint8 * 4crashes the same way). So at the least the documentation should mention_pack_next to the existing warning. ctypes might also raiseTypeErrorfor these structures on x86-64 System V, since it knows their layout; the sweep suggests a condition (at most 16 bytes, a field under-aligned), but it would need wider coverage (nested structures, arrays,_align_) before a check could be trusted not to reject calls that work. The reproducer, the sweep and its raw results are on a branch of my fork.