aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>2022-10-01 21:19:57 -0700
committerGitHub <noreply@github.com>2022-10-01 21:19:57 -0700
commitdbde686a49c2d0d7ce476d134f8daffd636382df (patch)
tree29b0f26d9dba15424127d730b3cf2bf4bb894821
parent9189cd6b05a32b67981d2157947d745b2d0dac88 (diff)
downloadcpython3-dbde686a49c2d0d7ce476d134f8daffd636382df.tar.gz
gh-97591: In `Exception.__setstate__()` acquire strong references before calling `tp_hash` slot (GH-97700)
(cherry picked from commit d63943860974f232b5f027dc6535d25d1b4d8fc0) Co-authored-by: Ofey Chan <ofey206@gmail.com>
-rw-r--r--Lib/test/test_baseexception.py25
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2022-10-01-08-55-09.gh-issue-97591.pw6kkH.rst2
-rw-r--r--Objects/exceptions.c8
3 files changed, 34 insertions, 1 deletions
diff --git a/Lib/test/test_baseexception.py b/Lib/test/test_baseexception.py
index 0061b3fa8e..4c3cf0b964 100644
--- a/Lib/test/test_baseexception.py
+++ b/Lib/test/test_baseexception.py
@@ -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"""
diff --git a/Misc/NEWS.d/next/Core and Builtins/2022-10-01-08-55-09.gh-issue-97591.pw6kkH.rst b/Misc/NEWS.d/next/Core and Builtins/2022-10-01-08-55-09.gh-issue-97591.pw6kkH.rst
new file mode 100644
index 0000000000..d3a5867db7
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2022-10-01-08-55-09.gh-issue-97591.pw6kkH.rst
@@ -0,0 +1,2 @@
+Fixed a missing incref/decref pair in `Exception.__setstate__()`.
+Patch by Ofey Chan.
diff --git a/Objects/exceptions.c b/Objects/exceptions.c
index 319eb238ec..5ab4ac09e6 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -167,8 +167,14 @@ 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;