@@ -1003,6 +1003,9 @@ class TestBinaryFormatValidation(BinaryFormatTestBase):
10031003 HDR_OFF_STR_TABLE = 36
10041004 HDR_OFF_FRAME_TABLE = 44
10051005 FILE_HEADER_PLACEHOLDER_SIZE = 64
1006+ FILE_FOOTER_SIZE = 32
1007+ FTR_OFF_STRINGS = 0
1008+ FTR_OFF_FRAMES = 4
10061009
10071010 def test_replay_rejects_more_threads_than_declared (self ):
10081011 """Replay rejects files with more unique threads than the header declares."""
@@ -1064,6 +1067,88 @@ def test_replay_rejects_trailing_partial_sample_header(self):
10641067 reader .replay_samples (RawCollector ())
10651068 self .assertEqual (str (cm .exception ), "Truncated sample data: 1 trailing bytes" )
10661069
1070+ # Minimum on-disk size of one table entry (see binary_io.h).
1071+ MIN_STRING_ENTRY_SIZE = 1
1072+ MIN_FRAME_ENTRY_SIZE = 7
1073+
1074+ def _read_offset (self , filename , hdr_off ):
1075+ with open (filename , "rb" ) as raw :
1076+ raw .seek (hdr_off )
1077+ return struct .unpack ("=Q" , raw .read (8 ))[0 ]
1078+
1079+ def _patch_footer_count (self , filename , ftr_off , value ):
1080+ size = os .path .getsize (filename )
1081+ with open (filename , "r+b" ) as raw :
1082+ raw .seek (size - self .FILE_FOOTER_SIZE + ftr_off )
1083+ raw .write (struct .pack ("=I" , value ))
1084+
1085+ def test_open_rejects_string_count_larger_than_file (self ):
1086+ """Open rejects a footer string count larger than the file."""
1087+ samples = [[make_interpreter (0 , [
1088+ make_thread (1 , [make_frame ("s.py" , 10 , "s" )])
1089+ ])]]
1090+ filename = self .create_binary_file (samples , compression = "none" )
1091+ size = os .path .getsize (filename )
1092+ str_off = self ._read_offset (filename , self .HDR_OFF_STR_TABLE )
1093+ max_strings = (size - str_off ) // self .MIN_STRING_ENTRY_SIZE
1094+ self ._patch_footer_count (filename , self .FTR_OFF_STRINGS , 0xFFFFFFFF )
1095+
1096+ with self .assertRaises (ValueError ) as cm :
1097+ with BinaryReader (filename ):
1098+ pass
1099+ self .assertEqual (
1100+ str (cm .exception ),
1101+ f"Invalid string count 4294967295 exceeds maximum "
1102+ f"possible { max_strings } " ,
1103+ )
1104+
1105+ def test_open_rejects_frame_count_larger_than_file (self ):
1106+ """Open rejects a footer frame count larger than the file."""
1107+ samples = [[make_interpreter (0 , [
1108+ make_thread (1 , [make_frame ("f.py" , 10 , "f" )])
1109+ ])]]
1110+ filename = self .create_binary_file (samples , compression = "none" )
1111+ size = os .path .getsize (filename )
1112+ frame_off = self ._read_offset (filename , self .HDR_OFF_FRAME_TABLE )
1113+ max_frames = (size - frame_off ) // self .MIN_FRAME_ENTRY_SIZE
1114+ self ._patch_footer_count (filename , self .FTR_OFF_FRAMES , 0xFFFFFFFF )
1115+
1116+ # On a 32-bit build this count overflows the allocation size, so the
1117+ # size_t overflow guard (OverflowError) fires before the count check.
1118+ with self .assertRaises ((ValueError , OverflowError )) as cm :
1119+ with BinaryReader (filename ):
1120+ pass
1121+ if isinstance (cm .exception , ValueError ):
1122+ self .assertEqual (
1123+ str (cm .exception ),
1124+ f"Invalid frame count 4294967295 exceeds maximum "
1125+ f"possible { max_frames } " ,
1126+ )
1127+
1128+ def test_open_accepts_frame_count_at_capacity_boundary (self ):
1129+ """A frame count at the file-size cap opens; one more is rejected."""
1130+ samples = [[make_interpreter (0 , [
1131+ make_thread (1 , [make_frame ("f.py" , 10 , "f" )])
1132+ ])]]
1133+ filename = self .create_binary_file (samples , compression = "none" )
1134+ size = os .path .getsize (filename )
1135+ frame_off = self ._read_offset (filename , self .HDR_OFF_FRAME_TABLE )
1136+ max_frames = (size - frame_off ) // self .MIN_FRAME_ENTRY_SIZE
1137+
1138+ self ._patch_footer_count (filename , self .FTR_OFF_FRAMES , max_frames )
1139+ with BinaryReader (filename ):
1140+ pass
1141+
1142+ self ._patch_footer_count (filename , self .FTR_OFF_FRAMES , max_frames + 1 )
1143+ with self .assertRaises (ValueError ) as cm :
1144+ with BinaryReader (filename ):
1145+ pass
1146+ self .assertEqual (
1147+ str (cm .exception ),
1148+ f"Invalid frame count { max_frames + 1 } exceeds maximum "
1149+ f"possible { max_frames } " ,
1150+ )
1151+
10671152
10681153class TestBinaryEncodings (BinaryFormatTestBase ):
10691154 """Tests specifically targeting different stack encodings."""
0 commit comments