Skip to content

Commit 6c0977c

Browse files
committed
gh-155639: Reject ZIP files with inconsistent EOCD disk information
1 parent 1cf7d89 commit 6c0977c

3 files changed

Lines changed: 96 additions & 3 deletions

File tree

Lib/test/test_zipfile/test_core.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1136,6 +1136,54 @@ def test_bad_zip64_end_of_central_dir(self):
11361136
zipfile.ZipFile(io.BytesIO(zipdata))
11371137
self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata)))
11381138

1139+
def test_multi_disk_zip64_end_of_central_dir(self):
1140+
zipdata = self.make_zip64_file()
1141+
eocd64 = zipdata.rfind(zipfile.stringEndArchive64)
1142+
disk_fields_offset = eocd64 + struct.calcsize('<4sQ2H')
1143+
1144+
for disk_number, disk_start in ((1, 0), (0, 1), (1, 1)):
1145+
corrupted = bytearray(zipdata)
1146+
struct.pack_into(
1147+
'<LL', corrupted, disk_fields_offset,
1148+
disk_number, disk_start,
1149+
)
1150+
with self.subTest(
1151+
disk_number=disk_number,
1152+
disk_start=disk_start,
1153+
):
1154+
with self.assertRaisesRegex(zipfile.BadZipFile,
1155+
'multiple disks'):
1156+
zipfile.ZipFile(io.BytesIO(corrupted))
1157+
self.assertFalse(zipfile.is_zipfile(io.BytesIO(corrupted)))
1158+
1159+
def test_multi_disk_end_of_central_dir_with_zip64(self):
1160+
zipdata = self.make_zip64_file()
1161+
eocd = zipdata.rfind(zipfile.stringEndArchive)
1162+
1163+
for disk_number, disk_start in ((1, 0), (0, 1), (1, 1)):
1164+
corrupted = bytearray(zipdata)
1165+
struct.pack_into(
1166+
'<HH', corrupted, eocd + 4,
1167+
disk_number, disk_start,
1168+
)
1169+
with self.subTest(
1170+
disk_number=disk_number,
1171+
disk_start=disk_start,
1172+
):
1173+
with self.assertRaisesRegex(zipfile.BadZipFile,
1174+
'multiple disks'):
1175+
zipfile.ZipFile(io.BytesIO(corrupted))
1176+
self.assertFalse(zipfile.is_zipfile(io.BytesIO(corrupted)))
1177+
1178+
def test_zip64_end_of_central_dir_disk_sentinels(self):
1179+
zipdata = bytearray(self.make_zip64_file())
1180+
eocd = zipdata.rfind(zipfile.stringEndArchive)
1181+
struct.pack_into('<HH', zipdata, eocd + 4, 0xffff, 0xffff)
1182+
1183+
with zipfile.ZipFile(io.BytesIO(zipdata)) as zf:
1184+
self.assertEqual(zf.namelist(), ['test.txt'])
1185+
self.assertTrue(zipfile.is_zipfile(io.BytesIO(zipdata)))
1186+
11391187
def test_zip64_end_of_central_dir_record_not_found(self):
11401188
zipdata = self.make_zip64_file()
11411189
zipdata = zipdata.replace(b"PK\x06\x06", b'\x00'*4)
@@ -4246,6 +4294,34 @@ def test_damaged_zipfile(self):
42464294
fp = io.BytesIO(zipfiledata[:N])
42474295
self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, fp)
42484296

4297+
def test_multi_disk_end_of_central_dir(self):
4298+
for comment in (b'', b'comment'):
4299+
archive = io.BytesIO()
4300+
with zipfile.ZipFile(archive, 'w') as zf:
4301+
zf.writestr('entry.txt', b'payload')
4302+
zf.comment = comment
4303+
zipdata = archive.getvalue()
4304+
eocd = zipdata.rfind(zipfile.stringEndArchive)
4305+
4306+
for disk_number, disk_start in ((1, 0), (0, 1), (1, 1)):
4307+
corrupted = bytearray(zipdata)
4308+
struct.pack_into(
4309+
'<HH', corrupted, eocd + 4,
4310+
disk_number, disk_start,
4311+
)
4312+
with self.subTest(
4313+
comment=comment,
4314+
disk_number=disk_number,
4315+
disk_start=disk_start,
4316+
):
4317+
with self.assertRaisesRegex(zipfile.BadZipFile,
4318+
'multiple disks'):
4319+
zipfile.ZipFile(io.BytesIO(corrupted))
4320+
fp = io.BytesIO(corrupted)
4321+
pos = fp.seek(5)
4322+
self.assertFalse(zipfile.is_zipfile(fp))
4323+
self.assertEqual(fp.tell(), pos)
4324+
42494325
def test_is_zip_valid_file(self):
42504326
"""Check that is_zipfile() correctly identifies zip files."""
42514327
# - passing a filename

Lib/zipfile/__init__.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ def _check_zipfile(fp):
247247
centdir = struct.unpack(structCentralDir, data) # CD is the right size
248248
if centdir[_CD_SIGNATURE] == stringCentralDir:
249249
return True # First central directory entry has correct magic number
250-
except OSError:
250+
except (OSError, BadZipFile):
251251
pass
252252
return False
253253

@@ -282,6 +282,15 @@ def _handle_prepended_data(endrec, debug=0):
282282

283283
return offset_cd, concat
284284

285+
286+
def _validate_end_record(endrec, *, allow_zip64_sentinels=False):
287+
valid_disk_numbers = (0, 0xffff) if allow_zip64_sentinels else (0,)
288+
if (endrec[_ECD_DISK_NUMBER] not in valid_disk_numbers or
289+
endrec[_ECD_DISK_START] not in valid_disk_numbers):
290+
raise BadZipFile("zipfiles that span multiple disks are not supported")
291+
return endrec
292+
293+
285294
def _EndRecData64(fpin, offset, endrec):
286295
"""
287296
Read the ZIP64 end-of-archive records and use that to update endrec
@@ -372,7 +381,10 @@ def _EndRecData(fpin):
372381
endrec.append(filesize - sizeEndCentDir)
373382

374383
# Try to read the "Zip64 end of central directory" structure
375-
return _EndRecData64(fpin, filesize - sizeEndCentDir, endrec)
384+
_validate_end_record(endrec, allow_zip64_sentinels=True)
385+
return _validate_end_record(
386+
_EndRecData64(fpin, filesize - sizeEndCentDir, endrec)
387+
)
376388

377389
# Either this is not a ZIP file, or it is a ZIP file with an archive
378390
# comment. Search the end of the file for the "end of central directory"
@@ -396,7 +408,10 @@ def _EndRecData(fpin):
396408
endrec.append(maxCommentStart + start)
397409

398410
# Try to read the "Zip64 end of central directory" structure
399-
return _EndRecData64(fpin, maxCommentStart + start, endrec)
411+
_validate_end_record(endrec, allow_zip64_sentinels=True)
412+
return _validate_end_record(
413+
_EndRecData64(fpin, maxCommentStart + start, endrec)
414+
)
400415

401416
# Unable to find a valid end of central directory structure
402417
return None
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Reject ZIP archives whose end of central directory records indicate that
2+
the archive spans multiple disks.

0 commit comments

Comments
 (0)