From: Victor Stinner Date: Mon, 7 Feb 2022 13:53:15 +0000 (+0100) Subject: bpo-46323: _ctypes.CFuncPtr fails if _argtypes_ is too long (GH-31188) X-Git-Tag: v3.11.0a6~290 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4cce1352bb47babaeefb68fcfcc48ffa073745c3;p=thirdparty%2FPython%2Fcpython.git bpo-46323: _ctypes.CFuncPtr fails if _argtypes_ is too long (GH-31188) 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. --- diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index b08629e8df4d..ab4d31b0acb0 100644 --- a/Lib/ctypes/__init__.py +++ b/Lib/ctypes/__init__.py @@ -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") diff --git a/Lib/ctypes/test/test_callbacks.py b/Lib/ctypes/test/test_callbacks.py index d8e9c5a760e2..5561ffefe12f 100644 --- a/Lib/ctypes/test/test_callbacks.py +++ b/Lib/ctypes/test/test_callbacks.py @@ -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 index 000000000000..e144450f2527 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2022-02-07-13-27-59.bpo-46323.7UENAj.rst @@ -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. diff --git a/Modules/_ctypes/_ctypes.c b/Modules/_ctypes/_ctypes.c index 96078c7726d5..da9dd096c73d 100644 --- a/Modules/_ctypes/_ctypes.c +++ b/Modules/_ctypes/_ctypes.c @@ -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); diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index 928737edb09b..da2956788a1d 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -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 diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 9e82ce857471..0badb48b2a41 100644 --- a/Modules/_ctypes/ctypes.h +++ b/Modules/_ctypes/ctypes.h @@ -11,6 +11,15 @@ #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);