From f9f5fa6b8d067112de47ce6a8be2f9879b35b610 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Thu, 22 Oct 2026 14:16:04 -0700 Subject: [PATCH] gh-159739: Detect recursive untyped ctypes arguments (GH-160194) _as_parameter_ chains were recursion-checked for typed arguments but not for foreign calls without argtypes. A cyclic chain could exhaust the C stack and crash before the function was called. Apply the same recursion guard to untyped conversion. Backport to 3.15 and 3.14. (cherry picked from commit 3e19e0408d9791c235c6c85818de1b9b2eb93dfa) Co-authored-by: Jelle Zijlstra --- Lib/test/test_ctypes/test_as_parameter.py | 4 ++++ .../2026-10-21-21-26-10.gh-issue-159739.b4ueJ7.rst | 1 + Modules/_ctypes/callproc.c | 9 +++++++-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.b4ueJ7.rst diff --git a/Lib/test/test_ctypes/test_as_parameter.py b/Lib/test/test_ctypes/test_as_parameter.py index abbfb6efdcea55b..d081f7ccb0eb079 100644 --- a/Lib/test/test_ctypes/test_as_parameter.py +++ b/Lib/test/test_ctypes/test_as_parameter.py @@ -213,6 +213,10 @@ class A: with self.assertRaises(RecursionError): c_type.from_param(a) + function = CDLL(_ctypes_test.__file__)._testfunc_p_p + with self.assertRaisesRegex(ArgumentError, 'RecursionError'): + function(a) + class AsParamWrapper: def __init__(self, param): diff --git a/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.b4ueJ7.rst b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.b4ueJ7.rst new file mode 100644 index 000000000000000..6230801bbc09278 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-21-21-26-10.gh-issue-159739.b4ueJ7.rst @@ -0,0 +1 @@ +Detect recursive ``_as_parameter_`` chains for :mod:`ctypes` calls without argument types. diff --git a/Modules/_ctypes/callproc.c b/Modules/_ctypes/callproc.c index 746034c8004c5c4..380a8f93c885b9a 100644 --- a/Modules/_ctypes/callproc.c +++ b/Modules/_ctypes/callproc.c @@ -91,6 +91,7 @@ module _ctypes #define DONT_USE_SEH #endif +#include "pycore_ceval.h" // _Py_EnterRecursiveCall() #include "pycore_runtime.h" // _PyRuntime #include "pycore_global_objects.h"// _Py_ID() #include "pycore_traceback.h" // _PyTraceback_Add() @@ -744,8 +745,12 @@ static int ConvParam(ctypes_state *st, attribute) */ if (arg) { - int result; - result = ConvParam(st, arg, index, pa); + if (_Py_EnterRecursiveCall(" while processing _as_parameter_")) { + Py_DECREF(arg); + return -1; + } + int result = ConvParam(st, arg, index, pa); + _Py_LeaveRecursiveCall(); Py_DECREF(arg); return result; }