https://github.com/python/cpython/commit/44579b0da558b5a45b17358dffbc4a2ee947cdf7
commit: 44579b0da558b5a45b17358dffbc4a2ee947cdf7
branch: main
author: Saugat Pandey <[email protected]>
committer: ZeroIntensity <[email protected]>
date: 2026-11-04T14:21:06Z
summary:

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 <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-26-16-17-45.gh-issue-160708.w7z33g.rst
M Lib/test/test_ctypes/test_cfuncs.py
M Lib/test/test_ctypes/test_numbers.py
M Modules/_ctypes/_ctypes.c

diff --git a/Lib/test/test_ctypes/test_cfuncs.py 
b/Lib/test/test_ctypes/test_cfuncs.py
index 937be8eaa95c19d..c87bb7b7a40ba8e 100644
--- a/Lib/test/test_ctypes/test_cfuncs.py
+++ b/Lib/test/test_ctypes/test_cfuncs.py
@@ -73,6 +73,16 @@ def test_int(self):
         self.assertEqual(self._dll.tf_i(-2147483646), -715827882)
         self.assertEqual(self.S(), -2147483646)
 
+    def test_int_resized(self):
+        # gh-160708: only the value of a resized object is passed,
+        # not the extra space added by resize().
+        self._dll.tf_i.restype = c_int
+        self._dll.tf_i.argtypes = (c_int,)
+        value = c_int(-2147483646)
+        ctypes.resize(value, 1024)
+        self.assertEqual(self._dll.tf_i(value), -715827882)
+        self.assertEqual(self.S(), -2147483646)
+
     def test_int_plus(self):
         self._dll.tf_bi.restype = c_int
         self._dll.tf_bi.argtypes = (c_byte, c_int)
diff --git a/Lib/test/test_ctypes/test_numbers.py 
b/Lib/test/test_ctypes/test_numbers.py
index 0e29cc141892e88..876bb00eb3796f7 100644
--- a/Lib/test/test_ctypes/test_numbers.py
+++ b/Lib/test/test_ctypes/test_numbers.py
@@ -74,6 +74,19 @@ def test_resized_scalar_value(self):
         ctypes.resize(value, 65536)
         self.assertEqual(value.value, 17)
 
+    def test_resized_scalar_bool(self):
+        # gh-160708: bool() only looks at the value, not at the extra
+        # space added by resize().
+        for size in (16, 1024):
+            with self.subTest(size=size):
+                value = c_int(0)
+                ctypes.resize(value, size)
+                ctypes.memset(ctypes.addressof(value) + sizeof(c_int), 1,
+                              size - sizeof(c_int))
+                self.assertIs(bool(value), False)
+                value.value = 1
+                self.assertIs(bool(value), True)
+
     def test_unsigned_values(self):
         # the value given to the constructor is available
         # as the 'value' attribute
@@ -142,6 +155,16 @@ def test_complex(self):
             array = (t * 3)()
             self.assertEqual(memoryview(array).format, prefix + format)
 
+    @unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
+                         "requires C11 complex type")
+    def test_complex_bool(self):
+        # gh-160708: long double complex can be larger than 16 bytes.
+        for t in [ctypes.c_double_complex, ctypes.c_float_complex,
+                  ctypes.c_longdouble_complex]:
+            with self.subTest(t=t):
+                self.assertIs(bool(t()), False)
+                self.assertIs(bool(t(1j)), True)
+
     @unittest.skipUnless(hasattr(ctypes, "c_double_complex"),
                          "requires C11 complex type")
     def test_complex_round_trip(self):
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-26-16-17-45.gh-issue-160708.w7z33g.rst 
b/Misc/NEWS.d/next/Library/2026-10-26-16-17-45.gh-issue-160708.w7z33g.rst
new file mode 100644
index 000000000000000..7a88edad93f7c27
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-26-16-17-45.gh-issue-160708.w7z33g.rst
@@ -0,0 +1,3 @@
+Fix out-of-bounds memory access in :func:`bool` on :mod:`ctypes` simple
+objects enlarged by :func:`ctypes.resize`, and when passing such objects to
+a foreign function.
diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c
index 58c6e6fde27eec1..56d39e29f140dd0 100644
--- a/Modules/_ctypes/_ctypes.c
+++ b/Modules/_ctypes/_ctypes.c
@@ -2317,7 +2317,7 @@ PyCSimpleType_paramfunc(ctypes_state *st, CDataObject 
*self)
     parg->tag = fd->code;
     parg->pffi_type = fd->pffi_type;
     parg->obj = Py_NewRef(self);
-    memcpy(&parg->value, self->b_ptr, self->b_size);
+    memcpy(&parg->value, self->b_ptr, info->size);
     return parg;
 }
 
@@ -5560,7 +5560,20 @@ static int
 Simple_bool_impl(CDataObject *self)
 /*[clinic end generated code: output=c4466ece6a99fe8b input=326a1c8b24d8918e]*/
 {
-    return memcmp(self->b_ptr, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 
self->b_size);
+    ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self)));
+    StgInfo *info;
+    if (PyStgInfo_FromObject(st, (PyObject *)self, &info) < 0) {
+        return -1;
+    }
+    assert(info != NULL);
+
+    assert(info->size <= self->b_size);
+    for (Py_ssize_t i = 0; i < info->size; i++) {
+        if (self->b_ptr[i]) {
+            return 1;
+        }
+    }
+    return 0;
 }
 
 /* "%s(%s)" % (self.__class__.__name__, self.value) */

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to