marshal.loads() deterministically segfaults on a 16-byte structural
payload containing a TYPE_TUPLE | FLAG_REF whose elements include a
TYPE_REF back to the partial tuple itself.
The root cause is that R_REF() registers the tuple in p->refs
before its slots are populated. A nested TYPE_REF back-reference
then yields the partial tuple to a hashing site (PySet_Add), and
tuplehash calls PyObject_Hash(NULL) on the unfilled slot.
TYPE_FROZENSET, TYPE_CODE, and TYPE_SLICE already use the correct
two-phase pattern (r_ref_reserve / r_ref_insert) that avoids this.
TYPE_TUPLE, TYPE_LIST, TYPE_DICT, and TYPE_SET do not.
Originally filed as GHSA-m7gv-g5p9-9qqq. PSRT assessed this as outside
the security threat model since marshal.loads is documented as not secure
against malicious data. Converting to a public bug per their guidance.
I have found other bugs in marshal. For example, a reference loop containing slice can be successfully marshalled, but the produced data cannot be unmarshalled.
>>> import marshal
>>> a =slice([])
>>> a.stop.append(a)
>>> a
slice(None, [slice(None, [...], None)], None)
>>> marshal.dumps(a, 5)
b'\xbaN[\x01\x00\x00\x00r\x00\x00\x00\x00N'
>>> marshal.loads(marshal.dumps(a, 5))
Traceback (most recent call last):
File "<python-input-5>", line 1, in <module>
marshal.loads(marshal.dumps(a, 5))
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^
ValueError: bad marshal data (invalid reference)
This issue is larger than anticipated, so I'll work on it.
I think this is unfortunately really a graph problem where we would ideally have a fast and easy case for acyclic digraphs and a safer but slower case for cycles. I am only beginning to understand the problem fully compared to you, but do you think a cycle check can be accurately implemented with low overhead?
Hi @serhiy-storchaka , I was just about to hit send on the experimental branch in #148700 when you sent this. Coincidence!
Please let me know what you think of the general design. I am 99% sure I can erase the performance regression at this point but still iterating on the ceiling.
If all goes well, we'll follow the original plan and break that damn recursion. I anyone came with really good arguments (unlikely), we will need more complex solution.
Due to lack of response we can suppose that that not many will regret breaking that case. But it would be safer to not backport this change to 3.13 and 3.14.
Activity
Add NEWS blurb for pythongh-148653
pythongh-148653: Fix SIGSEGV in marshal.loads for self-referencing tu…
serhiy-storchaka commented on Apr 16, 2026
With PR #148652 it fails.
serhiy-storchaka commented on Apr 17, 2026
I have found other bugs in marshal. For example, a reference loop containing slice can be successfully marshalled, but the produced data cannot be unmarshalled.
This issue is larger than anticipated, so I'll work on it.
serhiy-storchaka commented on Apr 17, 2026
The problem with frozendict:
(note that the nested dict is not a frozendict).
mjbommar commented on Apr 17, 2026
You can see my other comments on #148652:
#148652 (comment)
I think this is unfortunately really a graph problem where we would ideally have a fast and easy case for acyclic digraphs and a safer but slower case for cycles. I am only beginning to understand the problem fully compared to you, but do you think a cycle check can be accurately implemented with low overhead?
pythongh-148653: Fix some marshal errors related to recursive immutab…
serhiy-storchaka commented on Apr 17, 2026
#148698 adds multiple tests and fixes several bugs, except the original bug, with recursive tuples. After merging it we will continue with tuples.
mjbommar commented on Apr 17, 2026
Hi @serhiy-storchaka , I was just about to hit send on the experimental branch in #148700 when you sent this. Coincidence!
Please let me know what you think of the general design. I am 99% sure I can erase the performance regression at this point but still iterating on the ceiling.
Add Misc/perf-work-summary.md — outcome record for the three shipped …
gh-148653: Fix some marshal errors related to recursive immutable obj…
[3.14] pythongh-148653: Fix some marshal errors related to recursive …
7 remaining items
pythongh-148653: Fix reference leaks in test_marshal introduced in py…
[3.13] gh-148653: Fix reference leaks in test_marshal introduced in g…
[3.14] gh-148653: Fix reference leaks in test_marshal introduced in g…
pythongh-148653: Fix SIGSEGV in marshal.loads for self-referencing tu…
pythongh-148653: Fix some marshal errors related to recursive immutab…
pythongh-148653: Fix reference leaks in test_marshal introduced in py…
serhiy-storchaka commented on May 4, 2026
Opened discussion https://discuss.python.org/t/recursive-structures-in-marshal/107172.
If all goes well, we'll follow the original plan and break that damn recursion. I anyone came with really good arguments (unlikely), we will need more complex solution.
serhiy-storchaka commented on May 11, 2026
Due to lack of response we can suppose that that not many will regret breaking that case. But it would be safer to not backport this change to 3.13 and 3.14.
serhiy-storchaka commented on Aug 16, 2026
Too late for 3.15 -- #155903 will fix 3.16.
gh-148653: Forbid marshalling recursive tuples (GH-155903)
pythongh-148653: Forbid marshalling recursive tuples (pythonGH-155903)