mirror of
https://github.com/python/cpython.git
synced 2025-08-02 08:02:56 +00:00
[3.13] gh-117779: Fix reading duplicated entries in zipfile by name (GH-129254) (GH-132263)
(cherry picked from commit 0f04f2456a
)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
This commit is contained in:
parent
5911768600
commit
67c7de49dc
3 changed files with 121 additions and 6 deletions
|
@ -2351,7 +2351,36 @@ class OtherTests(unittest.TestCase):
|
||||||
self.assertRaises(RuntimeError, zf.extract, 'a.txt')
|
self.assertRaises(RuntimeError, zf.extract, 'a.txt')
|
||||||
|
|
||||||
@requires_zlib()
|
@requires_zlib()
|
||||||
def test_full_overlap(self):
|
def test_full_overlap_different_names(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\x00b\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('b')), 1033)
|
||||||
|
with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'):
|
||||||
|
zipf.read('a')
|
||||||
|
|
||||||
|
@requires_zlib()
|
||||||
|
def test_full_overlap_different_names2(self):
|
||||||
data = (
|
data = (
|
||||||
b'PK\x03\x04\x14\x00\x00\x00\x08\x00\xa0lH\x05\xe2\x1e'
|
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'8\xbb\x10\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00a\xed'
|
||||||
|
@ -2375,9 +2404,43 @@ class OtherTests(unittest.TestCase):
|
||||||
self.assertEqual(zi.header_offset, 0)
|
self.assertEqual(zi.header_offset, 0)
|
||||||
self.assertEqual(zi.compress_size, 16)
|
self.assertEqual(zi.compress_size, 16)
|
||||||
self.assertEqual(zi.file_size, 1033)
|
self.assertEqual(zi.file_size, 1033)
|
||||||
self.assertEqual(len(zipf.read('a')), 1033)
|
|
||||||
with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'):
|
with self.assertRaisesRegex(zipfile.BadZipFile, 'File name.*differ'):
|
||||||
zipf.read('b')
|
zipf.read('b')
|
||||||
|
with self.assertWarnsRegex(UserWarning, 'Overlapped entries') as cm:
|
||||||
|
self.assertEqual(len(zipf.read('a')), 1033)
|
||||||
|
self.assertEqual(cm.filename, __file__)
|
||||||
|
|
||||||
|
@requires_zlib()
|
||||||
|
def test_full_overlap_same_name(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\x00aPK\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', 'a'])
|
||||||
|
self.assertEqual(len(zipf.infolist()), 2)
|
||||||
|
zi = zipf.getinfo('a')
|
||||||
|
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)
|
||||||
|
self.assertEqual(len(zipf.read(zi)), 1033)
|
||||||
|
self.assertEqual(len(zipf.read(zipf.infolist()[1])), 1033)
|
||||||
|
with self.assertWarnsRegex(UserWarning, 'Overlapped entries') as cm:
|
||||||
|
self.assertEqual(len(zipf.read(zipf.infolist()[0])), 1033)
|
||||||
|
self.assertEqual(cm.filename, __file__)
|
||||||
|
with self.assertWarnsRegex(UserWarning, 'Overlapped entries') as cm:
|
||||||
|
zipf.open(zipf.infolist()[0]).close()
|
||||||
|
self.assertEqual(cm.filename, __file__)
|
||||||
|
|
||||||
@requires_zlib()
|
@requires_zlib()
|
||||||
def test_quoted_overlap(self):
|
def test_quoted_overlap(self):
|
||||||
|
@ -2410,6 +2473,47 @@ class OtherTests(unittest.TestCase):
|
||||||
zipf.read('a')
|
zipf.read('a')
|
||||||
self.assertEqual(len(zipf.read('b')), 1033)
|
self.assertEqual(len(zipf.read('b')), 1033)
|
||||||
|
|
||||||
|
@requires_zlib()
|
||||||
|
def test_overlap_with_central_dir(self):
|
||||||
|
data = (
|
||||||
|
b'PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00G_|Z'
|
||||||
|
b'\xe2\x1e8\xbb\x0b\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00'
|
||||||
|
b'\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81\x00\x00\x00\x00aP'
|
||||||
|
b'K\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00/\x00\x00\x00\x00'
|
||||||
|
b'\x00\x00\x00\x00\x00'
|
||||||
|
)
|
||||||
|
with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
|
||||||
|
self.assertEqual(zipf.namelist(), ['a'])
|
||||||
|
self.assertEqual(len(zipf.infolist()), 1)
|
||||||
|
zi = zipf.getinfo('a')
|
||||||
|
self.assertEqual(zi.header_offset, 0)
|
||||||
|
self.assertEqual(zi.compress_size, 11)
|
||||||
|
self.assertEqual(zi.file_size, 1033)
|
||||||
|
with self.assertRaisesRegex(zipfile.BadZipFile, 'Bad magic number'):
|
||||||
|
zipf.read('a')
|
||||||
|
|
||||||
|
@requires_zlib()
|
||||||
|
def test_overlap_with_archive_comment(self):
|
||||||
|
data = (
|
||||||
|
b'PK\x01\x02\x14\x03\x14\x00\x00\x00\x08\x00G_|Z'
|
||||||
|
b'\xe2\x1e8\xbb\x0b\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00'
|
||||||
|
b'\x00\x00\x00\x00\x00\x00\x00\x00\xb4\x81E\x00\x00\x00aP'
|
||||||
|
b'K\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00/\x00\x00\x00\x00'
|
||||||
|
b'\x00\x00\x00*\x00'
|
||||||
|
b'PK\x03\x04\x14\x00\x00\x00\x08\x00G_|Z\xe2\x1e'
|
||||||
|
b'8\xbb\x0b\x00\x00\x00\t\x04\x00\x00\x01\x00\x00\x00aK'
|
||||||
|
b'L\x1c\x05\xa3`\x14\x8cx\x00\x00'
|
||||||
|
)
|
||||||
|
with zipfile.ZipFile(io.BytesIO(data), 'r') as zipf:
|
||||||
|
self.assertEqual(zipf.namelist(), ['a'])
|
||||||
|
self.assertEqual(len(zipf.infolist()), 1)
|
||||||
|
zi = zipf.getinfo('a')
|
||||||
|
self.assertEqual(zi.header_offset, 69)
|
||||||
|
self.assertEqual(zi.compress_size, 11)
|
||||||
|
self.assertEqual(zi.file_size, 1033)
|
||||||
|
with self.assertRaisesRegex(zipfile.BadZipFile, 'Overlapped entries'):
|
||||||
|
zipf.read('a')
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
unlink(TESTFN)
|
unlink(TESTFN)
|
||||||
unlink(TESTFN2)
|
unlink(TESTFN2)
|
||||||
|
|
|
@ -1521,9 +1521,8 @@ class ZipFile:
|
||||||
print("total", total)
|
print("total", total)
|
||||||
|
|
||||||
end_offset = self.start_dir
|
end_offset = self.start_dir
|
||||||
for zinfo in sorted(self.filelist,
|
for zinfo in reversed(sorted(self.filelist,
|
||||||
key=lambda zinfo: zinfo.header_offset,
|
key=lambda zinfo: zinfo.header_offset)):
|
||||||
reverse=True):
|
|
||||||
zinfo._end_offset = end_offset
|
zinfo._end_offset = end_offset
|
||||||
end_offset = zinfo.header_offset
|
end_offset = zinfo.header_offset
|
||||||
|
|
||||||
|
@ -1685,7 +1684,16 @@ class ZipFile:
|
||||||
|
|
||||||
if (zinfo._end_offset is not None and
|
if (zinfo._end_offset is not None and
|
||||||
zef_file.tell() + zinfo.compress_size > zinfo._end_offset):
|
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 == zinfo.header_offset:
|
||||||
|
import warnings
|
||||||
|
warnings.warn(
|
||||||
|
f"Overlapped entries: {zinfo.orig_filename!r} "
|
||||||
|
f"(possible zip bomb)",
|
||||||
|
skip_file_prefixes=(os.path.dirname(__file__),))
|
||||||
|
else:
|
||||||
|
raise BadZipFile(
|
||||||
|
f"Overlapped entries: {zinfo.orig_filename!r} "
|
||||||
|
f"(possible zip bomb)")
|
||||||
|
|
||||||
# check for encrypted flag & handle password
|
# check for encrypted flag & handle password
|
||||||
is_encrypted = zinfo.flag_bits & _MASK_ENCRYPTED
|
is_encrypted = zinfo.flag_bits & _MASK_ENCRYPTED
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Fix reading duplicated entries in :mod:`zipfile` by name.
|
||||||
|
Reading duplicated entries (except the last one) by ``ZipInfo``
|
||||||
|
now emits a warning instead of raising an exception.
|
Loading…
Add table
Add a link
Reference in a new issue