Skip to content

Commit 58e01a1

Browse files
[3.12] pythongh-123431: Harmonize extension code checks in pickle (pythonGH-123434) (python#123460)
This checks are redundant in normal circumstances and can only work if the extension registry was intentionally broken. (cherry picked from commit 0c3ea30) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
1 parent 10adcb4 commit 58e01a1

File tree

3 files changed

+72
-23
lines changed

3 files changed

+72
-23
lines changed

Lib/pickle.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ def decode_long(data):
397397
return int.from_bytes(data, byteorder='little', signed=True)
398398

399399

400+
_NoValue = object()
401+
400402
# Pickling machinery
401403

402404
class _Pickler:
@@ -1091,11 +1093,16 @@ def save_global(self, obj, name=None):
10911093
(obj, module_name, name))
10921094

10931095
if self.proto >= 2:
1094-
code = _extension_registry.get((module_name, name))
1095-
if code:
1096-
assert code > 0
1096+
code = _extension_registry.get((module_name, name), _NoValue)
1097+
if code is not _NoValue:
10971098
if code <= 0xff:
1098-
write(EXT1 + pack("<B", code))
1099+
data = pack("<B", code)
1100+
if data == b'\0':
1101+
# Should never happen in normal circumstances,
1102+
# since the type and the value of the code are
1103+
# checked in copyreg.add_extension().
1104+
raise RuntimeError("extension code 0 is out of range")
1105+
write(EXT1 + data)
10991106
elif code <= 0xffff:
11001107
write(EXT2 + pack("<H", code))
11011108
else:
@@ -1589,9 +1596,8 @@ def load_ext4(self):
15891596
dispatch[EXT4[0]] = load_ext4
15901597

15911598
def get_extension(self, code):
1592-
nil = []
1593-
obj = _extension_cache.get(code, nil)
1594-
if obj is not nil:
1599+
obj = _extension_cache.get(code, _NoValue)
1600+
if obj is not _NoValue:
15951601
self.append(obj)
15961602
return
15971603
key = _inverted_registry.get(code)

Lib/test/pickletester.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,6 +1297,35 @@ def find_class(module_name, global_name):
12971297
self.assertEqual(loads(b'cmath\nlog\n.'), ('math', 'log'))
12981298
self.assertEqual(loads(b'\x8c\x04math\x8c\x03log\x93.'), ('math', 'log'))
12991299

1300+
def test_bad_ext_code(self):
1301+
# unregistered extension code
1302+
self.check_unpickling_error(ValueError, b'\x82\x01.')
1303+
self.check_unpickling_error(ValueError, b'\x82\xff.')
1304+
self.check_unpickling_error(ValueError, b'\x83\x01\x00.')
1305+
self.check_unpickling_error(ValueError, b'\x83\xff\xff.')
1306+
self.check_unpickling_error(ValueError, b'\x84\x01\x00\x00\x00.')
1307+
self.check_unpickling_error(ValueError, b'\x84\xff\xff\xff\x7f.')
1308+
# EXT specifies code <= 0
1309+
self.check_unpickling_error(pickle.UnpicklingError, b'\x82\x00.')
1310+
self.check_unpickling_error(pickle.UnpicklingError, b'\x83\x00\x00.')
1311+
self.check_unpickling_error(pickle.UnpicklingError, b'\x84\x00\x00\x00\x00.')
1312+
self.check_unpickling_error(pickle.UnpicklingError, b'\x84\x00\x00\x00\x80.')
1313+
self.check_unpickling_error(pickle.UnpicklingError, b'\x84\xff\xff\xff\xff.')
1314+
1315+
@support.cpython_only
1316+
def test_bad_ext_inverted_registry(self):
1317+
code = 1
1318+
def check(key, exc):
1319+
with support.swap_item(copyreg._inverted_registry, code, key):
1320+
with self.assertRaises(exc):
1321+
self.loads(b'\x82\x01.')
1322+
check(None, ValueError)
1323+
check((), ValueError)
1324+
check((__name__,), (TypeError, ValueError))
1325+
check((__name__, "MyList", "x"), (TypeError, ValueError))
1326+
check((__name__, None), (TypeError, ValueError))
1327+
check((None, "MyList"), (TypeError, ValueError))
1328+
13001329
def test_bad_reduce(self):
13011330
self.assertEqual(self.loads(b'cbuiltins\nint\n)R.'), 0)
13021331
self.check_unpickling_error(TypeError, b'N)R.')
@@ -2033,6 +2062,28 @@ def persistent_id(self, obj):
20332062
check({Clearer(): 1, Clearer(): 2})
20342063
check({1: Clearer(), 2: Clearer()})
20352064

2065+
@support.cpython_only
2066+
def test_bad_ext_code(self):
2067+
# This should never happen in normal circumstances, because the type
2068+
# and the value of the extesion code is checked in copyreg.add_extension().
2069+
key = (__name__, 'MyList')
2070+
def check(code, exc):
2071+
assert key not in copyreg._extension_registry
2072+
assert code not in copyreg._inverted_registry
2073+
with (support.swap_item(copyreg._extension_registry, key, code),
2074+
support.swap_item(copyreg._inverted_registry, code, key)):
2075+
for proto in protocols[2:]:
2076+
with self.assertRaises(exc):
2077+
self.dumps(MyList, proto)
2078+
2079+
check(object(), TypeError)
2080+
check(None, TypeError)
2081+
check(-1, (RuntimeError, struct.error))
2082+
check(0, RuntimeError)
2083+
check(2**31, (RuntimeError, OverflowError, struct.error))
2084+
check(2**1000, (OverflowError, struct.error))
2085+
check(-2**1000, (OverflowError, struct.error))
2086+
20362087

20372088
class AbstractPickleTests:
20382089
# Subclass must define self.dumps, self.loads.

Modules/_pickle.c

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3725,31 +3725,23 @@ save_global(PickleState *st, PicklerObject *self, PyObject *obj,
37253725
code_obj = PyDict_GetItemWithError(st->extension_registry,
37263726
extension_key);
37273727
Py_DECREF(extension_key);
3728-
/* The object is not registered in the extension registry.
3729-
This is the most likely code path. */
37303728
if (code_obj == NULL) {
37313729
if (PyErr_Occurred()) {
37323730
goto error;
37333731
}
3732+
/* The object is not registered in the extension registry.
3733+
This is the most likely code path. */
37343734
goto gen_global;
37353735
}
37363736

3737-
/* XXX: pickle.py doesn't check neither the type, nor the range
3738-
of the value returned by the extension_registry. It should for
3739-
consistency. */
3740-
3741-
/* Verify code_obj has the right type and value. */
3742-
if (!PyLong_Check(code_obj)) {
3743-
PyErr_Format(st->PicklingError,
3744-
"Can't pickle %R: extension code %R isn't an integer",
3745-
obj, code_obj);
3746-
goto error;
3747-
}
3748-
code = PyLong_AS_LONG(code_obj);
3737+
Py_INCREF(code_obj);
3738+
code = PyLong_AsLong(code_obj);
3739+
Py_DECREF(code_obj);
37493740
if (code <= 0 || code > 0x7fffffffL) {
3741+
/* Should never happen in normal circumstances, since the type and
3742+
the value of the code are checked in copyreg.add_extension(). */
37503743
if (!PyErr_Occurred())
3751-
PyErr_Format(st->PicklingError, "Can't pickle %R: extension "
3752-
"code %ld is out of range", obj, code);
3744+
PyErr_Format(PyExc_RuntimeError, "extension code %ld is out of range", code);
37533745
goto error;
37543746
}
37553747

0 commit comments

Comments
 (0)