https://github.com/python/cpython/commit/2789841a48f1edd8cfe1a9dc6e6c2b54c117d401
commit: 2789841a48f1edd8cfe1a9dc6e6c2b54c117d401
branch: main
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-01T18:23:38Z
summary:

gh-161094: Fix `zipfile.is_zipfile()` raising `ValueError` on file-like objects 
(GH-161095)

Return False instead of seeking to a negative offset when the end of
central directory record places the central directory before the start
of the file.

files:
A Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
M Lib/test/test_zipfile/test_core.py
M Lib/zipfile/__init__.py

diff --git a/Lib/test/test_zipfile/test_core.py 
b/Lib/test/test_zipfile/test_core.py
index ab1995e91286e68..84000f472c4df9e 100644
--- a/Lib/test/test_zipfile/test_core.py
+++ b/Lib/test/test_zipfile/test_core.py
@@ -4300,6 +4300,20 @@ def test_is_zip_valid_file(self):
         self.assertTrue(zipfile.is_zipfile(fp))
         self.assertEqual(fp.tell(), mid)
 
+    def test_is_zip_bad_central_directory_offset(self):
+        """Check that is_zipfile() rejects a central directory before the 
file."""
+        fp = io.BytesIO()
+        with zipfile.ZipFile(fp, mode="w") as zipf:
+            zipf.writestr("foo.txt", b"O, for a Muse of Fire!")
+        data = bytearray(fp.getvalue())
+        endrec = len(data) - zipfile.sizeEndCentDir
+        struct.pack_into("<L", data, endrec + 12, len(data))
+        fp = io.BytesIO(data)
+        pos = fp.seek(5)
+        self.assertFalse(zipfile.is_zipfile(fp))
+        self.assertEqual(fp.tell(), pos)
+        self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, 
io.BytesIO(data))
+
     def test_non_existent_file_raises_OSError(self):
         # make sure we don't raise an AttributeError when a 
partially-constructed
         # ZipFile instance is finalized; this tests for regression on SF 
tracker
diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py
index 5963f84bdad2996..3110759b29f2713 100644
--- a/Lib/zipfile/__init__.py
+++ b/Lib/zipfile/__init__.py
@@ -240,8 +240,9 @@ def _check_zipfile(fp):
                 return True     # Empty zipfiles are still zipfiles
             elif endrec[_ECD_DISK_NUMBER] == endrec[_ECD_DISK_START]:
                 # Central directory is on the same disk
-                fp.seek(sum(_handle_prepended_data(endrec)))
-                if endrec[_ECD_SIZE] >= sizeCentralDir:
+                start_dir = sum(_handle_prepended_data(endrec))
+                if start_dir >= 0 and endrec[_ECD_SIZE] >= sizeCentralDir:
+                    fp.seek(start_dir)
                     data = fp.read(sizeCentralDir)   # CD is where we expect 
it to be
                     if len(data) == sizeCentralDir:
                         centdir = struct.unpack(structCentralDir, data) # CD 
is the right size
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst 
b/Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
new file mode 100644
index 000000000000000..716da00504cc594
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
@@ -0,0 +1,4 @@
+Fix :func:`zipfile.is_zipfile` raising :exc:`ValueError` instead of returning
+``False`` for a file-like object, such as :class:`io.BytesIO`, whose end of
+central directory record places the central directory before the start of the
+file.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to