CVE-2026-79259
Overview
Background
- Archive deep inspection
- Safe Browsing’s utility-process step that walks the entries of a downloaded archive (
.7z,.zip, etc.) to detect executables and nested archives before allowing the download. - `ArchiveAnalyzer`
- the base analyzer that iterates archive entries and, per entry, may spawn a nested analyzer to recurse into an entry that is itself an archive.
- Nested analyzer
- a child
ArchiveAnalyzercreated byArchiveAnalyzer::CreateForArchiveType()and pointed at a temp file, used to inspect an entry whose file type looks like an archive. - Directory entry
- a
7z/archive member that represents a folder rather than a file; its name can still carry an archive-looking extension (e.g.folder.zip).
Root Cause Analysis
In ArchiveAnalyzer::UpdateResultsForEntry(), the decision to recurse into an entry was gated only by if (!is_encrypted), then keyed on the entry’s file extension via GetFileType(path). Because the is_directory flag was ignored, a directory entry whose name has an archive extension (such as folder.zip) was treated as a real nested archive, and a nested analyzer was created and run against the temp file — a file that holds the previous entry’s bytes or is empty, since a directory carries no content of its own. The commit message notes that SevenZipAnalyzer::OnDirectory() also failed to record that this nested work was pending, so the outer analysis returned early and never inspected any entries appearing after the misclassified directory. The invariant violated is that only actual file entries with archive content should trigger recursion; a content-less directory should not.
The fix tightens the guard to if (!is_encrypted && !is_directory), so directory entries are never sent to a nested analyzer and the sequential inspection of later entries proceeds.
folder.zip short-circuit the whole scan; the fix simply excludes directory entries from nested analysis.Attack Path
- Craft the archive
An attacker builds a
7zarchive containing a directory entry whose name carries an archive extension (e.g.folder.zip) placed before a malicious executable entry (e.g.file.exe). - Trigger deep inspection
A victim downloads the archive, and Safe Browsing invokes
ArchiveAnalyzer::UpdateResultsForEntry()on each entry during deep inspection. - Misclassify the directory
For the
folder.zipdirectory entry, the oldif (!is_encrypted)check passes andCreateForArchiveType(GetFileType(path))spawns a nested analyzer against the temp file (empty or holding a prior entry’s bytes). - Abort the scan early
Because
SevenZipAnalyzer::OnDirectory()did not mark nested work as pending, the overall analysis returns before inspecting the laterfile.exeentry, so the executable is never scanned. - Evade detection The malicious executable reaches the user without Safe Browsing having deep-inspected it, bypassing download protection (inferred that this enables delivery of otherwise-flagged malware).
Impact Assessment
(inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/utility/safe_browsing/archive_analyzer.cc |
modified | |
TEST_Fchrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc |
modified |
Files Changed
third_party/lzma_sdk/google/test_data/archive_named_folder.7zthird_party/lzma_sdk/google/seven_zip_reader_unittest.ccchrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.ccchrome/utility/safe_browsing/archive_analyzer.cc
Audit Directions
- Extension-only type dispatchLook for archive/file handlers that pick a parser or recursion target from a name/extension alone without also checking entry kind (directory vs file) or actual content.
- Early-exit on unaccounted async workAudit analyzers that spawn nested/asynchronous work without recording it as pending, where a missed pending flag lets the parent return early and skip remaining items.
- Directory entries in archive readersReview other archive analyzers (
SevenZipAnalyzer, ZIP, RAR handlers) for whether directory entries are consistently excluded from content extraction and nested inspection.
Patch
From 9fd8769735435ca0e9ca2e323e0909bf68bee7d4 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
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 <devtools-gerritcodereview-exitgate@google.com>
Reviewed-by: thefrog <thefrog@chromium.org>
Commit-Queue: Andrew Paseltiner <apaseltiner@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1667330}
---
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/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
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
Regression Test / PoC
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
Original Bug Report
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.ccchrome/utility/safe_browsing/archive_analyzer.ccthird_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
- Create a 7z archive where the first entry is a directory named
bypass.zip. - Add a known-malicious executable (e.g., a test EICAR file or
malware.exe) as the second entry in the archive. - Download the archive using a Chrome instance with Safe Browsing ‘Enhanced Protection’ or Deep Scanning enabled.
- 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.