From ae01b8cd69f53a509b5782ad080c9254ae13edb1 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Sun, 25 May 2025 17:28:40 +0800 Subject: [PATCH] Raise on overlapping file blocks --- Lib/test/test_zipfile/test_core.py | 15 +++++++++++++ Lib/zipfile/__init__.py | 35 +++++++++++++++++++++--------- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 563ce2c6154..8fddf5ab1c5 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -2024,6 +2024,21 @@ def test_repack_data_descriptor_no_sig_and_zip64(self): # check file size self.assertEqual(os.path.getsize(TESTFN), expected_size) + def test_repack_overlapping_blocks(self): + for ii in ([0], [1], [2]): + with self.subTest(remove=ii): + self._prepare_zip_from_test_files(TESTFN, self.test_files) + with open(TESTFN, 'r+b') as fh: + with zipfile.ZipFile(fh, 'a') as zh: + zh.writestr('file.txt', b'dummy') + for i in ii: + zh.infolist()[i].file_size += 50 + zh.infolist()[i].compress_size += 50 + + with zipfile.ZipFile(TESTFN, 'a') as zh: + with self.assertRaises(zipfile.BadZipFile): + zh.repack() + @mock.patch('zipfile._ZipRepacker') def test_repack_closed(self, m_repack): self._prepare_zip_from_test_files(TESTFN, self.test_files) diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 7adced683f8..16636bf1bb7 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -1452,25 +1452,40 @@ def _repack(self, zfile): # doesn't match the actual entry order filelist = sorted(zfile.filelist, key=lambda x: x.header_offset) - try: - data_offset = filelist[0].header_offset - except IndexError: - data_offset = zfile.start_dir - - # calculate the starting entry offset (bytes to skip) - entry_offset = self._calc_initial_entry_offset(fp, data_offset) - - # move file entries + # calculate each entry size and validate + entry_size_list = [] + used_entry_size_list = [] for i, zinfo in enumerate(filelist): - # get the total size of the entry try: offset = filelist[i + 1].header_offset except IndexError: offset = zfile.start_dir entry_size = offset - zinfo.header_offset + # may raise on an invalid local file header used_entry_size = self._calc_local_file_entry_size(fp, zinfo) + self._debug(3, i, zinfo.orig_filename, entry_size, used_entry_size) + if used_entry_size > entry_size: + raise BadZipFile( + f"Overlapped entries: {zinfo.orig_filename!r} " + f"(possible zip bomb)") + + entry_size_list.append(entry_size) + used_entry_size_list.append(used_entry_size) + + # calculate the starting entry offset (bytes to skip) + try: + data_offset = filelist[0].header_offset + except IndexError: + data_offset = zfile.start_dir + entry_offset = self._calc_initial_entry_offset(fp, data_offset) + + # move file entries + for i, zinfo in enumerate(filelist): + entry_size = entry_size_list[i] + used_entry_size = used_entry_size_list[i] + # update the header and move entry data to the new position if entry_offset > 0: old_header_offset = zinfo.header_offset