Content-Length: 611201 | pFad | https://github.com/python/cpython/commit/ac1c1e7ef05a15fdabdf38d11cbfb02a93ad6cfa

47 [3.13] gh-148653: Fix some marshal errors related to recursive code o… · python/cpython@ac1c1e7 · GitHub
Skip to content

Commit ac1c1e7

Browse files
[3.13] gh-148653: Fix some marshal errors related to recursive code objects (GH-148698) (GH-148711) (GH-148713)
(cherry picked from commit d496c63) Forbid marshalling recursive code objects which cannot be correctly unmarshalled. Add multiple tests for recursive data structures. (cherry picked from commit 2e37d83)
1 parent 53e0725 commit ac1c1e7

File tree

3 files changed

+128
-7
lines changed

3 files changed

+128
-7
lines changed

Lib/test/test_marshal.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,96 @@ def test_recursion_limit(self):
310310
last.append([0])
311311
self.assertRaises(ValueError, marshal.dumps, head)
312312

313+
def test_reference_loop_list(self):
314+
a = []
315+
a.append(a)
316+
for v in range(3):
317+
self.assertRaises(ValueError, marshal.dumps, a, v)
318+
for v in range(3, marshal.version + 1):
319+
d = marshal.dumps(a, v)
320+
b = marshal.loads(d)
321+
self.assertIsInstance(b, list)
322+
self.assertIs(b[0], b)
323+
324+
def test_reference_loop_dict(self):
325+
a = {}
326+
a[None] = a
327+
for v in range(3):
328+
self.assertRaises(ValueError, marshal.dumps, a, v)
329+
for v in range(3, marshal.version + 1):
330+
d = marshal.dumps(a, v)
331+
b = marshal.loads(d)
332+
self.assertIsInstance(b, dict)
333+
self.assertIs(b[None], b)
334+
335+
def test_reference_loop_tuple(self):
336+
a = ([],)
337+
a[0].append(a)
338+
for v in range(3):
339+
self.assertRaises(ValueError, marshal.dumps, a, v)
340+
for v in range(3, marshal.version + 1):
341+
d = marshal.dumps(a, v)
342+
b = marshal.loads(d)
343+
self.assertIsInstance(b, tuple)
344+
self.assertIsInstance(b[0], list)
345+
self.assertIs(b[0][0], b)
346+
347+
def test_reference_loop_code(self):
348+
def f():
349+
return 1234.5
350+
code = f.__code__
351+
a = []
352+
code = code.replace(co_consts=code.co_consts + (a,))
353+
a.append(code)
354+
for v in range(marshal.version + 1):
355+
self.assertRaises(ValueError, marshal.dumps, code, v)
356+
357+
def test_loads_reference_loop_list(self):
358+
data = b'\xdb\x01\x00\x00\x00r\x00\x00\x00\x00' # [<R>]
359+
a = marshal.loads(data)
360+
self.assertIsInstance(a, list)
361+
self.assertIs(a[0], a)
362+
363+
def test_loads_reference_loop_dict(self):
364+
data = b'\xfbNr\x00\x00\x00\x000' # {None: <R>}
365+
a = marshal.loads(data)
366+
self.assertIsInstance(a, dict)
367+
self.assertIs(a[None], a)
368+
369+
def test_loads_abnormal_reference_loops(self):
370+
# Indirect self-references of tuples.
371+
data = b'\xa8\x01\x00\x00\x00[\x01\x00\x00\x00r\x00\x00\x00\x00' # ([<R>],)
372+
a = marshal.loads(data)
373+
self.assertIsInstance(a, tuple)
374+
self.assertIsInstance(a[0], list)
375+
self.assertIs(a[0][0], a)
376+
377+
data = b'\xa8\x01\x00\x00\x00{Nr\x00\x00\x00\x000' # ({None: <R>},)
378+
a = marshal.loads(data)
379+
self.assertIsInstance(a, tuple)
380+
self.assertIsInstance(a[0], dict)
381+
self.assertIs(a[0][None], a)
382+
383+
# Direct self-reference which cannot be created in Python.
384+
data = b'\xa8\x01\x00\x00\x00r\x00\x00\x00\x00' # (<R>,)
385+
a = marshal.loads(data)
386+
self.assertIsInstance(a, tuple)
387+
self.assertIs(a[0], a)
388+
389+
# Direct self-references which cannot be created in Python
390+
# because of unhashability.
391+
data = b'\xfbr\x00\x00\x00\x00N0' # {<R>: None}
392+
self.assertRaises(TypeError, marshal.loads, data)
393+
data = b'\xbc\x01\x00\x00\x00r\x00\x00\x00\x00' # {<R>}
394+
self.assertRaises(TypeError, marshal.loads, data)
395+
396+
for data in [
397+
# Direct self-references which cannot be created in Python.
398+
b'\xbe\x01\x00\x00\x00r\x00\x00\x00\x00', # frozenset({<R>})
399+
]:
400+
with self.subTest(data=data):
401+
self.assertRaises(ValueError, marshal.loads, data)
402+
313403
def test_exact_type_match(self):
314404
# Former bug:
315405
# >>> class Int(int): pass
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Forbid :mod:`marshalling <marshal>` recursive code objects
2+
which cannot be correctly unmarshalled.

Python/marshal.c

Lines changed: 36 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,6 @@ static int
310310
w_ref(PyObject *v, char *flag, WFILE *p)
311311
{
312312
_Py_hashtable_entry_t *entry;
313-
int w;
314313

315314
if (p->version < 3 || p->hashtable == NULL)
316315
return 0; /* not writing object references */
@@ -327,20 +326,28 @@ w_ref(PyObject *v, char *flag, WFILE *p)
327326
entry = _Py_hashtable_get_entry(p->hashtable, v);
328327
if (entry != NULL) {
329328
/* write the reference index to the stream */
330-
w = (int)(uintptr_t)entry->value;
329+
uintptr_t w = (uintptr_t)entry->value;
330+
if (w & 0x80000000LU) {
331+
PyErr_Format(PyExc_ValueError, "cannot marshal recursion %T objects", v);
332+
goto err;
333+
}
331334
/* we don't store "long" indices in the dict */
332-
assert(0 <= w && w <= 0x7fffffff);
335+
assert(w <= 0x7fffffff);
333336
w_byte(TYPE_REF, p);
334-
w_long(w, p);
337+
w_long((int)w, p);
335338
return 1;
336339
} else {
337-
size_t s = p->hashtable->nentries;
340+
size_t w = p->hashtable->nentries;
338341
/* we don't support long indices */
339-
if (s >= 0x7fffffff) {
342+
if (w >= 0x7fffffff) {
340343
PyErr_SetString(PyExc_ValueError, "too many objects");
341344
goto err;
342345
}
343-
w = (int)s;
346+
// Corresponding code should call w_complete() after
347+
// writing the object.
348+
if (PyCode_Check(v)) {
349+
w |= 0x80000000LU;
350+
}
344351
if (_Py_hashtable_set(p->hashtable, Py_NewRef(v),
345352
(void *)(uintptr_t)w) < 0) {
346353
Py_DECREF(v);
@@ -354,6 +361,27 @@ w_ref(PyObject *v, char *flag, WFILE *p)
354361
return 1;
355362
}
356363

364+
static void
365+
w_complete(PyObject *v, WFILE *p)
366+
{
367+
if (p->version < 3 || p->hashtable == NULL) {
368+
return;
369+
}
370+
if (Py_REFCNT(v) == 1) {
371+
return;
372+
}
373+
374+
_Py_hashtable_entry_t *entry = _Py_hashtable_get_entry(p->hashtable, v);
375+
if (entry == NULL) {
376+
return;
377+
}
378+
assert(entry != NULL);
379+
uintptr_t w = (uintptr_t)entry->value;
380+
assert(w & 0x80000000LU);
381+
w &= ~0x80000000LU;
382+
entry->value = (void *)(uintptr_t)w;
383+
}
384+
357385
static void
358386
w_complex_object(PyObject *v, char flag, WFILE *p);
359387

@@ -603,6 +631,7 @@ w_complex_object(PyObject *v, char flag, WFILE *p)
603631
w_object(co->co_linetable, p);
604632
w_object(co->co_exceptiontable, p);
605633
Py_DECREF(co_code);
634+
w_complete(v, p);
606635
}
607636
else if (PyObject_CheckBuffer(v)) {
608637
/* Write unknown bytes-like objects as a bytes object */

0 commit comments

Comments
 (0)








ApplySandwichStrip

pFad - (p)hone/(F)rame/(a)nonymizer/(d)eclutterfier!      Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

Fetched URL: https://github.com/python/cpython/commit/ac1c1e7ef05a15fdabdf38d11cbfb02a93ad6cfa

Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy