Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 82be19f

Browse files
committed
Issue #11564: Avoid crashes when trying to pickle huge objects or containers
(more than 2**31 items). Instead, in most cases, an OverflowError is raised.
1 parent aa26b27 commit 82be19f

5 files changed

Lines changed: 217 additions & 81 deletions

File tree

Lib/test/pickletester.py

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@
22
import unittest
33
import pickle
44
import pickletools
5+
import sys
56
import copyreg
67
from http.cookies import SimpleCookie
78

8-
from test.support import TestFailed, TESTFN, run_with_locale
9+
from test.support import (
10+
TestFailed, TESTFN, run_with_locale,
11+
_2G, _4G, precisionbigmemtest,
12+
)
913

1014
from pickle import bytes_types
1115

@@ -14,6 +18,8 @@
1418
# kind of outer loop.
1519
protocols = range(pickle.HIGHEST_PROTOCOL + 1)
1620

21+
character_size = 4 if sys.maxunicode > 0xFFFF else 2
22+
1723

1824
# Return True if opcode code appears in the pickle, else False.
1925
def opcode_in_pickle(code, pickle):
@@ -1098,6 +1104,100 @@ def test_empty_bytestring(self):
10981104
empty = self.loads(b'\x80\x03U\x00q\x00.', encoding='koi8-r')
10991105
self.assertEqual(empty, '')
11001106

1107+
def check_negative_32b_binXXX(self, dumped):
1108+
if sys.maxsize > 2**32:
1109+
self.skipTest("test is only meaningful on 32-bit builds")
1110+
# XXX Pure Python pickle reads lengths as signed and passes
1111+
# them directly to read() (hence the EOFError)
1112+
with self.assertRaises((pickle.UnpicklingError, EOFError,
1113+
ValueError, OverflowError)):
1114+
self.loads(dumped)
1115+
1116+
def test_negative_32b_binbytes(self):
1117+
# On 32-bit builds, a BINBYTES of 2**31 or more is refused
1118+
self.check_negative_32b_binXXX(b'\x80\x03B\xff\xff\xff\xffxyzq\x00.')
1119+
1120+
def test_negative_32b_binunicode(self):
1121+
# On 32-bit builds, a BINUNICODE of 2**31 or more is refused
1122+
self.check_negative_32b_binXXX(b'\x80\x03X\xff\xff\xff\xffxyzq\x00.')
1123+
1124+
1125+
class BigmemPickleTests(unittest.TestCase):
1126+
1127+
# Binary protocols can serialize longs of up to 2GB-1
1128+
1129+
@precisionbigmemtest(size=_2G, memuse=1 + 1, dry_run=False)
1130+
def test_huge_long_32b(self, size):
1131+
data = 1 << (8 * size)
1132+
try:
1133+
for proto in protocols:
1134+
if proto < 2:
1135+
continue
1136+
with self.assertRaises((ValueError, OverflowError)):
1137+
self.dumps(data, protocol=proto)
1138+
finally:
1139+
data = None
1140+
1141+
# Protocol 3 can serialize up to 4GB-1 as a bytes object
1142+
# (older protocols don't have a dedicated opcode for bytes and are
1143+
# too inefficient)
1144+
1145+
@precisionbigmemtest(size=_2G, memuse=1 + 1, dry_run=False)
1146+
def test_huge_bytes_32b(self, size):
1147+
data = b"abcd" * (size // 4)
1148+
try:
1149+
for proto in protocols:
1150+
if proto < 3:
1151+
continue
1152+
try:
1153+
pickled = self.dumps(data, protocol=proto)
1154+
self.assertTrue(b"abcd" in pickled[:15])
1155+
self.assertTrue(b"abcd" in pickled[-15:])
1156+
finally:
1157+
pickled = None
1158+
finally:
1159+
data = None
1160+
1161+
@precisionbigmemtest(size=_4G, memuse=1 + 1, dry_run=False)
1162+
def test_huge_bytes_64b(self, size):
1163+
data = b"a" * size
1164+
try:
1165+
for proto in protocols:
1166+
if proto < 3:
1167+
continue
1168+
with self.assertRaises((ValueError, OverflowError)):
1169+
self.dumps(data, protocol=proto)
1170+
finally:
1171+
data = None
1172+
1173+
# All protocols use 1-byte per printable ASCII character; we add another
1174+
# byte because the encoded form has to be copied into the internal buffer.
1175+
1176+
@precisionbigmemtest(size=_2G, memuse=2 + character_size, dry_run=False)
1177+
def test_huge_str_32b(self, size):
1178+
data = "abcd" * (size // 4)
1179+
try:
1180+
for proto in protocols:
1181+
try:
1182+
pickled = self.dumps(data, protocol=proto)
1183+
self.assertTrue(b"abcd" in pickled[:15])
1184+
self.assertTrue(b"abcd" in pickled[-15:])
1185+
finally:
1186+
pickled = None
1187+
finally:
1188+
data = None
1189+
1190+
@precisionbigmemtest(size=_4G, memuse=1 + character_size, dry_run=False)
1191+
def test_huge_str_64b(self, size):
1192+
data = "a" * size
1193+
try:
1194+
for proto in protocols:
1195+
with self.assertRaises((ValueError, OverflowError)):
1196+
self.dumps(data, protocol=proto)
1197+
finally:
1198+
data = None
1199+
1200+
11011201
# Test classes for reduce_ex
11021202

11031203
class REX_one(object):

Lib/test/support.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,7 +1089,7 @@ def wrapper(self):
10891089
return wrapper
10901090
return decorator
10911091

1092-
def precisionbigmemtest(size, memuse):
1092+
def precisionbigmemtest(size, memuse, dry_run=True):
10931093
def decorator(f):
10941094
def wrapper(self):
10951095
size = wrapper.size
@@ -1099,10 +1099,11 @@ def wrapper(self):
10991099
else:
11001100
maxsize = size
11011101

1102-
if real_max_memuse and real_max_memuse < maxsize * memuse:
1103-
raise unittest.SkipTest(
1104-
"not enough memory: %.1fG minimum needed"
1105-
% (size * memuse / (1024 ** 3)))
1102+
if ((real_max_memuse or not dry_run)
1103+
and real_max_memuse < maxsize * memuse):
1104+
raise unittest.SkipTest(
1105+
"not enough memory: %.1fG minimum needed"
1106+
% (size * memuse / (1024 ** 3)))
11061107

11071108
return f(self, maxsize)
11081109
wrapper.size = size

Lib/test/test_pickle.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from test.pickletester import AbstractPickleModuleTests
88
from test.pickletester import AbstractPersistentPicklerTests
99
from test.pickletester import AbstractPicklerUnpicklerObjectTests
10+
from test.pickletester import BigmemPickleTests
1011

1112
try:
1213
import _pickle
@@ -37,13 +38,13 @@ def loads(self, buf, **kwds):
3738
return u.load()
3839

3940

40-
class InMemoryPickleTests(AbstractPickleTests):
41+
class InMemoryPickleTests(AbstractPickleTests, BigmemPickleTests):
4142

4243
pickler = pickle._Pickler
4344
unpickler = pickle._Unpickler
4445

45-
def dumps(self, arg, proto=None):
46-
return pickle.dumps(arg, proto)
46+
def dumps(self, arg, protocol=None):
47+
return pickle.dumps(arg, protocol)
4748

4849
def loads(self, buf, **kwds):
4950
return pickle.loads(buf, **kwds)

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ Core and Builtins
2222
Library
2323
-------
2424

25+
- Issue #11564: Avoid crashes when trying to pickle huge objects or containers
26+
(more than 2**31 items). Instead, in most cases, an OverflowError is raised.
27+
2528
- Issue #12287: Fix a stack corruption in ossaudiodev module when the FD is
2629
greater than FD_SETSIZE.
2730

0 commit comments

Comments
 (0)