CVE-2026-87548
Overview
Background
- `SevenZipAnalyzer`
- the Safe Browsing utility-process component that iterates over the entries of a
.7zarchive to classify a download and populateArchiveAnalyzerResults. - `ArchiveAnalyzerResults`
- the result struct whose fields (
success,analysis_result,encryption_info,archived_binary) feed the download ping’sarchive_summary.parser_statusanddownload_type. - `ArchiveAnalysisResult`
- an enum of terminal analysis outcomes; the fix uses its
kFailedDuringIterationvalue to signal that iteration aborted on a bad entry. - Download ping
- the Safe Browsing telemetry/verdict message that reports how an archived download was parsed; correct classification depends on the analyzer’s
results()state.
Root Cause Analysis
Inside the entry-completion handler (the commit message identifies it as SevenZipAnalyzer::EntryDone() (inferred), since the @@ header @@ -93,6 +93,11 @@ carries no function name), the pre-patch code only had a branch that set awaiting_nested_ = true and returned false for one case, then fell straight through to return true for everything else. When an unencrypted entry failed to unpack, execution reached that fall-through path without ever recording a failure, so results()->success retained its valid state and analysis_result was left unchanged. This violated the invariant that a failed unpack of a non-encrypted entry must produce a failed archive analysis, causing the download ping to incorrectly report archive_summary.parser_status = "VALID" and download_type = "ARCHIVE".
The fix adds an else branch that sets results()->success = false and results()->analysis_result = ArchiveAnalysisResult::kFailedDuringIteration before returning, so the malformed archive is marked failed and the ping instead omits parser_status and reports download_type = "INVALID_SEVEN_ZIP". The change tightens state validation — it adds a failure-marking branch that previously did not exist — rather than relaxing any check.
return true without touching results(), so the archive was reported as valid. The fix closes the gap by explicitly marking success = false and analysis_result = kFailedDuringIteration on that path.Attack Path
- Craft a malformed 7z
An attacker builds a
.7zarchive with an unencrypted entry that cannot be unpacked (the shipped test caseunsupported_coder_chain.7zuses an unsupported coder chain). - Trigger analysis
The archive is delivered as a download, and Safe Browsing runs
SevenZipAnalyzerover it in the utility process; the failing entry reaches the completion handler’s fall-through path. - Mis-record archive state
Because no
elsebranch marked the failure,results()->successstays valid andanalysis_resultis unchanged, so iteration ends as if the archive parsed successfully. - Emit incorrect telemetry/verdict
The download ping reports
archive_summary.parser_status = "VALID"anddownload_type = "ARCHIVE"for an archive that never parsed, misclassifying the download(inferred)as to any resulting detection bypass — the diff shows only the corrected metadata, not a downstream security decision.
Impact Assessment
.7z download, which is reported as a valid archive instead of an invalid one within the Safe Browsing analysis (utility) process; the only precondition demonstrated by the diff is that the archive contain an unencrypted entry that fails to unpack. The concrete security consequence — such as evading archive-content inspection or a detection bypass — is (inferred), because the patch changes only reported metadata and does not itself demonstrate a bypass. The vendor advisory rates this medium severity and does not report in-the-wild exploitation; NVD scores it CVSS 4.3.Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc |
modified |
Files Changed
third_party/lzma_sdk/google/test_data/unsupported_coder_chain.7zchrome/utility/safe_browsing/seven_zip_analyzer.ccchrome/services/file_util/public/cpp/sandboxed_seven_zip_analyzer_unittest.cc
Audit Directions
- Fall-through success pathsAudit analyzer/parser handlers where an error case returns without mutating the shared
results()/state object and control falls through to a successreturn, leaving the outcome falsely valid. - Encrypted-vs-unencrypted asymmetryReview branches that special-case one condition (e.g., encrypted or nested entries via
awaiting_nested_) but omit the complementaryelse, so the unhandled case silently succeeds. - Metadata-to-verdict couplingCheck other Safe Browsing archive analyzers that populate
ArchiveAnalyzerResultsfields consumed by the download ping (parser_status,download_type) for cases where a partial or failed parse can still be reported assuccess = true.
Patch
From 9f4f461aaf77809973b4f4e42de7b090ae8deb9b Mon Sep 17 00:00:00 2001 From: thefrog <[email protected]> Date: Fri, 31 Jul 2026 14:41:25 -0700 Subject: [PATCH] Fix 7z unpacking error incorrect reported metadata in download ping When an unencrypted 7z archive entry failed to unpack, SevenZipAnalyzer::EntryDone() previously incorrectly marked the archive result as valid. As a result, the download ping would incorrectly contain: - archive_summary.parser_status = "VALID" - download_type = "ARCHIVE" This change updates SevenZipAnalyzer::EntryDone() so that if an unencrypted entry fails to unpack, the archive analysis is marked as failed. The download ping now contains: - archive_summary.parser_status is not set - download_type = "INVALID_SEVEN_ZIP" Reviewed in https://crrev.com/i/9620201. Fixed: 513495219 Change-Id: I6efa84b5ec9fdfe1ca34fc1aef9de002d1ccf6f8 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8183009 Auto-Submit: thefrog <[email protected]> Commit-Queue: Yaw Frempong <[email protected]> Commit-Queue: thefrog <[email protected]> Reviewed-by: Yaw Frempong <[email protected]> SLSA-Policy-Verified: SLSA Policy Verification Service <[email protected]> Cr-Commit-Position: refs/heads/main@{#1672117} --- diff --git a/chrome/utility/safe_browsing/seven_zip_analyzer.cc b/chrome/utility/safe_browsing/seven_zip_analyzer.cc index 259392e28..766668f 100644 --- a/chrome/utility/safe_browsing/seven_zip_analyzer.cc +++ b/chrome/utility/safe_browsing/seven_zip_analyzer.cc @@ -93,6 +93,11 @@ awaiting_nested_ = true; return false; } + } else { + // If an unencrypted entry failed to unpack, mark the entire archive + // analysis as failed so we do not report it as a valid archive. + results()->success = false; + results()->analysis_result = ArchiveAnalysisResult::kFailedDuringIteration; } return true; diff --git a/third_party/lzma_sdk/google/test_data/unsupported_coder_chain.7z b/third_party/lzma_sdk/google/test_data/unsupported_coder_chain.7z new file mode 100644 index 0000000..589cbc3 --- /dev/null +++ b/third_party/lzma_sdk/google/test_data/unsupported_coder_chain.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 ad9ad97..86a2771 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 @@ -235,6 +235,18 @@ EXPECT_FALSE(results.archived_binary[0].is_archive()); } +TEST_F(SandboxedSevenZipAnalyzerTest, UnsupportedCoderChainFailsAnalysis) { + safe_browsing::ArchiveAnalyzerResults results; + RunAnalyzer( + dir_test_data_.Append(FILE_PATH_LITERAL("unsupported_coder_chain.7z")), + &results); + EXPECT_FALSE(results.success); + EXPECT_EQ(ArchiveAnalysisResult::kFailedDuringIteration, + results.analysis_result); + EXPECT_FALSE(results.encryption_info.is_encrypted); + EXPECT_EQ(0, results.archived_binary.size()); +} + TEST_F(SandboxedSevenZipAnalyzerTest, NotASevenZip) { safe_browsing::ArchiveAnalyzerResults results; RunAnalyzer(dir_test_data_.Append(FILE_PATH_LITERAL("not_a_seven_zip.7z")),
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 ad9ad97..86a2771 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
@@ -235,6 +235,18 @@
EXPECT_FALSE(results.archived_binary[0].is_archive());
}
+TEST_F(SandboxedSevenZipAnalyzerTest, UnsupportedCoderChainFailsAnalysis) {
+ safe_browsing::ArchiveAnalyzerResults results;
+ RunAnalyzer(
+ dir_test_data_.Append(FILE_PATH_LITERAL("unsupported_coder_chain.7z")),
+ &results);
+ EXPECT_FALSE(results.success);
+ EXPECT_EQ(ArchiveAnalysisResult::kFailedDuringIteration,
+ results.analysis_result);
+ EXPECT_FALSE(results.encryption_info.is_encrypted);
+ EXPECT_EQ(0, results.archived_binary.size());
+}
+
TEST_F(SandboxedSevenZipAnalyzerTest, NotASevenZip) {
safe_browsing::ArchiveAnalyzerResults results;
RunAnalyzer(dir_test_data_.Append(FILE_PATH_LITERAL("not_a_seven_zip.7z")),
Original Bug Report
Safe Browsing bypass in 7z analysis due to ignored error and coder limit discrepancy
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. 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 reader ignores errors during encryption detection, causing incorrect metadata reporting. Combined with a coder limit discrepancy, this allows crafted archives to bypass Safe Browsing analysis. Malicious payloads can be hidden from deep-scanning while remaining accessible to users.
Affected files:
third_party/lzma_sdk/google/seven_zip_reader.ccchrome/utility/safe_browsing/seven_zip_analyzer.cc
Estimated timestamp from git blame: 2022-09-20
Summary
A potential vulnerability exists in Chromium’s Safe Browsing 7z archive analysis where an ignored return value in SevenZipReaderImpl::IsFolderEncrypted leads to incorrect metadata reporting. When combined with a ‘parser differential’—where the initial archive scan permits more coders than the subsequent extraction and metadata pass—an attacker can craft a 7z archive that Safe Browsing fails to correctly identify as containing encrypted or executable content. This may allow malicious payloads to bypass deep-scanning signals.
Root Cause Analysis
The issue resides in SevenZipReaderImpl::IsFolderEncrypted within third_party/lzma_sdk/google/seven_zip_reader.cc. This function calls SzGetNextFolderItem to parse a folder’s coder graph but ignores its SRes return value:
// third_party/lzma_sdk/google/seven_zip_reader.cc
CSzFolder folder;
SzGetNextFolderItem(&folder, &span); // <-- SRes return value IGNORED
for (size_t i = 0; i < folder.NumCoders && i < SZ_NUM_CODERS_IN_FOLDER_MAX; ++i) {
// ...
}
In the LZMA SDK, SzGetNextFolderItem initializes f->NumCoders = 0 and only sets the actual value after all validation passes. If validation fails (e.g., returning SZ_ERROR_UNSUPPORTED), NumCoders remains 0. Consequently, the loop in IsFolderEncrypted never runs, and the function incorrectly returns false regardless of the actual folder contents.
Parser Differential
A discrepancy exists between the archive scanning pass (ReadUnpackInfo called during SzArEx_Open) and the decoding/metadata pass (SzGetNextFolderItem).
- The initial scanning pass in the LZMA SDK allows up to 64 coders (
k_Scan_NumCoders_MAX). - The decoding pass and metadata parsing used by Chrome’s reader limit this to 4 coders (
SZ_NUM_CODERS_IN_FOLDER_MAX).
An archive folder utilizing 5 coders will be accepted during the initial SzArEx_Open call but will cause SzGetNextFolderItem to fail during the subsequent metadata-collection and extraction phases.
Potential Exploitation Steps
An attacker might follow these steps to potentially trigger the vulnerability:
- Craft a
.7zfile where a payload folder uses 5 coders, including an encryption method (e.g., 7zAES). - When a user downloads the file,
SevenZipAnalyzerinitiates a scan in the sandboxed utility process. SzArEx_Opensucceeds because its scanning pass allows up to 64 coders.IsFolderEncryptedis called for the payload entry.SzGetNextFolderItemfails due to the 4-coder limit and returnsSZ_ERROR_UNSUPPORTED. The error is ignored, andentry.is_encryptedis incorrectly set tofalse(sincefolder.NumCoderswas reset to 0).ExtractFilesubsequently fails duringSzAr_DecodeFolderfor the same reason (the 4-coder limit), returningResult::kUnsupported.- In
SevenZipAnalyzer::EntryDone, the following logic is applied:Sinceif (result == seven_zip::Result::kSuccess || entry.is_encrypted) { ... }resultiskUnsupportedandentry.is_encryptedisfalse, the analyzer silently drops the entry.
The resulting Safe Browsing report sent to the backend will omit the malicious payload’s metadata (filename, size, and executable status). This suppresses potential warnings like ZIPPED_EXECUTABLE, while the malicious payload remains accessible to the user via standard desktop 7z applications that support more than 4 coders.
Impact
Successful exploitation allows malicious files to bypass Safe Browsing analysis, reducing the effectiveness of download protection. The attacker can hide executable content or encrypted payloads from the analysis infrastructure.
Suggested Fix
- Modify
SevenZipReaderImpl::IsFolderEncryptedinthird_party/lzma_sdk/google/seven_zip_reader.ccto check and handle the return value ofSzGetNextFolderItem. - Consider aligning the coder limits between the scanning pass and the decoding pass in the LZMA SDK integration to prevent parser differentials. Increasing
SZ_NUM_CODERS_IN_FOLDER_MAXto matchk_Scan_NumCoders_MAX(within safe limits) or ensuring consistent enforcement across all passes would mitigate the issue.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.