Skip to content

Commit dcea95e

Browse files
committed
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.
1 parent b2c7b34 commit dcea95e

3 files changed

Lines changed: 40 additions & 0 deletions

File tree

Lib/test/test_decimal.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2988,6 +2988,34 @@ def test_none_args(self):
29882988
assert_signals(self, c, 'traps', [InvalidOperation, DivisionByZero,
29892989
Overflow])
29902990

2991+
def test_signaldict_reentrant_dealloc(self):
2992+
# A __bool__ that deallocates the owning context while assigning to or
2993+
# comparing Context.flags must raise, not crash.
2994+
if self.decimal is not C:
2995+
self.skipTest("SignalDict only exists in the C implementation")
2996+
Context = self.decimal.Context
2997+
InvalidOperation = self.decimal.InvalidOperation
2998+
2999+
class Evil:
3000+
def __init__(self, box):
3001+
self.box = box
3002+
def __bool__(self):
3003+
import gc
3004+
self.box.clear()
3005+
gc.collect()
3006+
return True
3007+
3008+
box = [Context()]
3009+
flags = box[0].flags
3010+
with self.assertRaises(ValueError):
3011+
flags[InvalidOperation] = Evil(box)
3012+
3013+
box = [Context()]
3014+
other = box[0].flags.copy()
3015+
other[InvalidOperation] = Evil(box)
3016+
with self.assertRaises(ValueError):
3017+
box[0].flags == other
3018+
29913019
def test_pickle(self):
29923020

29933021
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix a crash in the C implementation of :mod:`decimal` when a value's
2+
``__bool__`` deallocates the owning context while it is assigned to, or
3+
compared against, ``Context.flags``. Patch by tonghuaroot.

Modules/_decimal/_decimal.c

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,11 @@ signaldict_setitem(PyObject *self, PyObject *key, PyObject *value)
740740
return -1;
741741
}
742742

743+
if (SdFlagAddr(self) == NULL) {
744+
/* value.__bool__() may have deallocated the owning context. */
745+
return value_error_int(INVALID_SIGNALDICT_ERROR_MSG);
746+
}
747+
743748
if (x == 1) {
744749
SdFlags(self) |= flag;
745750
}
@@ -814,6 +819,10 @@ signaldict_richcompare(PyObject *v, PyObject *w, int op)
814819
return NULL;
815820
}
816821
}
822+
else if (SdFlagAddr(v) == NULL) {
823+
/* w's __bool__() may have deallocated v's context. */
824+
return value_error_ptr(INVALID_SIGNALDICT_ERROR_MSG);
825+
}
817826
else {
818827
res = (SdFlags(v)==flags) ^ (op==Py_NE) ? Py_True : Py_False;
819828
}

0 commit comments

Comments
 (0)