gh-50644: Forbid pickling of codecs streams (GH-109180)

Attempts to pickle or create a shallow or deep copy of codecs streams
now raise a TypeError.

Previously, copying failed with a RecursionError, while pickling
produced wrong results that eventually caused unpickling to fail with
a RecursionError.
This commit is contained in:
Serhiy Storchaka 2023-09-10 20:06:09 +03:00 committed by GitHub
parent 71b6e2602c
commit d6892c2b92
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 95 additions and 0 deletions

View file

@ -414,6 +414,9 @@ def __enter__(self):
def __exit__(self, type, value, tb):
self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
###
class StreamReader(Codec):
@ -663,6 +666,9 @@ def __enter__(self):
def __exit__(self, type, value, tb):
self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
###
class StreamReaderWriter:
@ -750,6 +756,9 @@ def __enter__(self):
def __exit__(self, type, value, tb):
self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
###
class StreamRecoder:
@ -866,6 +875,9 @@ def __enter__(self):
def __exit__(self, type, value, tb):
self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### Shortcuts
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):

View file

@ -1,7 +1,9 @@
import codecs
import contextlib
import copy
import io
import locale
import pickle
import sys
import unittest
import encodings
@ -1771,6 +1773,61 @@ def test_readlines(self):
f = self.reader(self.stream)
self.assertEqual(f.readlines(), ['\ud55c\n', '\uae00'])
def test_copy(self):
f = self.reader(Queue(b'\xed\x95\x9c\n\xea\xb8\x80'))
with self.assertRaisesRegex(TypeError, 'StreamReader'):
copy.copy(f)
with self.assertRaisesRegex(TypeError, 'StreamReader'):
copy.deepcopy(f)
def test_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
f = self.reader(Queue(b'\xed\x95\x9c\n\xea\xb8\x80'))
with self.assertRaisesRegex(TypeError, 'StreamReader'):
pickle.dumps(f, proto)
class StreamWriterTest(unittest.TestCase):
def setUp(self):
self.writer = codecs.getwriter('utf-8')
def test_copy(self):
f = self.writer(Queue(b''))
with self.assertRaisesRegex(TypeError, 'StreamWriter'):
copy.copy(f)
with self.assertRaisesRegex(TypeError, 'StreamWriter'):
copy.deepcopy(f)
def test_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
f = self.writer(Queue(b''))
with self.assertRaisesRegex(TypeError, 'StreamWriter'):
pickle.dumps(f, proto)
class StreamReaderWriterTest(unittest.TestCase):
def setUp(self):
self.reader = codecs.getreader('latin1')
self.writer = codecs.getwriter('utf-8')
def test_copy(self):
f = codecs.StreamReaderWriter(Queue(b''), self.reader, self.writer)
with self.assertRaisesRegex(TypeError, 'StreamReaderWriter'):
copy.copy(f)
with self.assertRaisesRegex(TypeError, 'StreamReaderWriter'):
copy.deepcopy(f)
def test_pickle(self):
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
f = codecs.StreamReaderWriter(Queue(b''), self.reader, self.writer)
with self.assertRaisesRegex(TypeError, 'StreamReaderWriter'):
pickle.dumps(f, proto)
class EncodedFileTest(unittest.TestCase):
@ -3346,6 +3403,28 @@ def test_seeking_write(self):
self.assertEqual(sr.readline(), b'abc\n')
self.assertEqual(sr.readline(), b'789\n')
def test_copy(self):
bio = io.BytesIO()
codec = codecs.lookup('ascii')
sr = codecs.StreamRecoder(bio, codec.encode, codec.decode,
encodings.ascii.StreamReader, encodings.ascii.StreamWriter)
with self.assertRaisesRegex(TypeError, 'StreamRecoder'):
copy.copy(sr)
with self.assertRaisesRegex(TypeError, 'StreamRecoder'):
copy.deepcopy(sr)
def test_pickle(self):
q = Queue(b'')
codec = codecs.lookup('ascii')
sr = codecs.StreamRecoder(q, codec.encode, codec.decode,
encodings.ascii.StreamReader, encodings.ascii.StreamWriter)
for proto in range(pickle.HIGHEST_PROTOCOL + 1):
with self.subTest(protocol=proto):
with self.assertRaisesRegex(TypeError, 'StreamRecoder'):
pickle.dumps(sr, proto)
@unittest.skipIf(_testinternalcapi is None, 'need _testinternalcapi module')
class LocaleCodecTest(unittest.TestCase):

View file

@ -0,0 +1,4 @@
Attempts to pickle or create a shallow or deep copy of :mod:`codecs` streams
now raise a TypeError. Previously, copying failed with a RecursionError,
while pickling produced wrong results that eventually caused unpickling
to fail with a RecursionError.