Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in Safe Browsing
DescriptionInsufficient validation of untrusted input in Safe Browsing
ComponentSafe Browsing
Bug ClassLogic Error
Tracker514461031
Fix commit85e1f72188e4 (chromium/src) +72/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
TEST
third_party/zlib/contrib/tests/utils_unittest.cc
modified
TEST_F
third_party/zlib/google/zip_reader_unittest.cc
modified

Files Changed

  • third_party/zlib/contrib/minizip/unzip.c
  • third_party/zlib/contrib/tests/utils_unittest.cc
  • third_party/zlib/google/test/data/enc_flag_mismatch.zip
  • third_party/zlib/google/test_data.filelist
  • third_party/zlib/google/zip_reader_unittest.cc
  • third_party/zlib/patches/0015-minizip-unzip-enable-decryption.patch
From 85e1f72188e40a6ef40c6cec7fc395ebbab0b9ff Mon Sep 17 00:00:00 2001
From: Hans Wennborg <[email protected]>
Date: Tue, 23 Jun 2026 10:16:23 -0700
Subject: [PATCH] [minizip] Check LFH / CD encryption flag consistency

unz64local_CheckCurrentFileCoherencyHeader performs various consistency
checks on the values in the Local File Header and Central Directory.
Make it check the encryption flag as well.

Bug: 514461031
Change-Id: Ifaf8620c6e0c345118712bce6e1206bbb83b3a2d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7942389
Reviewed-by: Adenilson Cavalcanti <[email protected]>
Commit-Queue: Adenilson Cavalcanti <[email protected]>
Auto-Submit: Hans Wennborg <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1651090}
---

diff --git a/third_party/zlib/contrib/minizip/unzip.c b/third_party/zlib/contrib/minizip/unzip.c
index 0264f7a..4eb0de3 100644
--- a/third_party/zlib/contrib/minizip/unzip.c
+++ b/third_party/zlib/contrib/minizip/unzip.c
@@ -1439,6 +1439,8 @@
 */
     if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK)
         err=UNZ_ERRNO;
+    else if ((err==UNZ_OK) && ((uFlags & 1) != (s->cur_file_info.flag & 1)))
+        err=UNZ_BADZIPFILE; /* LFH/CD encryption flag mismatch */
 
     if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK)
         err=UNZ_ERRNO;
diff --git a/third_party/zlib/contrib/tests/utils_unittest.cc b/third_party/zlib/contrib/tests/utils_unittest.cc
index f8cd93c7..6161e39 100644
--- a/third_party/zlib/contrib/tests/utils_unittest.cc
+++ b/third_party/zlib/contrib/tests/utils_unittest.cc
@@ -1424,6 +1424,29 @@
   EXPECT_EQ(unzClose(uzf), UNZ_OK);
 }
 
+TEST(ZlibTest, ZipEncryptionFlagMismatch) {
+  // Test archive created with info-zip:
+  // $ echo -n a > a && zip -P a -k a.zip a
+  // and then hex-edited to drop the encrypted flag from the central directory.
+  base::FilePath zip_file = TestDataDir().AppendASCII("enc_flag_mismatch.zip");
+
+  unzFile uzf = unzOpen(zip_file.AsUTF8Unsafe().c_str());
+  ASSERT_NE(uzf, nullptr);
+
+  char name[100];
+  unz_file_info file_info;
+
+  ASSERT_EQ(unzGoToFirstFile(uzf), UNZ_OK);
+  ASSERT_EQ(unzGetCurrentFileInfo(uzf, &file_info, name, sizeof(name),
+                                  nullptr, 0, nullptr, 0), UNZ_OK);
+  ASSERT_EQ(std::string(name), "A");
+
+  // minizip should reject the member due to lfh/cd encrypted flag mismatch.
+  EXPECT_EQ(unzOpenCurrentFilePassword(uzf, "a"), UNZ_BADZIPFILE);
+
+  EXPECT_EQ(unzClose(uzf), UNZ_OK);
+}
+
 TEST(ZlibTest, Crbug500521311) {
   base::FilePath zip_file = TestDataDir().AppendASCII("bug500521311.zip");
   unzFile uzf = unzOpen(zip_file.AsUTF8Unsafe().c_str());
diff --git a/third_party/zlib/google/test/data/enc_flag_mismatch.zip b/third_party/zlib/google/test/data/enc_flag_mismatch.zip
new file mode 100644
index 0000000..bb14007
--- /dev/null
+++ b/third_party/zlib/google/test/data/enc_flag_mismatch.zip
Binary files differ
diff --git a/third_party/zlib/google/test_data.filelist b/third_party/zlib/google/test_data.filelist
index 73ed6c3..9761df1 100644
--- a/third_party/zlib/google/test_data.filelist
+++ b/third_party/zlib/google/test_data.filelist
@@ -19,6 +19,7 @@
 test/data/create_symlink_test_zips.py
 test/data/create_test_zip.sh
 test/data/empty.zip
+test/data/enc_flag_mismatch.zip
 test/data/evil.zip
 test/data/evil_via_absolute_file_name.zip
 test/data/evil_via_invalid_utf8.zip
diff --git a/third_party/zlib/google/zip_reader_unittest.cc b/third_party/zlib/google/zip_reader_unittest.cc
index 7a9f24b..3de6568 100644
--- a/third_party/zlib/google/zip_reader_unittest.cc
+++ b/third_party/zlib/google/zip_reader_unittest.cc
@@ -558,6 +558,24 @@
   EXPECT_TRUE(reader.ok());
 }
 
+// An entry whose local file header has the "encrypted" general-purpose flag
+// bit set while the central directory does not should be rejected.
+TEST_F(ZipReaderTest, MismatchedEncryptionFlag) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("enc_flag_mismatch.zip")));
+
+  const ZipReader::Entry* entry = reader.Next();
+  ASSERT_TRUE(entry);
+  EXPECT_EQ(base::FilePath::FromASCII("A"), entry->path);
+  EXPECT_FALSE(entry->is_directory);
+  std::string contents = "dummy";
+  EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
+  EXPECT_EQ("", contents);
+
+  EXPECT_FALSE(reader.Next());
+  EXPECT_TRUE(reader.ok());
+}
+
 // Verifies that the ZipReader class can extract a file from a zip archive
 // stored in memory. This test opens a zip archive in a std::string object,
 // extracts its content, and verifies the content is the same as the expected
diff --git a/third_party/zlib/patches/0015-minizip-unzip-enable-decryption.patch b/third_party/zlib/patches/0015-minizip-unzip-enable-decryption.patch
index feeeb1c..dcc7499 100644
--- a/third_party/zlib/patches/0015-minizip-unzip-enable-decryption.patch
+++ b/third_party/zlib/patches/0015-minizip-unzip-enable-decryption.patch
@@ -26,3 +26,31 @@
          s->encrypted=1;
      }
  #    endif
+
+commit 874ed6b46a4f75407829e510db77cc673a4c86e7
+Author: Hans Wennborg <[email protected]>
+Date:   Mon Jun 15 11:33:46 2026 +0200
+
+    Check LFH / CD encryption flag consistency
+
+    unz64local_CheckCurrentFileCoherencyHeader performs various consistency
+    checks on the values in the Local File Header and Central Directory.
+    Make it check the encryption flag as well.
+
+    Bug: 514461031
+    Change-Id: Ifaf8620c6e0c345118712bce6e1206bbb83b3a2d
+    Reviewed-on: https://chromium-review.googlesource.com/7942389
+
+diff --git a/third_party/zlib/contrib/minizip/unzip.c b/third_party/zlib/contrib/minizip/unzip.c
+index 0264f7ac570f7..4eb0de302cfdf 100644
+--- a/third_party/zlib/contrib/minizip/unzip.c
++++ b/third_party/zlib/contrib/minizip/unzip.c
+@@ -1439,6 +1439,8 @@ local int unz64local_CheckCurrentFileCoherencyHeader(unz64_s* s, uInt* piSizeVar
+ */
+     if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK)
+         err=UNZ_ERRNO;
++    else if ((err==UNZ_OK) && ((uFlags & 1) != (s->cur_file_info.flag & 1)))
++        err=UNZ_BADZIPFILE; /* LFH/CD encryption flag mismatch */
+ 
+     if (unz64local_getShort(&s->z_filefunc, s->filestream,&uData) != UNZ_OK)
+         err=UNZ_ERRNO;
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/zlib/contrib/tests/utils_unittest.cc b/third_party/zlib/contrib/tests/utils_unittest.cc
index f8cd93c7..6161e39 100644
--- a/third_party/zlib/contrib/tests/utils_unittest.cc
+++ b/third_party/zlib/contrib/tests/utils_unittest.cc
@@ -1424,6 +1424,29 @@
   EXPECT_EQ(unzClose(uzf), UNZ_OK);
 }
 
+TEST(ZlibTest, ZipEncryptionFlagMismatch) {
+  // Test archive created with info-zip:
+  // $ echo -n a > a && zip -P a -k a.zip a
+  // and then hex-edited to drop the encrypted flag from the central directory.
+  base::FilePath zip_file = TestDataDir().AppendASCII("enc_flag_mismatch.zip");
+
+  unzFile uzf = unzOpen(zip_file.AsUTF8Unsafe().c_str());
+  ASSERT_NE(uzf, nullptr);
+
+  char name[100];
+  unz_file_info file_info;
+
+  ASSERT_EQ(unzGoToFirstFile(uzf), UNZ_OK);
+  ASSERT_EQ(unzGetCurrentFileInfo(uzf, &file_info, name, sizeof(name),
+                                  nullptr, 0, nullptr, 0), UNZ_OK);
+  ASSERT_EQ(std::string(name), "A");
+
+  // minizip should reject the member due to lfh/cd encrypted flag mismatch.
+  EXPECT_EQ(unzOpenCurrentFilePassword(uzf, "a"), UNZ_BADZIPFILE);
+
+  EXPECT_EQ(unzClose(uzf), UNZ_OK);
+}
+
 TEST(ZlibTest, Crbug500521311) {
   base::FilePath zip_file = TestDataDir().AppendASCII("bug500521311.zip");
   unzFile uzf = unzOpen(zip_file.AsUTF8Unsafe().c_str());
diff --git a/third_party/zlib/google/test/data/enc_flag_mismatch.zip b/third_party/zlib/google/test/data/enc_flag_mismatch.zip
new file mode 100644
index 0000000..bb14007
--- /dev/null
+++ b/third_party/zlib/google/test/data/enc_flag_mismatch.zip
Binary files differ
diff --git a/third_party/zlib/google/zip_reader_unittest.cc b/third_party/zlib/google/zip_reader_unittest.cc
index 7a9f24b..3de6568 100644
--- a/third_party/zlib/google/zip_reader_unittest.cc
+++ b/third_party/zlib/google/zip_reader_unittest.cc
@@ -558,6 +558,24 @@
   EXPECT_TRUE(reader.ok());
 }
 
+// An entry whose local file header has the "encrypted" general-purpose flag
+// bit set while the central directory does not should be rejected.
+TEST_F(ZipReaderTest, MismatchedEncryptionFlag) {
+  ZipReader reader;
+  ASSERT_TRUE(reader.Open(data_dir_.AppendASCII("enc_flag_mismatch.zip")));
+
+  const ZipReader::Entry* entry = reader.Next();
+  ASSERT_TRUE(entry);
+  EXPECT_EQ(base::FilePath::FromASCII("A"), entry->path);
+  EXPECT_FALSE(entry->is_directory);
+  std::string contents = "dummy";
+  EXPECT_FALSE(reader.ExtractCurrentEntryToString(&contents));
+  EXPECT_EQ("", contents);
+
+  EXPECT_FALSE(reader.Next());
+  EXPECT_TRUE(reader.ok());
+}
+
 // Verifies that the ZipReader class can extract a file from a zip archive
 // stored in memory. This test opens a zip archive in a std::string object,
 // extracts its content, and verifies the content is the same as the expected
Loading diff…

Original Bug Report

reported by [email protected]

Potential Safe Browsing bypass via ZIP CD / LFH encryption flag desynchronization

Flapjack, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: Chromium’s minizip library fails to verify that the encryption flag in a ZIP file’s Local File Header (LFH) matches the Central Directory (CD). An attacker can craft a ZIP archive where the CD indicates the file is unencrypted, causing Safe Browsing to scan raw ciphertext, while standard extraction tools will recognize the LFH encryption flag and successfully decrypt the malicious payload.

Affected files:

  • third_party/zlib/contrib/minizip/unzip.c
  • third_party/zlib/google/zip_reader.cc

Estimated timestamp from git blame: 2011-12-12

Summary

A logic flaw in Chromium’s minizip library allows an attacker to desynchronize the General Purpose Bit Flag (GPBF) between a ZIP archive’s Central Directory (CD) and its Local File Header (LFH). Specifically, minizip trusts the CD for a file’s encryption status but fails to verify if this matches the LFH during coherency checks.

By manipulating these headers, an attacker can trick Safe Browsing into extracting and scanning raw ciphertext (which contains no detectable malware signatures) while ensuring that standard OS extraction tools still prompt for a password and decrypt the actual malware payload onto the user’s disk.

Technical Details

When Chrome downloads a ZIP file, Safe Browsing uses ZipReader (third_party/zlib/google/zip_reader.cc) to iterate through the entries. ZipReader determines if an entry is encrypted based solely on the CD metadata:

// third_party/zlib/google/zip_reader.cc:332
entry_.is_encrypted = info.flag & 1;

When minizip opens the file (unzOpenCurrentFile3), it performs a coherency check (unz64local_CheckCurrentFileCoherencyHeader) between the LFH and the CD. However, this function has two critical behaviors:

  1. It never compares the encryption bit (bit 0) of the GPBF between the two headers.
  2. If the ‘Data Descriptor’ bit (bit 3) is set in the LFH, it explicitly skips validating the CRC and size fields against the CD:
// third_party/zlib/contrib/minizip/unzip.c:1460
    else if ((err==UNZ_OK) && (uData!=s->cur_file_info.crc) && ((uFlags & 8)==0))
        err=UNZ_BADZIPFILE;

If the CD indicates the file is unencrypted, minizip does not initialize decryption. It reads the raw ciphertext from the file and calculates the CRC of that ciphertext. When the file is closed (unzCloseCurrentFile), minizip compares the calculated CRC against the expected CRC it read from the CD.

Potential Exploitation Steps

An attacker can construct a malicious ZIP archive as follows:

  1. Encrypt Payload: Encrypt the malware payload, producing ciphertext. Calculate the CRC of the ciphertext (CRC_Cipher).
  2. Craft LFH: Create the Local File Header for the entry. Set bit 0 (Encrypted) and bit 3 (Data Descriptor present). Set CRC and sizes to 0.
  3. Append Data: Append the 12-byte encryption header and the ciphertext immediately after the LFH.
  4. Append Data Descriptor: Append a Data Descriptor containing the actual CRC of the plaintext payload and the plaintext sizes.
  5. Craft CD: Create the Central Directory entry. Set bit 0 to 0 (Unencrypted) and bit 3 to 0. Set the CRC field to CRC_Cipher and the sizes to match the ciphertext.

Resulting Behavior:

  • Safe Browsing (minizip): Reads the CD, sees the file is unencrypted, and extracts it without a password. Because bit 3 is set in the LFH, minizip ignores the 0-value CRC/sizes in the LFH. It reads exactly the ciphertext, calculates its CRC, and verifies it against the CRC_Cipher stored in the CD. The extraction succeeds, and Safe Browsing scans the raw ciphertext, finding no malware.
  • Standard Extraction Tools (e.g., WinRAR, 7-Zip): Prioritize the LFH. They see bit 0 is set, prompt the user for a password, and decrypt the data. Seeing bit 3 is set, they read the appended Data Descriptor and successfully verify the plaintext CRC. The malware is extracted to disk.

Note: These are potential steps based on source code analysis. Our tooling agent does not run code to dynamically verify the exploit.

Suggested Fix

In third_party/zlib/contrib/minizip/unzip.c, update unz64local_CheckCurrentFileCoherencyHeader to strictly enforce that the encryption bit (bit 0) in the Local File Header matches the encryption bit in the Central Directory.

    if (unz64local_getShort(&s->z_filefunc, s->filestream,&uFlags) != UNZ_OK)
        err=UNZ_ERRNO;
    else if ((err==UNZ_OK) && ((uFlags & 1) != (s->cur_file_info.flag & 1)))
        err=UNZ_BADZIPFILE;

This will cause minizip to reject archives where the encryption status is desynchronized, preventing the bypass.

Evaluated with Chrome root at commit: b7d0c4d810da1b31400f198c70d9720fc8f0e5a0


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.

View on issue tracker