From 4d3f42d2ff292eaf3debd43ab99cd6aad8591086 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 30 Dec 2025 13:58:26 +0800 Subject: [PATCH 1/6] gh-143236: fix _PyEval_LoadName has use-after-free issue --- Lib/test/test_frame.py | 25 +++++++++++++++++++ ...-12-30-13-56-40.gh-issue-143236.IxHw2y.rst | 1 + Python/ceval.c | 10 +++++++- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 18ade18d1a1708..4ee604215d7844 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -177,6 +177,31 @@ class C: self.clear_traceback_frames(exc.__traceback__) self.assertIs(None, wr()) + @support.cpython_only + def test_frame_clear_during_load_name(self): + def get_frame(): + try: + raise RuntimeError + except RuntimeError as e: + return e.__traceback__.tb_frame + + frame = get_frame() + + class Fuse(str): + cleared = False + __hash__ = str.__hash__ + def __eq__(self, other): + if not Fuse.cleared and other == "boom": + Fuse.cleared = True + Fuse.frame.clear() + return False + return super().__eq__(other) + + Fuse.frame = frame + frame.f_locals[Fuse("boom")] = 0 + with self.assertRaises(NameError): + exec("boom", {}, frame.f_locals) + class FrameAttrsTest(unittest.TestCase): diff --git a/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst b/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst new file mode 100644 index 00000000000000..54380aa2df4a67 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst @@ -0,0 +1 @@ +Fix a bug in :mod:`ceval` where the load name has use-after-free bug. diff --git a/Python/ceval.c b/Python/ceval.c index 924afaa97443cb..d276cd274c1f24 100644 --- a/Python/ceval.c +++ b/Python/ceval.c @@ -4061,9 +4061,17 @@ _PyEval_LoadName(PyThreadState *tstate, _PyInterpreterFrame *frame, PyObject *na "no locals found"); return NULL; } - if (PyMapping_GetOptionalItem(frame->f_locals, name, &value) < 0) { + + PyObject *locals = frame->f_locals; + Py_INCREF(locals); + + if (PyMapping_GetOptionalItem(locals, name, &value) < 0) { + Py_DECREF(locals); return NULL; } + + Py_DECREF(locals); + if (value != NULL) { return value; } From 7d537cda5850840862ca972cbe152ad27b79ad37 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 30 Dec 2025 14:33:02 +0800 Subject: [PATCH 2/6] fix: fix docs --- .../next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst b/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst index 54380aa2df4a67..8479757c68c53a 100644 --- a/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst +++ b/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst @@ -1 +1 @@ -Fix a bug in :mod:`ceval` where the load name has use-after-free bug. +Fix a bug in `ceval` where the load name has use-after-free bug. From 57ae1f0647473be3f5769adb8b963a47630c9dff Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 30 Dec 2025 14:35:41 +0800 Subject: [PATCH 3/6] fix: fix docs --- .../next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst b/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst index 8479757c68c53a..52d2e4ba94799a 100644 --- a/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst +++ b/Misc/NEWS.d/next/Library/2025-12-30-13-56-40.gh-issue-143236.IxHw2y.rst @@ -1 +1 @@ -Fix a bug in `ceval` where the load name has use-after-free bug. +Fix a bug in ceval where the load name has use-after-free bug. From 28b831302287fc40bab153b5e1210dbfb6754aa3 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 30 Dec 2025 14:52:14 +0800 Subject: [PATCH 4/6] fix: fix the test --- Lib/test/test_frame.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 4ee604215d7844..3e4827b2288758 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -187,15 +187,18 @@ def get_frame(): frame = get_frame() - class Fuse(str): + class Fuse: cleared = False - __hash__ = str.__hash__ + def __init__(self, s): + self.s = s + def __hash__(self): + return hash(self.s) def __eq__(self, other): if not Fuse.cleared and other == "boom": Fuse.cleared = True Fuse.frame.clear() return False - return super().__eq__(other) + return True Fuse.frame = frame frame.f_locals[Fuse("boom")] = 0 From 207369446e8ec07eac5e6c62cffcdd50e3cad861 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 30 Dec 2025 15:02:21 +0800 Subject: [PATCH 5/6] fix: fix the test --- Lib/test/test_frame.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index 3e4827b2288758..cc3aa1521a2259 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -197,8 +197,7 @@ def __eq__(self, other): if not Fuse.cleared and other == "boom": Fuse.cleared = True Fuse.frame.clear() - return False - return True + return False Fuse.frame = frame frame.f_locals[Fuse("boom")] = 0 From c7bb3cdc7e20ee618732674d0bbbadc1a5a7b7d4 Mon Sep 17 00:00:00 2001 From: fatelei Date: Tue, 30 Dec 2025 15:18:06 +0800 Subject: [PATCH 6/6] fix: fix the test --- Lib/test/test_frame.py | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_frame.py b/Lib/test/test_frame.py index cc3aa1521a2259..77eaeb4f9d8fea 100644 --- a/Lib/test/test_frame.py +++ b/Lib/test/test_frame.py @@ -187,22 +187,14 @@ def get_frame(): frame = get_frame() - class Fuse: - cleared = False - def __init__(self, s): - self.s = s - def __hash__(self): - return hash(self.s) - def __eq__(self, other): - if not Fuse.cleared and other == "boom": - Fuse.cleared = True - Fuse.frame.clear() - return False + class Fuse(dict): + def __getitem__(self, key): + if key == "boom": + frame.clear() + raise KeyError(key) - Fuse.frame = frame - frame.f_locals[Fuse("boom")] = 0 with self.assertRaises(NameError): - exec("boom", {}, frame.f_locals) + exec("boom", {}, Fuse()) class FrameAttrsTest(unittest.TestCase):