From 4cce1352bb47babaeefb68fcfcc48ffa073745c3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 7 Feb 2022 14:53:15 +0100 Subject: 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. --- Lib/ctypes/__init__.py | 30 +++++++++++++--------- Lib/ctypes/test/test_callbacks.py | 7 +++++ .../2022-02-07-13-27-59.bpo-46323.7UENAj.rst | 3 +++ Modules/_ctypes/_ctypes.c | 10 ++++++-- Modules/_ctypes/callproc.c | 8 ------ Modules/_ctypes/ctypes.h | 9 +++++++ 6 files changed, 45 insertions(+), 22 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2022-02-07-13-27-59.bpo-46323.7UENAj.rst diff --git a/Lib/ctypes/__init__.py b/Lib/ctypes/__init__.py index b08629e..ab4d31b 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 d8e9c5a..5561ffe 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 0000000..e144450 --- /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 96078c7..da9dd09 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 928737e..da29567 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -1119,14 +1119,6 @@ GetComError(HRESULT errcode, GUID *riid, IUnknown *pIunk) #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 * - argtypes is either NULL, or a tuple of the same size as argtuple diff --git a/Modules/_ctypes/ctypes.h b/Modules/_ctypes/ctypes.h index 9e82ce8..0badb48 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); -- cgit v0.12