From 0332605156f25931e4c5a28b39809b9a89eb8adc Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> Date: Sun, 31 Aug 2025 15:26:06 +0700 Subject: [PATCH 1/7] Fix segfaults in _ctypes due to invalid argtypes Signed-off-by: Nguyen Viet Dung <29406816+magnified103@users.noreply.github.com> --- Lib/test/test_ctypes/test_prototypes.py | 17 +++++++++++++ Modules/_ctypes/_ctypes.c | 33 +++++++++++++++++++------ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_ctypes/test_prototypes.py b/Lib/test/test_ctypes/test_prototypes.py index 63ae799ea86ab2..de9fe7169c9ea6 100644 --- a/Lib/test/test_ctypes/test_prototypes.py +++ b/Lib/test/test_ctypes/test_prototypes.py @@ -71,6 +71,23 @@ def test_paramflags(self): self.assertEqual(func(None), None) self.assertEqual(func(input=None), None) + + def test_invalid_paramflags(self): + proto = CFUNCTYPE(c_int, c_char_p) + with self.assertRaises(ValueError): + func = proto(("myprintf", testdll), ((1, "fmt"), (1, "arg1"))) + + def test_invalid_setattr_argtypes(self): + proto = CFUNCTYPE(c_int, c_char_p) + func = proto(("myprintf", testdll), ((1, "fmt"),)) + + self.assertRaisesRegex(TypeError, "_argtypes_ must be a sequence of types", + setattr, func, "argtypes", 123) + self.assertEqual(func.argtypes, (c_char_p,)) + + self.assertRaisesRegex(ValueError, "paramflags must have the same length as argtypes", + setattr, func, "argtypes", (c_char_p, c_int)) + self.assertEqual(func.argtypes, (c_char_p,)) def test_int_pointer_arg(self): diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 4bd3e380b3bc4b..3547c40ed6cb68 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -3647,6 +3647,9 @@ atomic_xgetref(PyObject *obj, PyObject **field) #endif } +static int +_validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags, PyObject *argtypes); + /*[clinic input] @@ -3762,14 +3765,27 @@ _ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value) { PyObject *converters; + PyTypeObject *type = Py_TYPE(self); + ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); + if (value == NULL || value == Py_None) { + /* Verify paramflags again due to constraints with argtypes */ + if (!_validate_paramflags(st, type, self->paramflags, value)) { + return -1; + } + atomic_xsetref(&self->argtypes, NULL); atomic_xsetref(&self->converters, NULL); } else { - ctypes_state *st = get_module_state_by_def(Py_TYPE(Py_TYPE(self))); converters = converters_from_argtypes(st, value); if (!converters) return -1; + + /* Verify paramflags again due to constraints with argtypes */ + if (!_validate_paramflags(st, type, self->paramflags, value)) { + Py_DECREF(converters); + return -1; + } atomic_xsetref(&self->converters, converters); Py_INCREF(value); atomic_xsetref(&self->argtypes, value); @@ -3899,10 +3915,9 @@ _check_outarg_type(ctypes_state *st, PyObject *arg, Py_ssize_t index) /* Returns 1 on success, 0 on error */ static int -_validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags) +_validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags, PyObject *argtypes) { Py_ssize_t i, len; - PyObject *argtypes; StgInfo *info; if (PyStgInfo_FromType(st, (PyObject *)type, &info) < 0) { @@ -3913,9 +3928,11 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags) "abstract class"); return 0; } - argtypes = info->argtypes; + if (argtypes == NULL || argtypes == Py_None) { + argtypes = info->argtypes; + } - if (paramflags == NULL || info->argtypes == NULL) + if (paramflags == NULL || argtypes == NULL) return 1; if (!PyTuple_Check(paramflags)) { @@ -3925,7 +3942,7 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags) } len = PyTuple_GET_SIZE(paramflags); - if (len != PyTuple_GET_SIZE(info->argtypes)) { + if (len != PyTuple_GET_SIZE(argtypes)) { PyErr_SetString(PyExc_ValueError, "paramflags must have the same length as argtypes"); return 0; @@ -4101,7 +4118,7 @@ PyCFuncPtr_FromDll(PyTypeObject *type, PyObject *args, PyObject *kwds) #endif #undef USE_DLERROR ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); - if (!_validate_paramflags(st, type, paramflags)) { + if (!_validate_paramflags(st, type, paramflags, NULL)) { Py_DECREF(ftuple); return NULL; } @@ -4145,7 +4162,7 @@ PyCFuncPtr_FromVtblIndex(PyTypeObject *type, PyObject *args, PyObject *kwds) paramflags = NULL; ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); - if (!_validate_paramflags(st, type, paramflags)) { + if (!_validate_paramflags(st, type, paramflags, NULL)) { return NULL; } self = (PyCFuncPtrObject *)generic_pycdata_new(st, type, args, kwds); From 07c9af5619127bdcd61643c579efb504db164ca3 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung Date: Sun, 31 Aug 2025 15:48:47 +0700 Subject: [PATCH 2/7] Fix lint Signed-off-by: Nguyen Viet Dung --- Lib/test/test_ctypes/test_prototypes.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_ctypes/test_prototypes.py b/Lib/test/test_ctypes/test_prototypes.py index de9fe7169c9ea6..f21ac4885af2d8 100644 --- a/Lib/test/test_ctypes/test_prototypes.py +++ b/Lib/test/test_ctypes/test_prototypes.py @@ -71,12 +71,12 @@ def test_paramflags(self): self.assertEqual(func(None), None) self.assertEqual(func(input=None), None) - + def test_invalid_paramflags(self): proto = CFUNCTYPE(c_int, c_char_p) with self.assertRaises(ValueError): func = proto(("myprintf", testdll), ((1, "fmt"), (1, "arg1"))) - + def test_invalid_setattr_argtypes(self): proto = CFUNCTYPE(c_int, c_char_p) func = proto(("myprintf", testdll), ((1, "fmt"),)) @@ -89,7 +89,6 @@ def test_invalid_setattr_argtypes(self): setattr, func, "argtypes", (c_char_p, c_int)) self.assertEqual(func.argtypes, (c_char_p,)) - def test_int_pointer_arg(self): func = testdll._testfunc_p_p if sizeof(c_longlong) == sizeof(c_void_p): From 46037b1ca8fbe52128e8d74fe386d30bdb7495f9 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sun, 31 Aug 2025 09:06:50 +0000 Subject: [PATCH 3/7] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst diff --git a/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst b/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst new file mode 100644 index 00000000000000..d8fecf95ad209d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst @@ -0,0 +1 @@ +Fix segmentation faults in the :mod:`ctypes` module due to invalid :attr:`argtypes`. Patch by Dung Nguyen. From 45f483ced75f8987de07b41ac1c1ef5adc9ea94f Mon Sep 17 00:00:00 2001 From: Dung Nguyen Date: Sun, 31 Aug 2025 22:09:00 +0700 Subject: [PATCH 4/7] Update Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst Co-authored-by: Yongzi Li <204532581+Yzi-Li@users.noreply.github.com> --- .../next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst b/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst index d8fecf95ad209d..40930b8ccbd9ca 100644 --- a/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst +++ b/Misc/NEWS.d/next/Library/2025-08-31-09-06-49.gh-issue-138008.heOvsU.rst @@ -1 +1 @@ -Fix segmentation faults in the :mod:`ctypes` module due to invalid :attr:`argtypes`. Patch by Dung Nguyen. +Fix segmentation faults in the :mod:`ctypes` module due to invalid :attr:`~ctypes._CFuncPtr.argtypes`. Patch by Dung Nguyen. From 40a1dce607c4303c5fef4466207bde82f4c2caa4 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung Date: Sat, 6 Sep 2025 20:41:25 +0700 Subject: [PATCH 5/7] Do suggested changes, add more tests --- Lib/test/test_ctypes/test_prototypes.py | 18 ++++++++++++++---- Modules/_ctypes/_ctypes.c | 18 ++++++------------ 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/Lib/test/test_ctypes/test_prototypes.py b/Lib/test/test_ctypes/test_prototypes.py index f21ac4885af2d8..a811a50bffb514 100644 --- a/Lib/test/test_ctypes/test_prototypes.py +++ b/Lib/test/test_ctypes/test_prototypes.py @@ -81,14 +81,24 @@ def test_invalid_setattr_argtypes(self): proto = CFUNCTYPE(c_int, c_char_p) func = proto(("myprintf", testdll), ((1, "fmt"),)) - self.assertRaisesRegex(TypeError, "_argtypes_ must be a sequence of types", - setattr, func, "argtypes", 123) + with self.assertRaisesRegex(TypeError, "_argtypes_ must be a sequence of types"): + func.argtypes = 123 self.assertEqual(func.argtypes, (c_char_p,)) - self.assertRaisesRegex(ValueError, "paramflags must have the same length as argtypes", - setattr, func, "argtypes", (c_char_p, c_int)) + with self.assertRaisesRegex(ValueError, "paramflags must have the same length as argtypes"): + func.argtypes = (c_char_p, c_int) self.assertEqual(func.argtypes, (c_char_p,)) + def test_paramflags_outarg(self): + proto = CFUNCTYPE(c_int, c_char_p, c_int) + with self.assertRaisesRegex(TypeError, "must be a pointer type"): + func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) + + proto = CFUNCTYPE(c_int, c_char_p, c_void_p) + func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) + with self.assertRaisesRegex(TypeError, "must be a pointer type"): + func.argtypes = (c_char_p, c_int) + def test_int_pointer_arg(self): func = testdll._testfunc_p_p if sizeof(c_longlong) == sizeof(c_void_p): diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 3547c40ed6cb68..77c186f336f09f 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -3763,21 +3763,14 @@ static int _ctypes_CFuncPtr_argtypes_set_impl(PyCFuncPtrObject *self, PyObject *value) /*[clinic end generated code: output=596a36e2ae89d7d1 input=c4627573e980aa8b]*/ { - PyObject *converters; - - PyTypeObject *type = Py_TYPE(self); - ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); - if (value == NULL || value == Py_None) { - /* Verify paramflags again due to constraints with argtypes */ - if (!_validate_paramflags(st, type, self->paramflags, value)) { - return -1; - } - atomic_xsetref(&self->argtypes, NULL); atomic_xsetref(&self->converters, NULL); } else { - converters = converters_from_argtypes(st, value); + PyTypeObject *type = Py_TYPE(self); + ctypes_state *st = get_module_state_by_def(Py_TYPE(type)); + + PyObject *converters = converters_from_argtypes(st, value); if (!converters) return -1; @@ -3932,8 +3925,9 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags, argtypes = info->argtypes; } - if (paramflags == NULL || argtypes == NULL) + if (paramflags == NULL || argtypes == NULL) { return 1; + } if (!PyTuple_Check(paramflags)) { PyErr_SetString(PyExc_TypeError, From 41f01a63fe43916ec573efa4aaec895cae1739de Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung Date: Sat, 6 Sep 2025 20:43:32 +0700 Subject: [PATCH 6/7] lint fix --- Lib/test/test_ctypes/test_prototypes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_ctypes/test_prototypes.py b/Lib/test/test_ctypes/test_prototypes.py index a811a50bffb514..d976e8da0e2d30 100644 --- a/Lib/test/test_ctypes/test_prototypes.py +++ b/Lib/test/test_ctypes/test_prototypes.py @@ -93,7 +93,7 @@ def test_paramflags_outarg(self): proto = CFUNCTYPE(c_int, c_char_p, c_int) with self.assertRaisesRegex(TypeError, "must be a pointer type"): func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) - + proto = CFUNCTYPE(c_int, c_char_p, c_void_p) func = proto(("myprintf", testdll), ((1, "fmt"), (2, "out"))) with self.assertRaisesRegex(TypeError, "must be a pointer type"): From 71f5edadbbcf8fa423c593db1f2e614e47570687 Mon Sep 17 00:00:00 2001 From: Nguyen Viet Dung Date: Tue, 9 Sep 2025 21:00:40 +0700 Subject: [PATCH 7/7] Remove the Py_None check in _validate_paramflags --- Modules/_ctypes/_ctypes.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 0364401a18dfdb..91fd23d413de21 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -3908,7 +3908,7 @@ _validate_paramflags(ctypes_state *st, PyTypeObject *type, PyObject *paramflags, "abstract class"); return 0; } - if (argtypes == NULL || argtypes == Py_None) { + if (argtypes == NULL) { argtypes = info->argtypes; }