mirror of
https://github.com/AdaCore/cpython.git
synced 2026-02-12 12:57:15 -08:00
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.
This commit is contained in:
@@ -2,11 +2,15 @@ import io
|
||||
import unittest
|
||||
import pickle
|
||||
import pickletools
|
||||
import sys
|
||||
import copyreg
|
||||
import weakref
|
||||
from http.cookies import SimpleCookie
|
||||
|
||||
from test.support import TestFailed, TESTFN, run_with_locale, no_tracing
|
||||
from test.support import (
|
||||
TestFailed, TESTFN, run_with_locale, no_tracing,
|
||||
_2G, _4G, precisionbigmemtest,
|
||||
)
|
||||
|
||||
from pickle import bytes_types
|
||||
|
||||
@@ -15,6 +19,8 @@ from pickle import bytes_types
|
||||
# kind of outer loop.
|
||||
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.
|
||||
def opcode_in_pickle(code, pickle):
|
||||
@@ -1127,6 +1133,99 @@ class AbstractPickleTests(unittest.TestCase):
|
||||
if proto >= 2:
|
||||
self.assertLessEqual(sizes[-1], 14)
|
||||
|
||||
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
|
||||
|
||||
|
||||
@@ -1142,7 +1142,7 @@ def bigmemtest(minsize, memuse):
|
||||
return wrapper
|
||||
return decorator
|
||||
|
||||
def precisionbigmemtest(size, memuse):
|
||||
def precisionbigmemtest(size, memuse, dry_run=True):
|
||||
"""Decorator for bigmem tests that need exact sizes.
|
||||
|
||||
Like bigmemtest, but without the size scaling upward to fill available
|
||||
@@ -1157,10 +1157,11 @@ def precisionbigmemtest(size, memuse):
|
||||
else:
|
||||
maxsize = size
|
||||
|
||||
if real_max_memuse and real_max_memuse < maxsize * memuse:
|
||||
raise unittest.SkipTest(
|
||||
"not enough memory: %.1fG minimum needed"
|
||||
% (size * memuse / (1024 ** 3)))
|
||||
if ((real_max_memuse or not dry_run)
|
||||
and real_max_memuse < maxsize * memuse):
|
||||
raise unittest.SkipTest(
|
||||
"not enough memory: %.1fG minimum needed"
|
||||
% (size * memuse / (1024 ** 3)))
|
||||
|
||||
return f(self, maxsize)
|
||||
wrapper.size = size
|
||||
|
||||
@@ -7,6 +7,7 @@ from test.pickletester import AbstractPickleTests
|
||||
from test.pickletester import AbstractPickleModuleTests
|
||||
from test.pickletester import AbstractPersistentPicklerTests
|
||||
from test.pickletester import AbstractPicklerUnpicklerObjectTests
|
||||
from test.pickletester import BigmemPickleTests
|
||||
|
||||
try:
|
||||
import _pickle
|
||||
@@ -37,13 +38,13 @@ class PyPicklerTests(AbstractPickleTests):
|
||||
return u.load()
|
||||
|
||||
|
||||
class InMemoryPickleTests(AbstractPickleTests):
|
||||
class InMemoryPickleTests(AbstractPickleTests, BigmemPickleTests):
|
||||
|
||||
pickler = pickle._Pickler
|
||||
unpickler = pickle._Unpickler
|
||||
|
||||
def dumps(self, arg, proto=None):
|
||||
return pickle.dumps(arg, proto)
|
||||
def dumps(self, arg, protocol=None):
|
||||
return pickle.dumps(arg, protocol)
|
||||
|
||||
def loads(self, buf, **kwds):
|
||||
return pickle.loads(buf, **kwds)
|
||||
|
||||
@@ -268,6 +268,9 @@ Core and Builtins
|
||||
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
|
||||
greater than FD_SETSIZE.
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user