Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper input validation in Safebrowsing
DescriptionImproper input validation in Safebrowsing
ComponentSafebrowsing
Bug ClassLogic Error
Tracker513222422
Fix commit9fd876973543 (chromium/src) +34/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-08-25

Changed Functions

FunctionChangeNotes
TEST_F
chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
modified
if
chrome/utility/safe_browsing/archive_analyzer.cc
modified

Files Changed

  • chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
  • chrome/utility/safe_browsing/archive_analyzer.cc
  • third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
  • third_party/lzma_sdk/google/test_data/archive_named_folder.7z
From 9fd8769735435ca0e9ca2e323e0909bf68bee7d4 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
Date: Thu, 23 Jul 2026 12:40:52 -0700
Subject: [PATCH] Don't recurse into directories during archive deep inspection

ArchiveAnalyzer::UpdateResultsForEntry() chose a nested analyzer based
only on the entry's file extension, so a 7z directory entry whose name
has an archive extension (e.g. "folder.zip") spawned a nested analyzer
against the temp file. This temp file holds the previous entry's bytes
or is empty. SevenZipAnalyzer::OnDirectory() also did not record that
nested work was pending, so the overall analysis returned early without
inspecting any later entries in the archive.

Fixed: 513222422
Change-Id: Icce57e5dc57211aaf98c970d4d40367e7795e547
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8132854
SLSA-Policy-Verified: SLSA Policy Verification Service <[email protected]>
Reviewed-by: thefrog <[email protected]>
Commit-Queue: Andrew Paseltiner <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1667330}
---

diff --git a/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc b/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
index 1c0037b..ad9ad97 100644
--- a/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
+++ b/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
@@ -163,6 +163,35 @@
   EXPECT_FALSE(results.archived_binary[2].is_archive());
 }
 
+TEST_F(SandboxedSevenZipAnalyzerTest, BinaryAfterArchiveNamedFolder) {
+  safe_browsing::ArchiveAnalyzerResults results;
+  RunAnalyzer(
+      dir_test_data_.Append(FILE_PATH_LITERAL("archive_named_folder.7z")),
+      &results);
+  ASSERT_TRUE(results.success);
+  EXPECT_TRUE(results.has_executable);
+  EXPECT_TRUE(results.has_archive);
+  EXPECT_EQ(1, results.file_count);
+  EXPECT_EQ(1, results.directory_count);
+  ASSERT_EQ(2, results.archived_binary.size());
+
+  EXPECT_EQ("folder.zip", results.archived_binary[0].file_path());
+  EXPECT_EQ(ClientDownloadRequest::ARCHIVE,
+            results.archived_binary[0].download_type());
+  EXPECT_FALSE(results.archived_binary[0].is_executable());
+  EXPECT_TRUE(results.archived_binary[0].is_archive());
+
+  EXPECT_EQ("file.exe", results.archived_binary[1].file_path());
+  EXPECT_EQ(ClientDownloadRequest::WIN_EXECUTABLE,
+            results.archived_binary[1].download_type());
+  EXPECT_EQ("B32E028F9B83C5FFB806CA7DFE7A3ECE5F1AED5A0368B0A140B35A67F5B000B3",
+            base::HexEncode(results.archived_binary[1].digests().sha256()));
+  EXPECT_EQ(19, results.archived_binary[1].length());
+  EXPECT_FALSE(results.archived_binary[1].is_encrypted());
+  EXPECT_TRUE(results.archived_binary[1].is_executable());
+  EXPECT_FALSE(results.archived_binary[1].is_archive());
+}
+
 TEST_F(SandboxedSevenZipAnalyzerTest, NestedArchive) {
   safe_browsing::ArchiveAnalyzerResults results;
   RunAnalyzer(dir_test_data_.Append(FILE_PATH_LITERAL("inner_archive.7z")),
diff --git a/chrome/utility/safe_browsing/archive_analyzer.cc b/chrome/utility/safe_browsing/archive_analyzer.cc
index 7aaf094..49e7ae8 100644
--- a/chrome/utility/safe_browsing/archive_analyzer.cc
+++ b/chrome/utility/safe_browsing/archive_analyzer.cc
@@ -104,7 +104,7 @@
                                             bool is_encrypted,
                                             bool is_directory,
                                             bool contents_valid) {
-  if (!is_encrypted) {
+  if (!is_encrypted && !is_directory) {
     nested_analyzer_ = ArchiveAnalyzer::CreateForArchiveType(GetFileType(path));
     if (nested_analyzer_) {
       // Archive analyzers expect to start at the beginning of the
diff --git a/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc b/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
index 188180a..b291d9a 100644
--- a/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
+++ b/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
@@ -31,6 +31,10 @@
 //   values with 0.
 //
 //   echo "This is not an exe" > file.exe
+//   mkdir folder.zip
+//   7z a archive_named_folder.7z file.exe folder.zip
+//
+//   echo "This is not an exe" > file.exe
 //   7z a -p encrypted.7z file.exe  # Provided 1234 as the password
 //
 //   echo "This is not an exe" > file.exe
diff --git a/third_party/lzma_sdk/google/test_data/archive_named_folder.7z b/third_party/lzma_sdk/google/test_data/archive_named_folder.7z
new file mode 100644
index 0000000..de4becb9
--- /dev/null
+++ b/third_party/lzma_sdk/google/test_data/archive_named_folder.7z
Binary files differ
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc b/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
index 1c0037b..ad9ad97 100644
--- a/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
+++ b/chrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
@@ -163,6 +163,35 @@
   EXPECT_FALSE(results.archived_binary[2].is_archive());
 }
 
+TEST_F(SandboxedSevenZipAnalyzerTest, BinaryAfterArchiveNamedFolder) {
+  safe_browsing::ArchiveAnalyzerResults results;
+  RunAnalyzer(
+      dir_test_data_.Append(FILE_PATH_LITERAL("archive_named_folder.7z")),
+      &results);
+  ASSERT_TRUE(results.success);
+  EXPECT_TRUE(results.has_executable);
+  EXPECT_TRUE(results.has_archive);
+  EXPECT_EQ(1, results.file_count);
+  EXPECT_EQ(1, results.directory_count);
+  ASSERT_EQ(2, results.archived_binary.size());
+
+  EXPECT_EQ("folder.zip", results.archived_binary[0].file_path());
+  EXPECT_EQ(ClientDownloadRequest::ARCHIVE,
+            results.archived_binary[0].download_type());
+  EXPECT_FALSE(results.archived_binary[0].is_executable());
+  EXPECT_TRUE(results.archived_binary[0].is_archive());
+
+  EXPECT_EQ("file.exe", results.archived_binary[1].file_path());
+  EXPECT_EQ(ClientDownloadRequest::WIN_EXECUTABLE,
+            results.archived_binary[1].download_type());
+  EXPECT_EQ("B32E028F9B83C5FFB806CA7DFE7A3ECE5F1AED5A0368B0A140B35A67F5B000B3",
+            base::HexEncode(results.archived_binary[1].digests().sha256()));
+  EXPECT_EQ(19, results.archived_binary[1].length());
+  EXPECT_FALSE(results.archived_binary[1].is_encrypted());
+  EXPECT_TRUE(results.archived_binary[1].is_executable());
+  EXPECT_FALSE(results.archived_binary[1].is_archive());
+}
+
 TEST_F(SandboxedSevenZipAnalyzerTest, NestedArchive) {
   safe_browsing::ArchiveAnalyzerResults results;
   RunAnalyzer(dir_test_data_.Append(FILE_PATH_LITERAL("inner_archive.7z")),
diff --git a/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc b/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
index 188180a..b291d9a 100644
--- a/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
+++ b/third_party/lzma_sdk/google/seven_zip_reader_unittest.cc
@@ -31,6 +31,10 @@
 //   values with 0.
 //
 //   echo "This is not an exe" > file.exe
+//   mkdir folder.zip
+//   7z a archive_named_folder.7z file.exe folder.zip
+//
+//   echo "This is not an exe" > file.exe
 //   7z a -p encrypted.7z file.exe  # Provided 1234 as the password
 //
 //   echo "This is not an exe" > file.exe
Loading diff…

Original Bug Report

reported by [email protected]

Safe Browsing deep-scanning bypass in 7z analyzer via directory entries

Project Fortify, 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 without the Chrome Security team. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A logic error in the 7z archive analyzer allows specifically crafted archives to bypass Safe Browsing deep-scanning. By including a directory entry with an archive-type extension (e.g., ‘.zip’), an attacker can cause the analysis process to terminate prematurely. This results in subsequent malicious files within the same 7z archive being ignored by the security scanner.

Affected files:

  • chrome/utility/safe_browsing/seven_zip_analyzer.cc
  • chrome/utility/safe_browsing/archive_analyzer.cc
  • third_party/lzma_sdk/google/seven_zip_reader.cc

Estimated timestamp from git blame: 2023-04-26

Summary

A potential vulnerability has been identified in the Safe Browsing 7z analyzer where the extraction process can be tricked into finishing early. This allows an attacker to hide malicious binaries within a 7z archive, bypassing hash-blocklist and heuristic checks during deep scanning.

Potential Root Cause

The issue appears to reside in SevenZipAnalyzer::OnDirectory within chrome/utility/safe_browsing/seven_zip_analyzer.cc.

When the analyzer encounters an entry, it calls UpdateResultsForEntry in chrome/utility/safe_browsing/archive_analyzer.cc. This method checks the file extension to decide if it should perform a nested analysis (e.g., scanning a .zip inside the .7z). For a directory named with an archive extension (like folder.zip), it initiates a nested analysis and returns false, signaling that extraction should pause for asynchronous work.

However, in SevenZipAnalyzer::OnDirectory, the code fails to set the awaiting_nested_ flag to true when UpdateResultsForEntry returns false:

// chrome/utility/safe_browsing/seven_zip_analyzer.cc
bool SevenZipAnalyzer::OnDirectory(const seven_zip::EntryInfo& entry) {
  return UpdateResultsForEntry(
      temp_file_.Duplicate(), GetRootPath().Append(entry.file_path),
      entry.file_size, entry.is_encrypted, /*is_directory=*/true,
      /*contents_valid=*/!entry.is_encrypted);
} // BUG: awaiting_nested_ is never set to true here.

When this returns false to the underlying SevenZipReader, the extraction loop breaks. Control returns to SevenZipAnalyzer::ResumeExtraction(), which checks awaiting_nested_. Because the flag is false, it returns true, indicating completion to the parent ArchiveAnalyzer. This causes the analyzer to report final results to the browser process prematurely, before subsequent entries in the 7z archive have been inspected.

Furthermore, if the nested analysis eventually finishes, it may trigger a CHECK failure in the utility process because the completion callback (finished_analysis_callback_) has already been consumed and nullified during the premature completion.

Potential Steps to Reproduce

  1. Create a 7z archive where the first entry is a directory named bypass.zip.
  2. Add a known-malicious executable (e.g., a test EICAR file or malware.exe) as the second entry in the archive.
  3. Download the archive using a Chrome instance with Safe Browsing ‘Enhanced Protection’ or Deep Scanning enabled.
  4. Observe the Safe Browsing logs (e.g., via chrome://safe-browsing) to see if the analysis report includes the malicious executable. If the bypass is successful, the report will likely show a valid archive with no identified binaries.

Suggested Fix

In chrome/utility/safe_browsing/seven_zip_analyzer.cc, update OnDirectory to correctly set the awaiting_nested_ flag when UpdateResultsForEntry returns false, consistent with how it is handled in EntryDone:

bool SevenZipAnalyzer::OnDirectory(const seven_zip::EntryInfo& entry) {
  if (!UpdateResultsForEntry(
          temp_file_.Duplicate(), GetRootPath().Append(entry.file_path),
          entry.file_size, entry.is_encrypted, /*is_directory=*/true,
          /*contents_valid=*/!entry.is_encrypted)) {
    awaiting_nested_ = true;
    return false;
  }
  return true;
}

Evaluated with Chrome root at commit: b3153093eb3c78c3e88ccf562bcbc20437a04b0e


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