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.
This commit is contained in:
Victor Stinner 2022-02-07 14:53:15 +01:00 committed by GitHub
parent 8e98175a03
commit 4cce1352bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 45 additions and 22 deletions

View File

@ -92,15 +92,18 @@ def CFUNCTYPE(restype, *argtypes, **kw):
flags |= _FUNCFLAG_USE_LASTERROR flags |= _FUNCFLAG_USE_LASTERROR
if kw: if kw:
raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
try: try:
return _c_functype_cache[(restype, argtypes, flags)] return _c_functype_cache[(restype, argtypes, flags)]
except KeyError: except KeyError:
class CFunctionType(_CFuncPtr): pass
_argtypes_ = argtypes
_restype_ = restype class CFunctionType(_CFuncPtr):
_flags_ = flags _argtypes_ = argtypes
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType _restype_ = restype
return CFunctionType _flags_ = flags
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
return CFunctionType
if _os.name == "nt": if _os.name == "nt":
from _ctypes import LoadLibrary as _dlopen from _ctypes import LoadLibrary as _dlopen
@ -116,15 +119,18 @@ if _os.name == "nt":
flags |= _FUNCFLAG_USE_LASTERROR flags |= _FUNCFLAG_USE_LASTERROR
if kw: if kw:
raise ValueError("unexpected keyword argument(s) %s" % kw.keys()) raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
try: try:
return _win_functype_cache[(restype, argtypes, flags)] return _win_functype_cache[(restype, argtypes, flags)]
except KeyError: except KeyError:
class WinFunctionType(_CFuncPtr): pass
_argtypes_ = argtypes
_restype_ = restype class WinFunctionType(_CFuncPtr):
_flags_ = flags _argtypes_ = argtypes
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType _restype_ = restype
return WinFunctionType _flags_ = flags
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
return WinFunctionType
if WINFUNCTYPE.__doc__: if WINFUNCTYPE.__doc__:
WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE") WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")

View File

@ -294,15 +294,22 @@ class SampleCallbacksTestCase(unittest.TestCase):
return len(args) return len(args)
CTYPES_MAX_ARGCOUNT = 1024 CTYPES_MAX_ARGCOUNT = 1024
# valid call with nargs <= CTYPES_MAX_ARGCOUNT
proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT) proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT)
cb = proto(func) cb = proto(func)
args1 = (1,) * CTYPES_MAX_ARGCOUNT args1 = (1,) * CTYPES_MAX_ARGCOUNT
self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT) self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT)
# invalid call with nargs > CTYPES_MAX_ARGCOUNT
args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1) args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1)
with self.assertRaises(ArgumentError): with self.assertRaises(ArgumentError):
cb(*args2) 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 test_convert_result_error(self):
def func(): def func():
return ("tuple",) return ("tuple",)

View File

@ -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.

View File

@ -2382,7 +2382,6 @@ converters_from_argtypes(PyObject *ob)
_Py_IDENTIFIER(from_param); _Py_IDENTIFIER(from_param);
PyObject *converters; PyObject *converters;
Py_ssize_t i; Py_ssize_t i;
Py_ssize_t nArgs;
ob = PySequence_Tuple(ob); /* new reference */ ob = PySequence_Tuple(ob); /* new reference */
if (!ob) { if (!ob) {
@ -2391,7 +2390,14 @@ converters_from_argtypes(PyObject *ob)
return NULL; 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); converters = PyTuple_New(nArgs);
if (!converters) { if (!converters) {
Py_DECREF(ob); Py_DECREF(ob);

View File

@ -1118,14 +1118,6 @@ GetComError(HRESULT errcode, GUID *riid, IUnknown *pIunk)
#define IS_PASS_BY_REF(x) (x > 8 || !POW2(x)) #define IS_PASS_BY_REF(x) (x > 8 || !POW2(x))
#endif #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: * Requirements, must be ensured by the caller:
* - argtuple is tuple of arguments * - argtuple is tuple of arguments

View File

@ -11,6 +11,15 @@
#define PARAMFLAG_FLCID 0x4 #define PARAMFLAG_FLCID 0x4
#endif #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 tagPyCArgObject PyCArgObject;
typedef struct tagCDataObject CDataObject; typedef struct tagCDataObject CDataObject;
typedef PyObject *(* GETFUNC)(void *, Py_ssize_t size); typedef PyObject *(* GETFUNC)(void *, Py_ssize_t size);