From dcea95e3888a8d060fcd4946b43e0ab83a830865 Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Mon, 10 Aug 2026 23:58:42 +0800 Subject: [PATCH] gh-155493: Fix crash in decimal SignalDict on re-entrant Context teardown signaldict_setitem and signaldict_richcompare read the borrowed flags pointer after PyObject_IsTrue calls the value's __bool__, which can deallocate the owning Context. gh-146011 added this guard to signaldict_repr; this extends it to the two remaining methods. --- Lib/test/test_decimal.py | 28 +++++++++++++++++++ ...-08-10-12-30-00.gh-issue-155493.Sd3Uf9.rst | 3 ++ Modules/_decimal/_decimal.c | 9 ++++++ 3 files changed, 40 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-10-12-30-00.gh-issue-155493.Sd3Uf9.rst diff --git a/Lib/test/test_decimal.py b/Lib/test/test_decimal.py index b8c09c7f43e3e3b..81487d12a42542b 100644 --- a/Lib/test/test_decimal.py +++ b/Lib/test/test_decimal.py @@ -2988,6 +2988,34 @@ def test_none_args(self): assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero, Overflow]) + def test_signaldict_reentrant_dealloc(self): + # A __bool__ that deallocates the owning context while assigning to or + # comparing Context.flags must raise, not crash. + if self.decimal is not C: + self.skipTest("SignalDict only exists in the C implementation") + Context = self.decimal.Context + InvalidOperation = self.decimal.InvalidOperation + + class Evil: + def __init__(self, box): + self.box = box + def __bool__(self): + import gc + self.box.clear() + gc.collect() + return True + + box = [Context()] + flags = box[0].flags + with self.assertRaises(ValueError): + flags[InvalidOperation] = Evil(box) + + box = [Context()] + other = box[0].flags.copy() + other[InvalidOperation] = Evil(box) + with self.assertRaises(ValueError): + box[0].flags == other + def test_pickle(self): for proto in range(pickle.HIGHEST_PROTOCOL + 1): diff --git a/Misc/NEWS.d/next/Library/2026-08-10-12-30-00.gh-issue-155493.Sd3Uf9.rst b/Misc/NEWS.d/next/Library/2026-08-10-12-30-00.gh-issue-155493.Sd3Uf9.rst new file mode 100644 index 000000000000000..c7ef01718b92799 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-10-12-30-00.gh-issue-155493.Sd3Uf9.rst @@ -0,0 +1,3 @@ +Fix a crash in the C implementation of :mod:`decimal` when a value's +``__bool__`` deallocates the owning context while it is assigned to, or +compared against, ``Context.flags``. Patch by tonghuaroot. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index ada9b02d690717f..17e0b1a2145e2aa 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -740,6 +740,11 @@ signaldict_setitem(PyObject *self, PyObject *key, PyObject *value) return -1; } + if (SdFlagAddr(self) == NULL) { + /* value.__bool__() may have deallocated the owning context. */ + return value_error_int(INVALID_SIGNALDICT_ERROR_MSG); + } + if (x == 1) { SdFlags(self) |= flag; } @@ -814,6 +819,10 @@ signaldict_richcompare(PyObject *v, PyObject *w, int op) return NULL; } } + else if (SdFlagAddr(v) == NULL) { + /* w's __bool__() may have deallocated v's context. */ + return value_error_ptr(INVALID_SIGNALDICT_ERROR_MSG); + } else { res = (SdFlags(v)==flags) ^ (op==Py_NE) ? Py_True : Py_False; }