From: bkap123 <97006829+bkap123@users.noreply.github.com> Date: Wed, 18 Mar 2026 12:46:01 +0000 (-0400) Subject: gh-146075: Prevent crash in `functools.partial()` from malformed `str` subclass ... X-Git-Tag: v3.15.0a8~270 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=64862112b7beb005523e7bca15c59c78c5319e58;p=thirdparty%2FPython%2Fcpython.git gh-146075: Prevent crash in `functools.partial()` from malformed `str` subclass (GH-146078) In `partial_vectorcall`, an error returned by `PyDict_Contains` was considered to be a truthy value. Now, the error is handled appropriately. --- diff --git a/Lib/test/test_functools.py b/Lib/test/test_functools.py index dda42cb33072..efa85b564f7c 100644 --- a/Lib/test/test_functools.py +++ b/Lib/test/test_functools.py @@ -565,7 +565,19 @@ class TestPartial: g_partial = functools.partial(func, trigger, None, None, None, None, arg=None) self.assertEqual(repr(g_partial),"functools.partial(Function(old_function), EvilObject, None, None, None, None, arg=None)") + def test_str_subclass_error(self): + class BadStr(str): + def __eq__(self, other): + raise RuntimeError + def __hash__(self): + return str.__hash__(self) + + def f(**kwargs): + return kwargs + p = functools.partial(f, poison="") + with self.assertRaises(RuntimeError): + result = p(**{BadStr("poison"): "new_value"}) @unittest.skipUnless(c_functools, 'requires the C _functools module') class TestPartialC(TestPartial, unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2026-03-17-19-30-45.gh-issue-146075.85sCSh.rst b/Misc/NEWS.d/next/Library/2026-03-17-19-30-45.gh-issue-146075.85sCSh.rst new file mode 100644 index 000000000000..792ea3ad6686 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-03-17-19-30-45.gh-issue-146075.85sCSh.rst @@ -0,0 +1 @@ +Errors when calling :func:`functools.partial` with a malformed keyword will no longer crash the interpreter. diff --git a/Modules/_functoolsmodule.c b/Modules/_functoolsmodule.c index 723080ede1d9..576494e846ca 100644 --- a/Modules/_functoolsmodule.c +++ b/Modules/_functoolsmodule.c @@ -457,7 +457,11 @@ partial_vectorcall(PyObject *self, PyObject *const *args, for (Py_ssize_t i = 0; i < nkwds; ++i) { key = PyTuple_GET_ITEM(kwnames, i); val = args[nargs + i]; - if (PyDict_Contains(pto->kw, key)) { + int contains = PyDict_Contains(pto->kw, key); + if (contains < 0) { + goto error; + } + else if (contains == 1) { if (pto_kw_merged == NULL) { pto_kw_merged = PyDict_Copy(pto->kw); if (pto_kw_merged == NULL) {