diff --git a/Lib/test/test_ctypes/test_pointers.py b/Lib/test/test_ctypes/test_pointers.py index 78480af34474a54..eb3a5fdaa188c1e 100644 --- a/Lib/test/test_ctypes/test_pointers.py +++ b/Lib/test/test_ctypes/test_pointers.py @@ -98,6 +98,19 @@ def test_change_pointers(self): x.value = -99 self.assertEqual(res.contents.value, -99) + def test_contents_rejects_spoofed_class(self): + # gh-161390: __class__ must not make a non-ctypes object look valid. + class Forge: + __slots__ = ("a", "b", "c", "d") + __class__ = c_int + + def __init__(self): + self.a = c_int(7) + self.b = self.c = self.d = None + + with self.assertRaises(TypeError): + pointer(c_int(1)).contents = Forge() + def test_callbacks_with_pointers(self): # a function type receiving a pointer PROTOTYPE = CFUNCTYPE(c_int, POINTER(c_int)) diff --git a/Misc/NEWS.d/next/Library/2026-11-05-15-20-00.gh-issue-161390.p8Kq2m.rst b/Misc/NEWS.d/next/Library/2026-11-05-15-20-00.gh-issue-161390.p8Kq2m.rst new file mode 100644 index 000000000000000..d1f9c18253a5755 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-05-15-20-00.gh-issue-161390.p8Kq2m.rst @@ -0,0 +1,4 @@ +Assigning to the ``contents`` attribute of a :mod:`ctypes` pointer now +raises :exc:`TypeError` when the value is not a ctypes object. A value +whose ``__class__`` claimed to be a ctypes type could previously crash +the interpreter. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 56d39e29f140dd0..ce53a7a70ad7522 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -5785,16 +5785,12 @@ Pointer_set_contents_lock_held(PyObject *op, PyObject *value, void *closure) assert(stginfo); /* Cannot be NULL for pointer instances */ assert(stginfo->proto); if (!CDataObject_Check(st, value)) { - int res = PyObject_IsInstance(value, stginfo->proto); - if (res == -1) - return -1; - if (!res) { - PyErr_Format(PyExc_TypeError, - "expected %s instead of %s", - ((PyTypeObject *)(stginfo->proto))->tp_name, - Py_TYPE(value)->tp_name); - return -1; - } + /* PyObject_IsInstance() follows a spoofed __class__ (gh-161390). */ + PyErr_Format(PyExc_TypeError, + "expected %s instead of %s", + ((PyTypeObject *)(stginfo->proto))->tp_name, + Py_TYPE(value)->tp_name); + return -1; } dst = (CDataObject *)value;