CVE-2024-0450 (Python, published 2024-03-19T16:15Z)
Description: value: An issue was found in the CPython `zipfile` module affecting versions 3.12.1, 3.11.7, 3.10.13, 3.9.18, and 3.8.18 and prior.

The zipfile module is vulnerable to “quoted-overlap” zip-bombs which exploit the zip format to create a zip-bomb with a high compression ratio. The fixed versions of CPython makes the zipfile module reject zip archives which overlap entries in the archive.


Severity: nan (CVSS v3 base score: )
Weakness: NVD-CWE-noinfo \u2014 Insufficient Information
There is insufficient information about the issue to classify it; details are unkown or unspecified.
Repository: https://github.com/python/cpython
Files changed:
Lib/test/test_zipfile/test_core.py
Lib/zipfile/__init__.py
Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst
Commit message: 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.


Vulnerable code:

Fixed code:
    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')

    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)

        '_end_offset',
        self._end_offset = None         # Start of the next local header or central directory
        end_offset = self.start_dir
        for zinfo in sorted(self.filelist,
                            key=lambda zinfo: zinfo.header_offset,
                            reverse=True):
            zinfo._end_offset = end_offset
            end_offset = zinfo.header_offset
            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)")

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.