gh-97591: In `Exception.__setstate__()` acquire strong references before calling `tp_hash` slot (#97700)

This commit is contained in:
Ofey Chan 2022-10-02 11:57:17 +08:00 committed by GitHub
parent 8baef8ae36
commit d639438609
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 34 additions and 1 deletions

View File

@ -114,6 +114,31 @@ class ExceptionClassTests(unittest.TestCase):
[repr(exc), exc.__class__.__name__ + '()'])
self.interface_test_driver(results)
def test_setstate_refcount_no_crash(self):
# gh-97591: Acquire strong reference before calling tp_hash slot
# in PyObject_SetAttr.
import gc
d = {}
class HashThisKeyWillClearTheDict(str):
def __hash__(self) -> int:
d.clear()
return super().__hash__()
class Value(str):
pass
exc = Exception()
d[HashThisKeyWillClearTheDict()] = Value() # refcount of Value() is 1 now
# Exception.__setstate__ should aquire a strong reference of key and
# value in the dict. Otherwise, Value()'s refcount would go below
# zero in the tp_hash call in PyObject_SetAttr(), and it would cause
# crash in GC.
exc.__setstate__(d) # __hash__() is called again here, clearing the dict.
# This GC would crash if the refcount of Value() goes below zero.
gc.collect()
class UsageTests(unittest.TestCase):
"""Test usage of exceptions"""

View File

@ -0,0 +1,2 @@
Fixed a missing incref/decref pair in `Exception.__setstate__()`.
Patch by Ofey Chan.

View File

@ -167,10 +167,16 @@ BaseException_setstate(PyObject *self, PyObject *state)
return NULL;
}
while (PyDict_Next(state, &i, &d_key, &d_value)) {
if (PyObject_SetAttr(self, d_key, d_value) < 0)
Py_INCREF(d_key);
Py_INCREF(d_value);
int res = PyObject_SetAttr(self, d_key, d_value);
Py_DECREF(d_value);
Py_DECREF(d_key);
if (res < 0) {
return NULL;
}
}
}
Py_RETURN_NONE;
}