[Python-checkins] gh-97591: In `Exception.__setstate__()` acquire strong references before calling `tp_hash` slot (GH-97700)

miss-islington webhook-mailer at python.org
Sun Oct 2 00:18:43 EDT 2022


https://github.com/python/cpython/commit/c6fcbb4928ced97df81d2cef6c5dcec6bc7dcab7
commit: c6fcbb4928ced97df81d2cef6c5dcec6bc7dcab7
branch: 3.10
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: miss-islington <31488909+miss-islington at users.noreply.github.com>
date: 2022-10-01T21:18:38-07:00
summary:

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 at gmail.com>

files:
A Misc/NEWS.d/next/Core and Builtins/2022-10-01-08-55-09.gh-issue-97591.pw6kkH.rst
M Lib/test/test_baseexception.py
M Objects/exceptions.c

diff --git a/Lib/test/test_baseexception.py b/Lib/test/test_baseexception.py
index 8db497a17285..018626e8db63 100644
--- a/Lib/test/test_baseexception.py
+++ b/Lib/test/test_baseexception.py
@@ -114,6 +114,31 @@ def test_interface_no_arg(self):
                 [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 000000000000..d3a5867db7fc
--- /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 9639b4436a07..5dfd1d64d8c1 100644
--- a/Objects/exceptions.c
+++ b/Objects/exceptions.c
@@ -156,8 +156,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;



More information about the Python-checkins mailing list