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; }

