mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	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:
		
							parent
							
								
									71b6e2602c
								
							
						
					
					
						commit
						d6892c2b92
					
				
					 3 changed files with 95 additions and 0 deletions
				
			
		|  | @ -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): | ||||
|  |  | |||
|  | @ -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): | ||||
|  |  | |||
|  | @ -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. | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Serhiy Storchaka
						Serhiy Storchaka