Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
SUSE:SLE-12-SP5:GA
python3-doc.36306
CVE-2024-0450-zipfile-avoid-quoted-overlap-zipb...
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2024-0450-zipfile-avoid-quoted-overlap-zipbomb.patch of Package python3-doc.36306
From d8877aaabe9aa5d9b9904c222c552f3c6a85017c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka <storchaka@gmail.com> Date: Wed, 17 Jan 2024 15:41:50 +0200 Subject: [PATCH] [CVE-2024-0450] Protect zipfile from "quoted-overlap" zipbomb Raise BadZipFile when try to read an entry that overlaps with other entry or central directory. (cherry picked from commit 66363b9a7b9fe7c99eba3a185b74c5fdbf842eba) From-PR: gh#python/cpython!110016 Fixes: gh#python/cpython#109858 Patch: CVE-2024-0450-zipfile-avoid-quoted-overlap-zipbomb.patch --- Lib/test/test_zipfile.py | 67 +++++++++- Lib/zipfile.py | 12 + Misc/NEWS.d/next/Library/2023-09-28-13-15-51.gh-issue-109858.43e2dg.rst | 3 3 files changed, 79 insertions(+), 3 deletions(-) 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 @@ -11,6 +11,7 @@ import unittest from tempfile import TemporaryFile from random import randint, random, getrandbits +from unittest import mock from test.support import (TESTFN, findfile, unlink, rmtree, requires_zlib, requires_bz2, requires_lzma, @@ -717,13 +718,13 @@ class PyZipFileTests(unittest.TestCase): with captured_stdout() as reportSIO: zipfp.writepy(packagedir) reportStr = reportSIO.getvalue() - self.assertTrue('SyntaxError' in reportStr) + self.assertIn('SyntaxError', reportStr) # then check that the filter works on the whole package with captured_stdout() as reportSIO: zipfp.writepy(packagedir, filterfunc=lambda whatever: False) reportStr = reportSIO.getvalue() - self.assertTrue('SyntaxError' not in reportStr) + self.assertNotIn('SyntaxError', reportStr) # then check that the filter works on individual files def filter(path): @@ -733,7 +734,7 @@ class PyZipFileTests(unittest.TestCase): reportStr = reportSIO.getvalue() if reportStr: print(reportStr) - self.assertTrue('SyntaxError' not in reportStr) + self.assertNotIn('SyntaxError', reportStr) def test_write_with_optimization(self): import email @@ -1556,6 +1557,66 @@ class AbstractTestsWithRandomBinaryFiles with open(TESTFN, "wb") as fp: fp.write(self.data) + @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 @@ -311,6 +311,7 @@ class ZipInfo (object): 'compress_size', 'file_size', '_raw_time', + '_end_offset', ) def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): @@ -349,6 +350,7 @@ class ZipInfo (object): self.volume = 0 # Volume number of file header self.internal_attr = 0 # Internal attributes self.external_attr = 0 # External file attributes + self._end_offset = None # Start of the next local header or central directory # Other attributes are set by class ZipFile: # header_offset Byte offset to the file header # CRC CRC-32 of the uncompressed file @@ -1044,6 +1046,12 @@ class ZipFile: if self.debug > 2: print("total", total) + 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 def namelist(self): """Return a list of file names in the archive.""" @@ -1179,6 +1187,10 @@ class ZipFile: 'File name in directory %r and header %r differ.' % (zinfo.orig_filename, fname)) + if (zinfo._end_offset is not None and + zef_file.tell() + zinfo.compress_size > zinfo._end_offset): + raise BadZipFile("Overlapped entries: {!r} (possible zip bomb)".format(zinfo.orig_filename)) + # check for encrypted flag & handle password is_encrypted = zinfo.flag_bits & 0x1 zd = None --- /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.
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor