Skip to content

Commit 44579b0

Browse files
gh-160708: Use the type size for resized ctypes simple objects (GH-160720)
After ctypes.resize(), a simple object's buffer is larger than its type. bool() and passing the object to a foreign function still used the buffer size, so bool() read past a 16-byte string literal and the call copied the whole buffer into the fixed-size PyCArgObject value, corrupting the heap. Use the type's size instead, like the value getter does since gh-159739. bool() now checks the value's bytes directly, which also covers simple types larger than 16 bytes such as c_longdouble_complex on x86-64. Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
1 parent f25ee0e commit 44579b0

4 files changed

Lines changed: 51 additions & 2 deletions

File tree

‎Lib/test/test_ctypes/test_cfuncs.py‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,16 @@ def test_int(self):
7373
self.assertEqual(self._dll.tf_i(-2147483646), -715827882)
7474
self.assertEqual(self.S(), -2147483646)
7575

76+
def test_int_resized(self):
77+
# gh-160708: only the value of a resized object is passed,
78+
# not the extra space added by resize().
79+
self._dll.tf_i.restype = c_int
80+
self._dll.tf_i.argtypes = (c_int,)
81+
value = c_int(-2147483646)
82+
ctypes.resize(value, 1024)
83+
self.assertEqual(self._dll.tf_i(value), -715827882)
84+
self.assertEqual(self.S(), -2147483646)
85+
7686
def test_int_plus(self):
7787
self._dll.tf_bi.restype = c_int
7888
self._dll.tf_bi.argtypes = (c_byte, c_int)

‎Lib/test/test_ctypes/test_numbers.py‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ def test_resized_scalar_value(self):
7474
ctypes.resize(value, 65536)
7575
self.assertEqual(value.value, 17)
7676

77+
def test_resized_scalar_bool(self):
78+
# gh-160708: bool() only looks at the value, not at the extra
79+
# space added by resize().
80+
for size in (16, 1024):
81+
with self.subTest(size=size):
82+
value = c_int(0)
83+
ctypes.resize(value, size)
84+
ctypes.memset(ctypes.addressof(value) + sizeof(c_int), 1,
85+
size - sizeof(c_int))
86+
self.assertIs(bool(value), False)
87+
value.value = 1
88+
self.assertIs(bool(value), True)
89+
7790
def test_unsigned_values(self):
7891
# the value given to the constructor is available
7992
# as the 'value' attribute
@@ -142,6 +155,16 @@ def test_complex(self):
142155
array = (t * 3)()
143156
self.assertEqual(memoryview(array).format, prefix + format)
144157

158+
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
159+
"requires C11 complex type")
160+
def test_complex_bool(self):
161+
# gh-160708: long double complex can be larger than 16 bytes.
162+
for t in [ctypes.c_double_complex, ctypes.c_float_complex,
163+
ctypes.c_longdouble_complex]:
164+
with self.subTest(t=t):
165+
self.assertIs(bool(t()), False)
166+
self.assertIs(bool(t(1j)), True)
167+
145168
@unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
146169
"requires C11 complex type")
147170
def test_complex_round_trip(self):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix out-of-bounds memory access in :func:`bool` on :mod:`ctypes` simple
2+
objects enlarged by :func:`ctypes.resize`, and when passing such objects to
3+
a foreign function.

‎Modules/_ctypes/_ctypes.c‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2317,7 +2317,7 @@ PyCSimpleType_paramfunc(ctypes_state *st, CDataObject *self)
23172317
parg->tag = fd->code;
23182318
parg->pffi_type = fd->pffi_type;
23192319
parg->obj = Py_NewRef(self);
2320-
memcpy(&parg->value, self->b_ptr, self->b_size);
2320+
memcpy(&parg->value, self->b_ptr, info->size);
23212321
return parg;
23222322
}
23232323

@@ -5560,7 +5560,20 @@ static int
55605560
Simple_bool_impl(CDataObject *self)
55615561
/*[clinic end generated code: output=c4466ece6a99fe8b input=326a1c8b24d8918e]*/
55625562
{
5563-
return memcmp(self->b_ptr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", self->b_size);
5563+
ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
5564+
StgInfo *info;
5565+
if (PyStgInfo_FromObject(st, (PyObject *)self, &info) < 0) {
5566+
return -1;
5567+
}
5568+
assert(info != NULL);
5569+
5570+
assert(info->size <= self->b_size);
5571+
for (Py_ssize_t i = 0; i < info->size; i++) {
5572+
if (self->b_ptr[i]) {
5573+
return 1;
5574+
}
5575+
}
5576+
return 0;
55645577
}
55655578

55665579
/* "%s(%s)" % (self.__class__.__name__, self.value) */

0 commit comments

Comments
 (0)