Package: release.debian.org
Severity: normal
Tags: trixie
X-Debbugs-Cc: [email protected], [email protected]
Control: affects -1 + src:python3.13
User: [email protected]
Usertags: pu

Given the impact, this should go into trixie-updates.

Sorry, python3.13/3.13.5-2+deb13u3 had a use-after-free error, resulting 
in heap corruption, that trips up a number of users. See bug #1141977 
for user reports.

Zigo mentioned to me that it broke nova's test suite, and I verified 
this.

https://debusine.debian.net/debian/developers/work-request/927219/
The build fails with a couple of:

> testtools.testresult.real._StringException

It also broke building python3.13's own documentation, which is a good 
sign that it's a worrying regression.

https://debusine.debian.net/debian/developers/work-request/920956/

[ Reason ]
3.13.5-2+deb13u3 included a cherry-pick to fix a GC bug that affected 
OpenStack and existed in 3.13 -> 3.13.12. This depended on a related 
memory-handling change, that I missed when I grabbed the fix. This 
update includes the missing commit.

It was mentioned in the issue, I forgot to go down the rabbit hole of 
investigating the necessity for it.

[ Impact ]
Regression for many Python users :(

[ Tests ]
* The missing commit brings unit tests.
* Python's test suite passes (as it did for u3, sadface)
  https://debusine.debian.net/debian/r-stefanor-python/work-request/926428/
* Nova's test suite passes.
  https://debusine.debian.net/debian/r-stefanor-python/work-request/927171/
* Users who reported regressions in #1141977 have tested the update and 
  reported success.

[ Risks ]
This seems to make things better, rather than worse.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
* Patch: Fix use-after-free in dict.clear() with embedded values.
  Resolves a regression in 3.13.5-2+deb13u3. (Closes: #1141977)
diff -Nru python3.13-3.13.5/debian/changelog python3.13-3.13.5/debian/changelog
--- python3.13-3.13.5/debian/changelog  2026-06-13 11:18:01.000000000 -0300
+++ python3.13-3.13.5/debian/changelog  2026-07-15 17:25:40.000000000 -0300
@@ -1,3 +1,10 @@
+python3.13 (3.13.5-2+deb13u4) trixie; urgency=medium
+
+  * Patch: Fix use-after-free in dict.clear() with embedded values.
+    Resolves a regression in 3.13.5-2+deb13u3. (Closes: #1141977)
+
+ -- Stefano Rivera <[email protected]>  Wed, 15 Jul 2026 17:25:40 -0300
+
 python3.13 (3.13.5-2+deb13u3) trixie; urgency=medium
 
   [ Stefano Rivera ]
diff -Nru python3.13-3.13.5/debian/patches/dict-use-after-free.patch 
python3.13-3.13.5/debian/patches/dict-use-after-free.patch
--- python3.13-3.13.5/debian/patches/dict-use-after-free.patch  1969-12-31 
21:00:00.000000000 -0300
+++ python3.13-3.13.5/debian/patches/dict-use-after-free.patch  2026-07-15 
17:25:40.000000000 -0300
@@ -0,0 +1,143 @@
+From d567f451e157e241533ec4109fadd29301576060 Mon Sep 17 00:00:00 2001
+From: Sam Gross <[email protected]>
+Date: Mon, 2 Mar 2026 13:59:52 -0500
+Subject: [PATCH] [3.13] gh-130555: Fix use-after-free in dict.clear() with
+ embedded values (gh-145268) (#145430)
+
+---
+ Lib/test/test_dict.py                         | 63 +++++++++++++++++++
+ ...-02-26-12-00-00.gh-issue-130555.TMSOIu.rst |  3 +
+ Objects/dictobject.c                          | 31 ++++++---
+ 3 files changed, 88 insertions(+), 9 deletions(-)
+ create mode 100644 
Misc/NEWS.d/next/Core_and_Builtins/2026-02-26-12-00-00.gh-issue-130555.TMSOIu.rst
+
+--- a/Lib/test/test_dict.py
++++ b/Lib/test/test_dict.py
+@@ -1664,6 +1664,69 @@
+ class SubclassMappingTests(mapping_tests.BasicTestMappingProtocol):
+     type2test = Dict
+ 
++    def test_clear_reentrant_embedded(self):
++        # gh-130555: dict.clear() must be safe when values are embedded
++        # in an object and a destructor mutates the dict.
++        class MyObj: pass
++        class ClearOnDelete:
++            def __del__(self):
++                nonlocal x
++                del x
++
++        x = MyObj()
++        x.a = ClearOnDelete()
++
++        d = x.__dict__
++        d.clear()
++
++    def test_clear_reentrant_cycle(self):
++        # gh-130555: dict.clear() must be safe for embedded dicts when the
++        # object is part of a reference cycle and the last reference to the
++        # dict is via the cycle.
++        class MyObj: pass
++        obj = MyObj()
++        obj.f = obj
++        obj.attr = "attr"
++
++        d = obj.__dict__
++        del obj
++
++        d.clear()
++
++    def test_clear_reentrant_force_combined(self):
++        # gh-130555: dict.clear() must be safe when a destructor forces the
++        # dict from embedded/split to combined (setting ma_values to NULL).
++        class MyObj: pass
++        class ForceConvert:
++            def __del__(self):
++                d[1] = "trigger"
++
++        x = MyObj()
++        x.a = ForceConvert()
++        x.b = "other"
++
++        d = x.__dict__
++        d.clear()
++
++    def test_clear_reentrant_delete(self):
++        # gh-130555: dict.clear() must be safe when a destructor deletes
++        # a key from the same embedded dict.
++        class MyObj: pass
++        class DelKey:
++            def __del__(self):
++                try:
++                    del d['b']
++                except KeyError:
++                    pass
++
++        x = MyObj()
++        x.a = DelKey()
++        x.b = "value_b"
++        x.c = "value_c"
++
++        d = x.__dict__
++        d.clear()
++
+ 
+ if __name__ == "__main__":
+     unittest.main()
+--- /dev/null
++++ 
b/Misc/NEWS.d/next/Core_and_Builtins/2026-02-26-12-00-00.gh-issue-130555.TMSOIu.rst
+@@ -0,0 +1,3 @@
++Fix use-after-free in :meth:`dict.clear` when the dictionary values are
++embedded in an object and a destructor causes re-entrant mutation of the
++dictionary.
+--- a/Objects/dictobject.c
++++ b/Objects/dictobject.c
+@@ -2794,6 +2794,21 @@
+ }
+ 
+ static void
++clear_embedded_values(PyDictValues *values, Py_ssize_t nentries)
++{
++    PyObject *refs[SHARED_KEYS_MAX_SIZE];
++    assert(nentries <= SHARED_KEYS_MAX_SIZE);
++    for (Py_ssize_t i = 0; i < nentries; i++) {
++        refs[i] = values->values[i];
++        values->values[i] = NULL;
++    }
++    values->size = 0;
++    for (Py_ssize_t i = 0; i < nentries; i++) {
++        Py_XDECREF(refs[i]);
++    }
++}
++
++static void
+ clear_lock_held(PyObject *op)
+ {
+     PyDictObject *mp;
+@@ -2824,20 +2839,18 @@
+         assert(oldkeys->dk_refcnt == 1);
+         dictkeys_decref(interp, oldkeys, IS_DICT_SHARED(mp));
+     }
++    else if (oldvalues->embedded) {
++        clear_embedded_values(oldvalues, oldkeys->dk_nentries);
++    }
+     else {
++        set_values(mp, NULL);
++        set_keys(mp, Py_EMPTY_KEYS);
+         n = oldkeys->dk_nentries;
+         for (i = 0; i < n; i++) {
+             Py_CLEAR(oldvalues->values[i]);
+         }
+-        if (oldvalues->embedded) {
+-            oldvalues->size = 0;
+-        }
+-        else {
+-            set_values(mp, NULL);
+-            set_keys(mp, Py_EMPTY_KEYS);
+-            free_values(oldvalues, IS_DICT_SHARED(mp));
+-            dictkeys_decref(interp, oldkeys, false);
+-        }
++        free_values(oldvalues, IS_DICT_SHARED(mp));
++        dictkeys_decref(interp, oldkeys, false);
+     }
+     ASSERT_CONSISTENT(mp);
+ }
diff -Nru python3.13-3.13.5/debian/patches/series 
python3.13-3.13.5/debian/patches/series
--- python3.13-3.13.5/debian/patches/series     2026-06-13 11:18:01.000000000 
-0300
+++ python3.13-3.13.5/debian/patches/series     2026-07-15 17:25:40.000000000 
-0300
@@ -51,6 +51,7 @@
 CVE-2026-6100.patch
 ssl-sni-crash.patch
 ssl-reference-leak.patch
+dict-use-after-free.patch
 traverse-managed-dicts.patch
 CVE-2026-1502.patch
 CVE-2026-3276.patch
diff -Nru python3.13-3.13.5/debian/patches/traverse-managed-dicts.patch 
python3.13-3.13.5/debian/patches/traverse-managed-dicts.patch
--- python3.13-3.13.5/debian/patches/traverse-managed-dicts.patch       
2026-06-13 11:18:01.000000000 -0300
+++ python3.13-3.13.5/debian/patches/traverse-managed-dicts.patch       
2026-07-15 17:25:40.000000000 -0300
@@ -50,7 +50,7 @@
 +overwritten at runtime.
 --- a/Objects/dictobject.c
 +++ b/Objects/dictobject.c
-@@ -4553,10 +4553,8 @@
+@@ -4566,10 +4566,8 @@
  
      if (DK_IS_UNICODE(keys)) {
          if (_PyDict_HasSplitTable(mp)) {
@@ -63,7 +63,7 @@
              }
          }
          else {
-@@ -7121,16 +7119,21 @@
+@@ -7134,16 +7132,21 @@
      if((tp->tp_flags & Py_TPFLAGS_MANAGED_DICT) == 0) {
          return 0;
      }

Reply via email to