Skip to content

Commit e1d969a

Browse files
serhiy-storchakamjbommarclaude
authored
gh-148653: Forbid marshalling recursive tuples (GH-155903)
Also fix a crash when unmarshalling a self-referencing tuple. Co-authored-by: Michael Bommarito <michael.bommarito@gmail.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d94c4ad commit e1d969a

3 files changed

Lines changed: 36 additions & 30 deletions

File tree

Lib/test/test_marshal.py

Lines changed: 26 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -342,14 +342,26 @@ def test_reference_loop_dict(self):
342342
def test_reference_loop_tuple(self):
343343
a = ([],)
344344
a[0].append(a)
345-
for v in range(3):
345+
for v in range(marshal.version + 1):
346346
self.assertRaises(ValueError, marshal.dumps, a, v)
347+
348+
a = ({},)
349+
a[0][None] = a
350+
for v in range(marshal.version + 1):
351+
self.assertRaises(ValueError, marshal.dumps, a, v)
352+
353+
def test_shared_reference_tuple(self):
354+
# A tuple referenced more than once still round-trips with the
355+
# shared identity preserved.
356+
a = (1, 2)
347357
for v in range(3, marshal.version + 1):
348-
d = marshal.dumps(a, v)
349-
b = marshal.loads(d)
350-
self.assertIsInstance(b, tuple)
351-
self.assertIsInstance(b[0], list)
352-
self.assertIs(b[0][0], b)
358+
b = marshal.loads(marshal.dumps([a, a], v))
359+
self.assertEqual(b[0], a)
360+
self.assertIs(b[0], b[1])
361+
big = tuple(range(300)) # too large for TYPE_SMALL_TUPLE
362+
b = marshal.loads(marshal.dumps([big, big]))
363+
self.assertEqual(b[0], big)
364+
self.assertIs(b[0], b[1])
353365

354366
def test_reference_loop_code(self):
355367
def f():
@@ -409,27 +421,6 @@ def test_loads_reference_loop_dict(self):
409421
self.assertIs(a[None], a)
410422

411423
def test_loads_abnormal_reference_loops(self):
412-
# Indirect self-references of tuples.
413-
data = b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00' # ([<R>],)
414-
a = marshal.loads(data)
415-
self.assertIsInstance(a, tuple)
416-
self.assertIsInstance(a[0], list)
417-
self.assertIs(a[0][0], a)
418-
419-
data = b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000' # ({None: <R>},)
420-
a = marshal.loads(data)
421-
self.assertIsInstance(a, tuple)
422-
self.assertIsInstance(a[0], dict)
423-
self.assertIs(a[0][None], a)
424-
425-
# Direct self-reference which cannot be created in Python.
426-
# This creates a reference loop which cannot be collected.
427-
if False:
428-
data = b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00' # (<R>,)
429-
a = marshal.loads(data)
430-
self.assertIsInstance(a, tuple)
431-
self.assertIs(a[0], a)
432-
433424
# Direct self-references which cannot be created in Python
434425
# because of unhashability.
435426
data = b'\xfbr\x00\x00\x00\x00N0' # {<R>: None}
@@ -439,6 +430,8 @@ def test_loads_abnormal_reference_loops(self):
439430

440431
for data in [
441432
# Indirect self-references of immutable objects.
433+
b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00', # ([<R>],)
434+
b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000', # ({None: <R>},)
442435
b'\xba[\x01\x00\x00\x00r\x00\x00\x00\x00NN', # slice([<R>], None)
443436
b'\xbaN[\x01\x00\x00\x00r\x00\x00\x00\x00N', # slice(None, [<R>])
444437
b'\xbaNN[\x01\x00\x00\x00r\x00\x00\x00\x00', # slice(None, None, [<R>])
@@ -449,12 +442,18 @@ def test_loads_abnormal_reference_loops(self):
449442
b'\xfdN{Nr\x00\x00\x00\x0000', # frozendict({None: {None: <R>})
450443

451444
# Direct self-references which cannot be created in Python.
445+
b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00', # (<R>,)
452446
b'\xbe\x01\x00\x00\x00r\x00\x00\x00\x00', # frozenset({<R>})
453447
b'\xfdNr\x00\x00\x00\x000', # frozendict({None: <R>})
454448
b'\xfdr\x00\x00\x00\x00N0', # frozendict({<R>: None})
455449
b'\xbar\x00\x00\x00\x00NN', # slice(<R>, None)
456450
b'\xbaNr\x00\x00\x00\x00N', # slice(None, <R>)
457451
b'\xbaNNr\x00\x00\x00\x00', # slice(None, None, <R>)
452+
453+
# Indirect self-references which cannot be created in Python
454+
# because of unhashability.
455+
b'\xa8\x01\x00\x00\x00{r\x00\x00\x00\x00N0', # ({<R>: None},)
456+
b'\xa8\x01\x00\x00\x00<\x01\x00\x00\x00r\x00\x00\x00\x00', # ({<R>},)
458457
]:
459458
with self.subTest(data=data):
460459
self.assertRaises(ValueError, marshal.loads, data)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Forbid :mod:`marshalling <marshal>` recursive tuples, and fix a crash when
2+
unmarshalling a self-referencing tuple.

Python/marshal.c

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,9 @@ w_ref(PyObject *v, char *flag, WFILE *p)
417417
}
418418
// Corresponding code should call w_complete() after
419419
// writing the object.
420-
if (PyCode_Check(v) || PySlice_Check(v) || PyFrozenDict_CheckExact(v)) {
420+
if (PyTuple_CheckExact(v) || PyCode_Check(v) || PySlice_Check(v) ||
421+
PyFrozenDict_CheckExact(v))
422+
{
421423
w |= 0x80000000LU;
422424
}
423425
if (_Py_hashtable_set(p->hashtable, Py_NewRef(v),
@@ -596,6 +598,7 @@ w_complex_object(PyObject *v, char flag, WFILE *p)
596598
for (i = 0; i < n; i++) {
597599
w_object(PyTuple_GET_ITEM(v, i), p);
598600
}
601+
w_complete(v, p);
599602
}
600603
else if (PyList_CheckExact(v)) {
601604
W_TYPE(TYPE_LIST, p);
@@ -1417,8 +1420,10 @@ r_object(RFILE *p)
14171420
break;
14181421
}
14191422
_read_tuple:
1423+
idx = r_ref_reserve(flag, p);
1424+
if (idx < 0)
1425+
break;
14201426
v = PyTuple_New(n);
1421-
R_REF(v);
14221427
if (v == NULL)
14231428
break;
14241429

@@ -1433,7 +1438,7 @@ r_object(RFILE *p)
14331438
}
14341439
PyTuple_SET_ITEM(v, i, v2);
14351440
}
1436-
retval = v;
1441+
retval = r_ref_insert(v, idx, flag, p);
14371442
break;
14381443

14391444
case TYPE_LIST:

0 commit comments

Comments
 (0)