120 lines
5.5 KiB
Diff
120 lines
5.5 KiB
Diff
From 8281fc11b47064f9a4908358befa9db6829f8b88 Mon Sep 17 00:00:00 2001
|
|
From: Serhiy Storchaka <storchaka@gmail.com>
|
|
Date: Wed, 10 Jan 2024 15:55:36 +0200
|
|
Subject: [PATCH] gh-109858: Protect zipfile from "quoted-overlap" zipbomb
|
|
(GH-110016)
|
|
|
|
Raise BadZipFile when try to read an entry that overlaps with other entry or
|
|
central directory.
|
|
(cherry picked from commit 66363b9a7b9fe7c99eba3a185b74c5fdbf842eba)
|
|
|
|
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
|
|
---
|
|
Lib/test/test_zipfile.py | 60 ++++++++++
|
|
Lib/zipfile.py | 10 +
|
|
Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst | 3
|
|
3 files changed, 73 insertions(+)
|
|
create mode 100644 Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst
|
|
|
|
--- a/Lib/test/test_zipfile.py
|
|
+++ b/Lib/test/test_zipfile.py
|
|
@@ -2304,6 +2304,66 @@ class OtherTests(unittest.TestCase):
|
|
zipf.read('a')
|
|
self.assertEqual(len(zipf.read('b')), 1033)
|
|
|
|
+ @requires_zlib()
|
|
+ def test_full_overlap(self):
|
|
+ data = (
|
|
+ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e'
|
|
+ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed'
|
|
+ b'\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\d\x0b`P'
|
|
+ b'K\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2'
|
|
+ b'\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00'
|
|
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00aPK'
|
|
+ b'\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e'
|
|
+ b'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00\x00\x00'
|
|
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00bPK\x05'
|
|
+ b'\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00\x00/\x00\x00'
|
|
+ b'\x00\x00\x00'
|
|
+ )
|
|
+ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
|
|
+ self.assertEqual(zipf.namelist(), ['a', 'b'])
|
|
+ zi = zipf.getinfo('a')
|
|
+ self.assertEqual(zi.header_offset, 0)
|
|
+ self.assertEqual(zi.compress_size, 16)
|
|
+ self.assertEqual(zi.file_size, 1033)
|
|
+ zi = zipf.getinfo('b')
|
|
+ self.assertEqual(zi.header_offset, 0)
|
|
+ self.assertEqual(zi.compress_size, 16)
|
|
+ self.assertEqual(zi.file_size, 1033)
|
|
+ self.assertEqual(len(zipf.read('a')), 1033)
|
|
+ with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'):
|
|
+ zipf.read('b')
|
|
+
|
|
+ @requires_zlib()
|
|
+ def test_quoted_overlap(self):
|
|
+ data = (
|
|
+ b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05Y\xfc'
|
|
+ b'8\x044\x00\x00\x00(\x04\x00\x00\x01\x00\x00\x00a\x00'
|
|
+ b'\x1f\x00\xe0\xffPK\x03\x04\x14\x00\x00\x00\x08\x00\xa0l'
|
|
+ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00'
|
|
+ b'\x00\x00b\xed\xc0\x81\x08\x00\x00\x00\xc00\xd6\xfbK\\'
|
|
+ b'd\x0b`PK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0'
|
|
+ b'lH\x05Y\xfc8\x044\x00\x00\x00(\x04\x00\x00\x01'
|
|
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
|
+ b'\x00aPK\x01\x02\x14\x00\x14\x00\x00\x00\x08\x00\xa0l'
|
|
+ b'H\x05\xe2\x1e8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00'
|
|
+ b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00$\x00\x00\x00'
|
|
+ b'bPK\x05\x06\x00\x00\x00\x00\x02\x00\x02\x00^\x00\x00'
|
|
+ b'\x00S\x00\x00\x00\x00\x00'
|
|
+ )
|
|
+ with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
|
|
+ self.assertEqual(zipf.namelist(), ['a', 'b'])
|
|
+ zi = zipf.getinfo('a')
|
|
+ self.assertEqual(zi.header_offset, 0)
|
|
+ self.assertEqual(zi.compress_size, 52)
|
|
+ self.assertEqual(zi.file_size, 1064)
|
|
+ zi = zipf.getinfo('b')
|
|
+ self.assertEqual(zi.header_offset, 36)
|
|
+ self.assertEqual(zi.compress_size, 16)
|
|
+ self.assertEqual(zi.file_size, 1033)
|
|
+ with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'):
|
|
+ zipf.read('a')
|
|
+ self.assertEqual(len(zipf.read('b')), 1033)
|
|
+
|
|
def tearDown(self):
|
|
unlink(TESTFN)
|
|
unlink(TESTFN2)
|
|
--- a/Lib/zipfile.py
|
|
+++ b/Lib/zipfile.py
|
|
@@ -1217,6 +1217,12 @@ class _ZipWriteFile(io.BufferedIOBase):
|
|
self._zipfile._writing = False
|
|
|
|
|
|
+ end_offset = self._zipfile.start_dir
|
|
+ for zinfo in sorted(self._zipfile.filelist,
|
|
+ key=lambda zinfo: zinfo.header_offset,
|
|
+ reverse=True):
|
|
+ zinfo._end_offset = end_offset
|
|
+ end_offset = zinfo.header_offset
|
|
|
|
class ZipFile:
|
|
""" Class with methods to open, read, write, close, list zip files.
|
|
@@ -1600,6 +1606,10 @@ class ZipFile:
|
|
|
|
if (zinfo._end_offset is not None and
|
|
zef_file.tell() + zinfo.compress_size > zinfo._end_offset):
|
|
+ raise BadZipFile(f"Overlapped entries: {zinfo.orig_filename!r} (possible zip bomb)")
|
|
+
|
|
+ if (zinfo._end_offset is not None and
|
|
+ zef_file.tell() + zinfo.compress_size > zinfo._end_offset):
|
|
raise BadZipFile(f"Overlapped entries: {zinfo.orig_filename!r} (possible zip bomb)")
|
|
|
|
# check for encrypted flag & handle password
|
|
--- /dev/null
|
|
+++ b/Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst
|
|
@@ -0,0 +1,3 @@
|
|
+Protect :mod:`zipfile` from "quoted-overlap" zipbomb. It now raises
|
|
+BadZipFile when try to read an entry that overlaps with other entry or
|
|
+central directory.
|