]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-46323: _ctypes.CFuncPtr fails if _argtypes_ is too long (GH-31188)
authorVictor Stinner <vstinner@python.org>
Mon, 7 Feb 2022 13:53:15 +0000 (14:53 +0100)
committerGitHub <noreply@github.com>
Mon, 7 Feb 2022 13:53:15 +0000 (14:53 +0100)
ctypes.CFUNCTYPE() and ctypes.WINFUNCTYPE() now fail to create the
type if its "_argtypes_" member contains too many arguments.
Previously, the error was only raised when calling a function.

Change also how CFUNCTYPE() and WINFUNCTYPE() handle KeyError to
prevent creating a chain of exceptions if ctypes.CFuncPtr raises an
error.

Lib/ctypes/__init__.py
Lib/ctypes/test/test_callbacks.py
Misc/NEWS.d/next/Library/2022-02-07-13-27-59.bpo-46323.7UENAj.rst [new file with mode: 0644]
Modules/_ctypes/_ctypes.c
Modules/_ctypes/callproc.c
Modules/_ctypes/ctypes.h

index b08629e8df4dfd0a5746a4431915d6d22b288be6..ab4d31b0acb00286ea64fd133e9eacd9d0a18a14 100644 (file)
@@ -92,15 +92,18 @@ def CFUNCTYPE(restype, *argtypes, **kw):
         flags |= _FUNCFLAG_USE_LASTERROR
     if kw:
         raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
+
     try:
         return _c_functype_cache[(restype, argtypes, flags)]
     except KeyError:
-        class CFunctionType(_CFuncPtr):
-            _argtypes_ = argtypes
-            _restype_ = restype
-            _flags_ = flags
-        _c_functype_cache[(restype, argtypes, flags)] = CFunctionType
-        return CFunctionType
+        pass
+
+    class CFunctionType(_CFuncPtr):
+        _argtypes_ = argtypes
+        _restype_ = restype
+        _flags_ = flags
+    _c_functype_cache[(restype, argtypes, flags)] = CFunctionType
+    return CFunctionType
 
 if _os.name == "nt":
     from _ctypes import LoadLibrary as _dlopen
@@ -116,15 +119,18 @@ if _os.name == "nt":
             flags |= _FUNCFLAG_USE_LASTERROR
         if kw:
             raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
+
         try:
             return _win_functype_cache[(restype, argtypes, flags)]
         except KeyError:
-            class WinFunctionType(_CFuncPtr):
-                _argtypes_ = argtypes
-                _restype_ = restype
-                _flags_ = flags
-            _win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
-            return WinFunctionType
+            pass
+
+        class WinFunctionType(_CFuncPtr):
+            _argtypes_ = argtypes
+            _restype_ = restype
+            _flags_ = flags
+        _win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
+        return WinFunctionType
     if WINFUNCTYPE.__doc__:
         WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")
 
index d8e9c5a760e2c24b7c9a0bc97f9a93571fd55221..5561ffefe12f7522ca0141eb191018b088cfc9ed 100644 (file)
@@ -294,15 +294,22 @@ class SampleCallbacksTestCase(unittest.TestCase):
             return len(args)
 
         CTYPES_MAX_ARGCOUNT = 1024
+
+        # valid call with nargs <= CTYPES_MAX_ARGCOUNT
         proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT)
         cb = proto(func)
         args1 = (1,) * CTYPES_MAX_ARGCOUNT
         self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT)
 
+        # invalid call with nargs > CTYPES_MAX_ARGCOUNT
         args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1)
         with self.assertRaises(ArgumentError):
             cb(*args2)
 
+        # error when creating the type with too many arguments
+        with self.assertRaises(ArgumentError):
+            CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1))
+
     def test_convert_result_error(self):
         def func():
             return ("tuple",)
diff --git a/Misc/NEWS.d/next/Library/2022-02-07-13-27-59.bpo-46323.7UENAj.rst b/Misc/NEWS.d/next/Library/2022-02-07-13-27-59.bpo-46323.7UENAj.rst
new file mode 100644 (file)
index 0000000..e144450
--- /dev/null
@@ -0,0 +1,3 @@
+``ctypes.CFUNCTYPE()`` and ``ctypes.WINFUNCTYPE()`` now fail to create the type
+if its ``_argtypes_`` member contains too many arguments. Previously, the error
+was only raised when calling a function. Patch by Victor Stinner.
index 96078c7726d597a3d6e421d6d3ce2e9af125276d..da9dd096c73d89bdcd2d1db8a4212b38d56016ec 100644 (file)
@@ -2382,7 +2382,6 @@ converters_from_argtypes(PyObject *ob)
     _Py_IDENTIFIER(from_param);
     PyObject *converters;
     Py_ssize_t i;
-    Py_ssize_t nArgs;
 
     ob = PySequence_Tuple(ob); /* new reference */
     if (!ob) {
@@ -2391,7 +2390,14 @@ converters_from_argtypes(PyObject *ob)
         return NULL;
     }
 
-    nArgs = PyTuple_GET_SIZE(ob);
+    Py_ssize_t nArgs = PyTuple_GET_SIZE(ob);
+    if (nArgs > CTYPES_MAX_ARGCOUNT) {
+        PyErr_Format(PyExc_ArgError,
+                     "_argtypes_ has too many arguments (%zi), maximum is %i",
+                     nArgs, CTYPES_MAX_ARGCOUNT);
+        return NULL;
+    }
+
     converters = PyTuple_New(nArgs);
     if (!converters) {
         Py_DECREF(ob);
index 928737edb09b16b51ed91d0f3d3ea0ccdf774a7c..da2956788a1dd5fd0a1342fc0e2f8960e935a773 100644 (file)
@@ -1118,14 +1118,6 @@ GetComError(HRESULT errcode, GUID *riid, IUnknown *pIunk)
 #define IS_PASS_BY_REF(x) (x > 8 || !POW2(x))
 #endif
 
-/*
- * bpo-13097: Max number of arguments _ctypes_callproc will accept.
- *
- * This limit is enforced for the `alloca()` call in `_ctypes_callproc`,
- * to avoid allocating a massive buffer on the stack.
- */
-#define CTYPES_MAX_ARGCOUNT 1024
-
 /*
  * Requirements, must be ensured by the caller:
  * - argtuple is tuple of arguments
index 9e82ce857471406172d07b04d9d3bbe489529f44..0badb48b2a410b1ce79713b134c3d553bf3b017a 100644 (file)
 #define PARAMFLAG_FLCID 0x4
 #endif
 
+/*
+ * bpo-13097: Max number of arguments CFuncPtr._argtypes_ and
+ * _ctypes_callproc() will accept.
+ *
+ * This limit is enforced for the `alloca()` call in `_ctypes_callproc`,
+ * to avoid allocating a massive buffer on the stack.
+ */
+#define CTYPES_MAX_ARGCOUNT 1024
+
 typedef struct tagPyCArgObject PyCArgObject;
 typedef struct tagCDataObject CDataObject;
 typedef PyObject *(* GETFUNC)(void *, Py_ssize_t size);