[3.12] gh-50644: Forbid pickling of codecs streams (GH-109180) (#109231)

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.
(cherry picked from commit d6892c2b92)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
Miss Islington (bot) 2023-10-02 07:55:52 -07:00 committed by GitHub
parent be8255ad8c
commit 3e1c9e8264
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 @@ class StreamWriter(Codec):
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### ###
class StreamReader(Codec): class StreamReader(Codec):
@ -663,6 +666,9 @@ class StreamReader(Codec):
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### ###
class StreamReaderWriter: class StreamReaderWriter:
@ -750,6 +756,9 @@ class StreamReaderWriter:
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### ###
class StreamRecoder: class StreamRecoder:
@ -866,6 +875,9 @@ class StreamRecoder:
def __exit__(self, type, value, tb): def __exit__(self, type, value, tb):
self.stream.close() self.stream.close()
def __reduce_ex__(self, proto):
raise TypeError("can't serialize %s" % self.__class__.__name__)
### Shortcuts ### Shortcuts
def open(filename, mode='r', encoding=None, errors='strict', buffering=-1): def open(filename, mode='r', encoding=None, errors='strict', buffering=-1):

View file

@ -1,7 +1,9 @@
import codecs import codecs
import contextlib import contextlib
import copy
import io import io
import locale import locale
import pickle
import sys import sys
import unittest import unittest
import encodings import encodings
@ -1771,6 +1773,61 @@ class StreamReaderTest(unittest.TestCase):
f = self.reader(self.stream) f = self.reader(self.stream)
self.assertEqual(f.readlines(), ['\ud55c\n', '\uae00']) 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): class EncodedFileTest(unittest.TestCase):
@ -3346,6 +3403,28 @@ class StreamRecoderTest(unittest.TestCase):
self.assertEqual(sr.readline(), b'abc\n') self.assertEqual(sr.readline(), b'abc\n')
self.assertEqual(sr.readline(), b'789\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') @unittest.skipIf(_testinternalcapi is None, 'need _testinternalcapi module')
class LocaleCodecTest(unittest.TestCase): 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.