Commit 82be19f8 authored by Antoine Pitrou's avatar Antoine Pitrou

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.
parent aa26b275
...@@ -2,10 +2,14 @@ import io ...@@ -2,10 +2,14 @@ import io
import unittest import unittest
import pickle import pickle
import pickletools import pickletools
import sys
import copyreg import copyreg
from http.cookies import SimpleCookie from http.cookies import SimpleCookie
from test.support import TestFailed, TESTFN, run_with_locale from test.support import (
TestFailed, TESTFN, run_with_locale,
_2G, _4G, precisionbigmemtest,
)
from pickle import bytes_types from pickle import bytes_types
...@@ -14,6 +18,8 @@ from pickle import bytes_types ...@@ -14,6 +18,8 @@ from pickle import bytes_types
# kind of outer loop. # kind of outer loop.
protocols = range(pickle.HIGHEST_PROTOCOL + 1) protocols = range(pickle.HIGHEST_PROTOCOL + 1)
character_size = 4 if sys.maxunicode > 0xFFFF else 2
# Return True if opcode code appears in the pickle, else False. # Return True if opcode code appears in the pickle, else False.
def opcode_in_pickle(code, pickle): def opcode_in_pickle(code, pickle):
...@@ -1098,6 +1104,100 @@ class AbstractPickleTests(unittest.TestCase): ...@@ -1098,6 +1104,100 @@ class AbstractPickleTests(unittest.TestCase):
empty = self.loads(b'\x80\x03U\x00q\x00.', encoding='koi8-r') empty = self.loads(b'\x80\x03U\x00q\x00.', encoding='koi8-r')
self.assertEqual(empty, '') self.assertEqual(empty, '')
def check_negative_32b_binXXX(self, dumped):
if sys.maxsize > 2**32:
self.skipTest("test is only meaningful on 32-bit builds")
# XXX Pure Python pickle reads lengths as signed and passes
# them directly to read() (hence the EOFError)
with self.assertRaises((pickle.UnpicklingError, EOFError,
ValueError, OverflowError)):
self.loads(dumped)
def test_negative_32b_binbytes(self):
# On 32-bit builds, a BINBYTES of 2**31 or more is refused
self.check_negative_32b_binXXX(b'\x80\x03B\xff\xff\xff\xffxyzq\x00.')
def test_negative_32b_binunicode(self):
# On 32-bit builds, a BINUNICODE of 2**31 or more is refused
self.check_negative_32b_binXXX(b'\x80\x03X\xff\xff\xff\xffxyzq\x00.')
class BigmemPickleTests(unittest.TestCase):
# Binary protocols can serialize longs of up to 2GB-1
@precisionbigmemtest(size=_2G, memuse=1 + 1, dry_run=False)
def test_huge_long_32b(self, size):
data = 1 << (8 * size)
try:
for proto in protocols:
if proto < 2:
continue
with self.assertRaises((ValueError, OverflowError)):
self.dumps(data, protocol=proto)
finally:
data = None
# Protocol 3 can serialize up to 4GB-1 as a bytes object
# (older protocols don't have a dedicated opcode for bytes and are
# too inefficient)
@precisionbigmemtest(size=_2G, memuse=1 + 1, dry_run=False)
def test_huge_bytes_32b(self, size):
data = b"abcd" * (size // 4)
try:
for proto in protocols:
if proto < 3:
continue
try:
pickled = self.dumps(data, protocol=proto)
self.assertTrue(b"abcd" in pickled[:15])
self.assertTrue(b"abcd" in pickled[-15:])
finally:
pickled = None
finally:
data = None
@precisionbigmemtest(size=_4G, memuse=1 + 1, dry_run=False)
def test_huge_bytes_64b(self, size):
data = b"a" * size
try:
for proto in protocols:
if proto < 3:
continue
with self.assertRaises((ValueError, OverflowError)):
self.dumps(data, protocol=proto)
finally:
data = None
# All protocols use 1-byte per printable ASCII character; we add another
# byte because the encoded form has to be copied into the internal buffer.
@precisionbigmemtest(size=_2G, memuse=2 + character_size, dry_run=False)
def test_huge_str_32b(self, size):
data = "abcd" * (size // 4)
try:
for proto in protocols:
try:
pickled = self.dumps(data, protocol=proto)
self.assertTrue(b"abcd" in pickled[:15])
self.assertTrue(b"abcd" in pickled[-15:])
finally:
pickled = None
finally:
data = None
@precisionbigmemtest(size=_4G, memuse=1 + character_size, dry_run=False)
def test_huge_str_64b(self, size):
data = "a" * size
try:
for proto in protocols:
with self.assertRaises((ValueError, OverflowError)):
self.dumps(data, protocol=proto)
finally:
data = None
# Test classes for reduce_ex # Test classes for reduce_ex
class REX_one(object): class REX_one(object):
......
...@@ -1089,7 +1089,7 @@ def bigmemtest(minsize, memuse): ...@@ -1089,7 +1089,7 @@ def bigmemtest(minsize, memuse):
return wrapper return wrapper
return decorator return decorator
def precisionbigmemtest(size, memuse): def precisionbigmemtest(size, memuse, dry_run=True):
def decorator(f): def decorator(f):
def wrapper(self): def wrapper(self):
size = wrapper.size size = wrapper.size
...@@ -1099,10 +1099,11 @@ def precisionbigmemtest(size, memuse): ...@@ -1099,10 +1099,11 @@ def precisionbigmemtest(size, memuse):
else: else:
maxsize = size maxsize = size
if real_max_memuse and real_max_memuse < maxsize * memuse: if ((real_max_memuse or not dry_run)
raise unittest.SkipTest( and real_max_memuse < maxsize * memuse):
"not enough memory: %.1fG minimum needed" raise unittest.SkipTest(
% (size * memuse / (1024 ** 3))) "not enough memory: %.1fG minimum needed"
% (size * memuse / (1024 ** 3)))
return f(self, maxsize) return f(self, maxsize)
wrapper.size = size wrapper.size = size
......
...@@ -7,6 +7,7 @@ from test.pickletester import AbstractPickleTests ...@@ -7,6 +7,7 @@ from test.pickletester import AbstractPickleTests
from test.pickletester import AbstractPickleModuleTests from test.pickletester import AbstractPickleModuleTests
from test.pickletester import AbstractPersistentPicklerTests from test.pickletester import AbstractPersistentPicklerTests
from test.pickletester import AbstractPicklerUnpicklerObjectTests from test.pickletester import AbstractPicklerUnpicklerObjectTests
from test.pickletester import BigmemPickleTests
try: try:
import _pickle import _pickle
...@@ -37,13 +38,13 @@ class PyPicklerTests(AbstractPickleTests): ...@@ -37,13 +38,13 @@ class PyPicklerTests(AbstractPickleTests):
return u.load() return u.load()
class InMemoryPickleTests(AbstractPickleTests): class InMemoryPickleTests(AbstractPickleTests, BigmemPickleTests):
pickler = pickle._Pickler pickler = pickle._Pickler
unpickler = pickle._Unpickler unpickler = pickle._Unpickler
def dumps(self, arg, proto=None): def dumps(self, arg, protocol=None):
return pickle.dumps(arg, proto) return pickle.dumps(arg, protocol)
def loads(self, buf, **kwds): def loads(self, buf, **kwds):
return pickle.loads(buf, **kwds) return pickle.loads(buf, **kwds)
......
...@@ -22,6 +22,9 @@ Core and Builtins ...@@ -22,6 +22,9 @@ Core and Builtins
Library Library
------- -------
- 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.
- Issue #12287: Fix a stack corruption in ossaudiodev module when the FD is - Issue #12287: Fix a stack corruption in ossaudiodev module when the FD is
greater than FD_SETSIZE. greater than FD_SETSIZE.
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment