From 6c1ed0a2b57710194268f5cd8d5a43451750c3e7 Mon Sep 17 00:00:00 2001 From: Brij Kapadia <97006829+brijkapadia@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:21:43 -0400 Subject: [PATCH 1/2] [3.13] gh-146011: Fix use-after-free in `signaldict_repr` after deletion (GH-153784) (cherry picked from commit 41a087acc2d04c5bc3db93b5367456f05ae400a4) Co-authored-by: Brij Kapadia <97006829+brijkapadia@users.noreply.github.com> Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Victor Stinner --- Lib/test/test_decimal.py | 9 +++++++++ .../2026-07-15-21-56-40.gh-issue-146011.nWmHif.rst | 2 ++ Modules/_decimal/_decimal.c | 14 ++++++++++++++ 3 files changed, 25 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-07-15-21-56-40.gh-issue-146011.nWmHif.rst diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index 23107c603f8d413..fad648b537dc715 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -4098,6 +4098,15 @@ def test_float_operation_default(self): @requires_cdecimal class CContextFlags(ContextFlags, unittest.TestCase): decimal = C + + def test_signaldict_repr(self): + Context = self.decimal.Context + ctx = Context(prec=7) + mapping = ctx.flags + del ctx + with self.assertRaisesRegex(ValueError, 'invalid signal dict'): + repr(mapping) + class PyContextFlags(ContextFlags, unittest.TestCase): decimal = P diff --git a/Misc/NEWS.d/next/Library/2026-07-15-21-56-40.gh-issue-146011.nWmHif.rst b/Misc/NEWS.d/next/Library/2026-07-15-21-56-40.gh-issue-146011.nWmHif.rst new file mode 100644 index 000000000000000..0cac0257040bd6b --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-15-21-56-40.gh-issue-146011.nWmHif.rst @@ -0,0 +1,2 @@ +Fix a heap-use-after-free in the C implementation of :mod:`decimal` +when calling :func:`repr` after deleting the :class:`~decimal.Context`. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index cbe7af132409a02..e2cf0067e9d46c9 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1419,6 +1419,20 @@ context_traverse(PyDecContextObject *self, visitproc visit, void *arg) static int context_clear(PyDecContextObject *self) { + /* Since traps and flags hold a borrowed reference to the + flags stored in the context object, these references need + to be cleared when the context object is deallocated + because traps and flags can survive. See gh-146011. */ + PyDecSignalDictObject *traps = _PyDecSignalDictObject_CAST(self->traps); + PyDecSignalDictObject *flags = _PyDecSignalDictObject_CAST(self->flags); + + if (traps != NULL) { + traps->flags = NULL; + } + if (flags != NULL) { + flags->flags = NULL; + } + Py_CLEAR(self->traps); Py_CLEAR(self->flags); return 0; From d94c6659dd9359644cc4b0de4a13d47d33088710 Mon Sep 17 00:00:00 2001 From: Brij <97006829+brijkapadia@users.noreply.github.com> Date: Fri, 24 Jul 2026 08:59:49 -0400 Subject: [PATCH 2/2] fix cast --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index e2cf0067e9d46c9..8590efd1fabe52a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -1423,8 +1423,8 @@ context_clear(PyDecContextObject *self) flags stored in the context object, these references need to be cleared when the context object is deallocated because traps and flags can survive. See gh-146011. */ - PyDecSignalDictObject *traps = _PyDecSignalDictObject_CAST(self->traps); - PyDecSignalDictObject *flags = _PyDecSignalDictObject_CAST(self->flags); + PyDecSignalDictObject *traps = (PyDecSignalDictObject *)self->traps; + PyDecSignalDictObject *flags = (PyDecSignalDictObject *)self->flags; if (traps != NULL) { traps->flags = NULL;