Skip to content

Commit 4cce135

Browse files
authored
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.
1 parent 8e98175 commit 4cce135

File tree

6 files changed

+45
-22
lines changed

6 files changed

+45
-22
lines changed

Lib/ctypes/__init__.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,18 @@ def CFUNCTYPE(restype, *argtypes, **kw):
9292
flags |= _FUNCFLAG_USE_LASTERROR
9393
if kw:
9494
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
95+
9596
try:
9697
return _c_functype_cache[(restype, argtypes, flags)]
9798
except KeyError:
98-
class CFunctionType(_CFuncPtr):
99-
_argtypes_ = argtypes
100-
_restype_ = restype
101-
_flags_ = flags
102-
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
103-
return CFunctionType
99+
pass
100+
101+
class CFunctionType(_CFuncPtr):
102+
_argtypes_ = argtypes
103+
_restype_ = restype
104+
_flags_ = flags
105+
_c_functype_cache[(restype, argtypes, flags)] = CFunctionType
106+
return CFunctionType
104107

105108
if _os.name == "nt":
106109
from _ctypes import LoadLibrary as _dlopen
@@ -116,15 +119,18 @@ def WINFUNCTYPE(restype, *argtypes, **kw):
116119
flags |= _FUNCFLAG_USE_LASTERROR
117120
if kw:
118121
raise ValueError("unexpected keyword argument(s) %s" % kw.keys())
122+
119123
try:
120124
return _win_functype_cache[(restype, argtypes, flags)]
121125
except KeyError:
122-
class WinFunctionType(_CFuncPtr):
123-
_argtypes_ = argtypes
124-
_restype_ = restype
125-
_flags_ = flags
126-
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
127-
return WinFunctionType
126+
pass
127+
128+
class WinFunctionType(_CFuncPtr):
129+
_argtypes_ = argtypes
130+
_restype_ = restype
131+
_flags_ = flags
132+
_win_functype_cache[(restype, argtypes, flags)] = WinFunctionType
133+
return WinFunctionType
128134
if WINFUNCTYPE.__doc__:
129135
WINFUNCTYPE.__doc__ = CFUNCTYPE.__doc__.replace("CFUNCTYPE", "WINFUNCTYPE")
130136

Lib/ctypes/test/test_callbacks.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,15 +294,22 @@ def func(*args):
294294
return len(args)
295295

296296
CTYPES_MAX_ARGCOUNT = 1024
297+
298+
# valid call with nargs <= CTYPES_MAX_ARGCOUNT
297299
proto = CFUNCTYPE(c_int, *(c_int,) * CTYPES_MAX_ARGCOUNT)
298300
cb = proto(func)
299301
args1 = (1,) * CTYPES_MAX_ARGCOUNT
300302
self.assertEqual(cb(*args1), CTYPES_MAX_ARGCOUNT)
301303

304+
# invalid call with nargs > CTYPES_MAX_ARGCOUNT
302305
args2 = (1,) * (CTYPES_MAX_ARGCOUNT + 1)
303306
with self.assertRaises(ArgumentError):
304307
cb(*args2)
305308

309+
# error when creating the type with too many arguments
310+
with self.assertRaises(ArgumentError):
311+
CFUNCTYPE(c_int, *(c_int,) * (CTYPES_MAX_ARGCOUNT + 1))
312+
306313
def test_convert_result_error(self):
307314
def func():
308315
return ("tuple",)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
``ctypes.CFUNCTYPE()`` and ``ctypes.WINFUNCTYPE()`` now fail to create the type
2+
if its ``_argtypes_`` member contains too many arguments. Previously, the error
3+
was only raised when calling a function. Patch by Victor Stinner.

Modules/_ctypes/_ctypes.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2382,7 +2382,6 @@ converters_from_argtypes(PyObject *ob)
23822382
_Py_IDENTIFIER(from_param);
23832383
PyObject *converters;
23842384
Py_ssize_t i;
2385-
Py_ssize_t nArgs;
23862385

23872386
ob = PySequence_Tuple(ob); /* new reference */
23882387
if (!ob) {
@@ -2391,7 +2390,14 @@ converters_from_argtypes(PyObject *ob)
23912390
return NULL;
23922391
}
23932392

2394-
nArgs = PyTuple_GET_SIZE(ob);
2393+
Py_ssize_t nArgs = PyTuple_GET_SIZE(ob);
2394+
if (nArgs > CTYPES_MAX_ARGCOUNT) {
2395+
PyErr_Format(PyExc_ArgError,
2396+
"_argtypes_ has too many arguments (%zi), maximum is %i",
2397+
nArgs, CTYPES_MAX_ARGCOUNT);
2398+
return NULL;
2399+
}
2400+
23952401
converters = PyTuple_New(nArgs);
23962402
if (!converters) {
23972403
Py_DECREF(ob);

Modules/_ctypes/callproc.c

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1118,14 +1118,6 @@ GetComError(HRESULT errcode, GUID *riid, IUnknown *pIunk)
11181118
#define IS_PASS_BY_REF(x) (x > 8 || !POW2(x))
11191119
#endif
11201120

1121-
/*
1122-
* bpo-13097: Max number of arguments _ctypes_callproc will accept.
1123-
*
1124-
* This limit is enforced for the `alloca()` call in `_ctypes_callproc`,
1125-
* to avoid allocating a massive buffer on the stack.
1126-
*/
1127-
#define CTYPES_MAX_ARGCOUNT 1024
1128-
11291121
/*
11301122
* Requirements, must be ensured by the caller:
11311123
* - argtuple is tuple of arguments

Modules/_ctypes/ctypes.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
#define PARAMFLAG_FLCID 0x4
1212
#endif
1313

14+
/*
15+
* bpo-13097: Max number of arguments CFuncPtr._argtypes_ and
16+
* _ctypes_callproc() will accept.
17+
*
18+
* This limit is enforced for the `alloca()` call in `_ctypes_callproc`,
19+
* to avoid allocating a massive buffer on the stack.
20+
*/
21+
#define CTYPES_MAX_ARGCOUNT 1024
22+
1423
typedef struct tagPyCArgObject PyCArgObject;
1524
typedef struct tagCDataObject CDataObject;
1625
typedef PyObject *(* GETFUNC)(void *, Py_ssize_t size);

0 commit comments

Comments
 (0)