11import sys
2+ import textwrap
23import unittest
34from test import support
45from test .support import import_helper
6+ from test .support .script_helper import assert_python_failure
57
68_testlimitedcapi = import_helper .import_module ('_testlimitedcapi' )
79_testcapi = import_helper .import_module ('_testcapi' )
@@ -316,12 +318,18 @@ def test_join(self):
316318 bytes_join (b'' , NULL )
317319
318320
321+ def get_data_canary (writer ):
322+ size = writer .get_size () + 1
323+ return writer .get_data (size )
324+
325+
319326class BaseWriterTest :
320327 RESULT_TYPE = NotImplementedError
321328 SMALL_BUFFER = 11 # bytes
322329 assert SMALL_BUFFER < _testcapi .PyBytesWriter_small_buffer
323330 LARGE_BUFFER = _testcapi .PyBytesWriter_small_buffer + 17 # bytes
324331 NEW_BYTE = b'\xff '
332+ CANARY_BYTE = b'\xdd '
325333
326334 def create_writer (self , alloc = 0 , string = b'' ):
327335 raise NotImplementedError
@@ -344,6 +352,7 @@ def test_get_data(self):
344352 # Test PyBytesWriter_GetData()
345353 writer = self .create_writer (6 )
346354 NEW_BYTE = self .NEW_BYTE
355+ CANARY_BYTE = self .CANARY_BYTE
347356 self .assertEqual (writer .get_data (), NEW_BYTE * 6 )
348357 writer .write (0 , b'abc' )
349358 self .assertEqual (writer .get_data (), b'abc' + NEW_BYTE * 3 )
@@ -357,7 +366,7 @@ def test_get_data(self):
357366 writer .write (0 , b's' * small )
358367 self .assertEqual (writer .get_data (), b's' * small )
359368 writer .resize (large )
360- self .assertEqual (writer .get_data (), b's' * small + NEW_BYTE * (large - small ))
369+ self .assertEqual (writer .get_data (), b's' * small + CANARY_BYTE + NEW_BYTE * (large - small - 1 ))
361370 writer .write (small , b'L' * (large - small ))
362371 self .assertEqual (writer .get_data (), b's' * small + b'L' * (large - small ))
363372
@@ -443,6 +452,47 @@ def test_resize(self):
443452 writer .resize (_testcapi .PY_SSIZE_T_MAX )
444453 self .assertEqual (writer .finish (), b'x' * size )
445454
455+ @unittest .skipUnless (support .Py_DEBUG , 'need debug build' )
456+ def test_resize_canary (self ):
457+ CANARY_BYTE = self .CANARY_BYTE
458+ for size in (self .SMALL_BUFFER , self .LARGE_BUFFER ):
459+ with self .subTest (size = size ):
460+ # Truncate the last byte
461+ data = b'x' * size
462+ writer = self .create_writer (size )
463+ writer .write (0 , data )
464+ self .assertEqual (get_data_canary (writer ), data + CANARY_BYTE )
465+ writer .resize (size - 1 )
466+ self .assertEqual (get_data_canary (writer ), data [:- 1 ] + CANARY_BYTE )
467+ self .assertEqual (writer .finish (), data [:- 1 ])
468+
469+ # Make the buffer empty
470+ writer = self .create_writer (size )
471+ writer .write (0 , data )
472+ writer .resize (0 )
473+ self .assertEqual (writer .get_data (), b'' )
474+ self .assertEqual (writer .finish (), b'' )
475+
476+ @support .nomemtest
477+ def test_resize_error (self ):
478+ # Test PyBytesWriter_Resize() error
479+ init = b'x' * self .LARGE_BUFFER
480+ writer = self .create_writer (len (init ))
481+ writer .write (0 , init )
482+ size = len (init ) + 100
483+ try :
484+ with self .assertRaises (MemoryError ):
485+ _testcapi .set_nomemory (0 )
486+ writer .resize (size )
487+ finally :
488+ _testcapi .remove_mem_hooks ()
489+ suffix = b'still working'
490+ writer .write_bytes (suffix , - 1 )
491+ self .assertEqual (writer .finish (), init + suffix )
492+
493+ # Note: PyBytesWriter_Resize() leaves the buffer unchanged (no resize)
494+ # if the new size is smaller than the allocated size
495+
446496 def test_grow (self ):
447497 # Test PyBytesWriter_Grow()
448498 writer = self .create_writer (0 )
@@ -461,24 +511,6 @@ def test_grow(self):
461511 writer .grow (0 ) # noop
462512 self .assertEqual (writer .finish (), b'number=123' )
463513
464- for size in (self .SMALL_BUFFER , self .LARGE_BUFFER ):
465- with self .subTest (size = size ):
466- # Truncate the last byte
467- data = b'x' * size
468- writer = self .create_writer (size )
469- writer .write (0 , data )
470- self .assertEqual (writer .get_data (), data )
471- writer .grow (- 1 )
472- self .assertEqual (writer .get_data (), data [:- 1 ])
473- self .assertEqual (writer .finish (), data [:- 1 ])
474-
475- # Make the buffer empty
476- writer = self .create_writer (size )
477- writer .write (0 , data )
478- writer .grow (- size )
479- self .assertEqual (writer .get_data (), b'' )
480- self .assertEqual (writer .finish (), b'' )
481-
482514 # Switch from small buffer to large buffer
483515 writer = self .create_writer ()
484516 small , large = self .SMALL_BUFFER , self .LARGE_BUFFER
@@ -500,25 +532,45 @@ def test_grow(self):
500532 writer .grow (_testcapi .PY_SSIZE_T_MAX )
501533 self .assertEqual (writer .finish (), b'x' * size )
502534
535+ @unittest .skipUnless (support .Py_DEBUG , 'need debug build' )
536+ def test_grow_canary (self ):
537+ CANARY_BYTE = self .CANARY_BYTE
538+ for size in (self .SMALL_BUFFER , self .LARGE_BUFFER ):
539+ with self .subTest (size = size ):
540+ # Truncate the last byte
541+ data = b'x' * size
542+ writer = self .create_writer (size )
543+ writer .write (0 , data )
544+ self .assertEqual (get_data_canary (writer ), data + CANARY_BYTE )
545+ writer .grow (- 1 )
546+ self .assertEqual (get_data_canary (writer ), data [:- 1 ] + CANARY_BYTE )
547+ self .assertEqual (writer .finish (), data [:- 1 ])
548+
549+ # Make the buffer empty
550+ writer = self .create_writer (size )
551+ writer .write (0 , data )
552+ writer .grow (- size )
553+ self .assertEqual (writer .get_data (), b'' )
554+ self .assertEqual (writer .finish (), b'' )
555+
503556 @support .nomemtest
504- def test_resize_error (self ):
505- # Test PyBytesWriter_Resize () error
557+ def test_grow_error (self ):
558+ # Test PyBytesWriter_Grow () error
506559 init = b'x' * self .LARGE_BUFFER
507560 writer = self .create_writer (len (init ))
508561 writer .write (0 , init )
509- size = len (init ) + 100
510562 try :
511563 with self .assertRaises (MemoryError ):
512564 _testcapi .set_nomemory (0 )
513- writer .resize ( size )
565+ writer .grow ( 100 )
514566 finally :
515567 _testcapi .remove_mem_hooks ()
516568 suffix = b'still working'
517569 writer .write_bytes (suffix , - 1 )
518570 self .assertEqual (writer .finish (), init + suffix )
519571
520- # Note: PyBytesWriter_Resize () leaves the buffer unchanged (no resize)
521- # if the new size is smaller than the allocated size
572+ # Note: PyBytesWriter_Grow () leaves the buffer unchanged (no resize)
573+ # if grow is negative.
522574
523575 def test_format_i (self ):
524576 # Test PyBytesWriter_Format()
@@ -531,6 +583,49 @@ def test_format_i(self):
531583 writer .format_i (b'y=%i' , 456 )
532584 self .assertEqual (writer .finish (), b'x=123, y=456' )
533585
586+ @unittest .skipUnless (support .Py_DEBUG , 'need a Python debug build' )
587+ def test_canary_byte (self ):
588+ small_buffer = _testcapi .PyBytesWriter_small_buffer
589+ large_size = small_buffer * 10
590+ use_bytearray = (self .RESULT_TYPE == bytearray )
591+
592+ # Test small buffer and large buffer
593+ for size in (0 , self .SMALL_BUFFER , self .LARGE_BUFFER ):
594+ with self .subTest (size = size ):
595+ code = textwrap .dedent (f"""
596+ from test.support import SuppressCrashReport
597+ import _testcapi
598+ size = { size }
599+ # Add an extra '#' byte to trigger a buffer overflow
600+ data = b'x' * size + b'#'
601+ use_bytearray = { use_bytearray }
602+ writer = _testcapi.PyBytesWriter(size, use_bytearray)
603+ with SuppressCrashReport():
604+ writer.write(0, data, check=False)
605+ writer.finish()
606+ """ )
607+ proc = assert_python_failure ('-c' , code )
608+ self .assertIn (b'Buffer overflow detected in PyBytesWriter' ,
609+ proc .err )
610+ self .assertIn (f'at position { size } ' .encode (),
611+ proc .err )
612+
613+ @unittest .skipUnless (support .Py_DEBUG , 'need debug build' )
614+ def test_get_data_canary (self ):
615+ # Test PyBytesWriter_GetData()
616+ NEW_BYTE = self .NEW_BYTE
617+ CANARY_BYTE = self .CANARY_BYTE
618+
619+ writer = self .create_writer (6 )
620+ self .assertEqual (get_data_canary (writer ),
621+ NEW_BYTE * 6 + CANARY_BYTE )
622+ writer .write (0 , b'abc' )
623+ self .assertEqual (get_data_canary (writer ),
624+ b'abc' + NEW_BYTE * 3 + CANARY_BYTE )
625+ writer .write (3 , b'123' )
626+ self .assertEqual (get_data_canary (writer ),
627+ b'abc123' + CANARY_BYTE )
628+
534629
535630class BytesWriterTest (BaseWriterTest , unittest .TestCase ):
536631 RESULT_TYPE = bytes
0 commit comments