CVE-2026-11210
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TEST_Fchrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc |
modified | |
ifthird_party/unrar/google/unrar_wrapper.cc |
modified |
Files Changed
chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.ccchrome/test/data/safe_browsing/rar/bypass.rarthird_party/unrar/google/unrar_wrapper.cc
Patch
From ac02d64ce9f2ce5a68dd21ca43d562553e94be7b Mon Sep 17 00:00:00 2001 From: Andrew Paseltiner <[email protected]> Date: Tue, 28 Apr 2026 10:19:48 -0700 Subject: [PATCH] [Safe Browsing] Fix UnRAR extraction bypass with '*' filename This change fixes a bug where a RAR archive containing a file named '*' could cause the UnRAR library to prematurely terminate extraction, skipping subsequent files. This was due to an early-exit optimization in UnRAR that triggered when an entry exactly matched the default extraction mask ('*'). We fix this by forcing the Recurse flag to true in the RarReader wrapper, which disables this specific optimization. Fixed: 506473226 Change-Id: I566a1b1e5796136a2e9da623b2e5a1a5aa72e0fb Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7800438 Commit-Queue: Andrew Paseltiner <[email protected]> Reviewed-by: Daniel Rubery <[email protected]> Cr-Commit-Position: refs/heads/main@{#1621871} --- diff --git a/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc b/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc index 3123cfa..2993aa2 100644 --- a/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc +++ b/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc @@ -438,6 +438,31 @@ EXPECT_TRUE(results.archived_archive_filenames.empty()); } +TEST_F(SandboxedRarAnalyzerTest, AnalyzeRarWithAsterisk) { + // Verifies that a file named "*" doesn't trigger the exact match early exit + // in UnRAR, which would cause subsequent files to be skipped. + // See crbug.com/506473226. + // bypass.rar contains: "*", "evil.exe" + base::FilePath path; + ASSERT_NO_FATAL_FAILURE(path = GetFilePath("bypass.rar")); + + safe_browsing::ArchiveAnalyzerResults results; + AnalyzeFile(path, &results); + + ASSERT_TRUE(results.success); + EXPECT_TRUE(results.has_executable); + // Both "*" and "evil.exe" should be found. + EXPECT_THAT( + results.archived_binary, + testing::UnorderedElementsAre( + testing::Property( + &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path, + testing::Eq("*")), + testing::Property( + &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path, + testing::Eq("evil.exe")))); +} + TEST_F(SandboxedRarAnalyzerTest, CanDeleteDuringExecution) { base::FilePath file_path; ASSERT_NO_FATAL_FAILURE(file_path = GetFilePath("small_archive.rar")); diff --git a/chrome/test/data/safe_browsing/rar/bypass.rar b/chrome/test/data/safe_browsing/rar/bypass.rar new file mode 100644 index 0000000..44e3d32 --- /dev/null +++ b/chrome/test/data/safe_browsing/rar/bypass.rar Binary files differ diff --git a/third_party/unrar/google/unrar_wrapper.cc b/third_party/unrar/google/unrar_wrapper.cc index 2ba945f..762925a 100644 --- a/third_party/unrar/google/unrar_wrapper.cc +++ b/third_party/unrar/google/unrar_wrapper.cc @@ -64,14 +64,16 @@ L"-p" + (password_.empty() ? L"x" : base::UTF8ToWide(password_)); command_->ParseArg(password_flag.data()); command_->ParseArg(const_cast<wchar_t*>(L"t")); + command_->ParseDone(); + // Disables an optimization that can allow specially crafted archives to + // bypass analysis. See crbug.com/506473226. + command_->Recurse = RECURSE_ALWAYS; if (!writer_) { // If no custom writer is set, use the default FileWriter writing to temp_file. writer_ = std::make_unique<FileWriter>(temp_file_.Duplicate()); } - command_->ParseDone(); - archive_ = std::make_unique<Archive>(command_.get()); archive_->SetReaderDelegate(reader_.get()); archive_->SetWriterDelegate(writer_.get());
Regression Test / PoC
diff --git a/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc b/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
index 3123cfa..2993aa2 100644
--- a/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
+++ b/chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
@@ -438,6 +438,31 @@
EXPECT_TRUE(results.archived_archive_filenames.empty());
}
+TEST_F(SandboxedRarAnalyzerTest, AnalyzeRarWithAsterisk) {
+ // Verifies that a file named "*" doesn't trigger the exact match early exit
+ // in UnRAR, which would cause subsequent files to be skipped.
+ // See crbug.com/506473226.
+ // bypass.rar contains: "*", "evil.exe"
+ base::FilePath path;
+ ASSERT_NO_FATAL_FAILURE(path = GetFilePath("bypass.rar"));
+
+ safe_browsing::ArchiveAnalyzerResults results;
+ AnalyzeFile(path, &results);
+
+ ASSERT_TRUE(results.success);
+ EXPECT_TRUE(results.has_executable);
+ // Both "*" and "evil.exe" should be found.
+ EXPECT_THAT(
+ results.archived_binary,
+ testing::UnorderedElementsAre(
+ testing::Property(
+ &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path,
+ testing::Eq("*")),
+ testing::Property(
+ &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path,
+ testing::Eq("evil.exe"))));
+}
+
TEST_F(SandboxedRarAnalyzerTest, CanDeleteDuringExecution) {
base::FilePath file_path;
ASSERT_NO_FATAL_FAILURE(file_path = GetFilePath("small_archive.rar"));
diff --git a/chrome/test/data/safe_browsing/rar/bypass.rar b/chrome/test/data/safe_browsing/rar/bypass.rar
new file mode 100644
index 0000000..44e3d32
--- /dev/null
+++ b/chrome/test/data/safe_browsing/rar/bypass.rar
Binary files differ
Original Bug Report
Safe Browsing RAR analysis bypass via archive entry named '*'
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 Chrome’s UnRAR wrapper allows a specially crafted RAR archive to prematurely terminate extraction. By naming the first entry in an archive exactly ‘*’, an attacker can trigger an early exit optimization in UnRAR, silently bypassing Safe Browsing download protection for all subsequent files.
Affected files:
third_party/unrar/src/extract.cppthird_party/unrar/src/cmddata.cppthird_party/unrar/src/cmdfilter.cppthird_party/unrar/google/unrar_wrapper.cc
Estimated timestamp from git blame: 2025-12-16
A deterministic logic bug in Chrome’s UnRAR integration allows for a complete bypass of Safe Browsing download protection. The issue arises from how Chrome initializes the UnRAR library without providing specific file extraction arguments, inadvertently triggering an exact-match optimization within UnRAR.
Root Cause Analysis
In Chrome’s Safe Browsing implementation, the UnRAR wrapper (third_party/unrar/google/unrar_wrapper.cc) initializes the extraction process without providing specific file paths to extract. Consequently, CommandData::ParseDone() in third_party/unrar/src/cmddata.cpp defaults the file arguments to MASKALL (the literal character *).
// third_party/unrar/src/cmddata.cpp
void CommandData::ParseDone()
{
if (FileArgs.ItemsCount()==0 && !FileLists)
FileArgs.AddString(MASKALL); // MASKALL is L"*"
During extraction, CommandData::IsProcessFile() in third_party/unrar/src/cmdfilter.cpp compares the current archive entry’s filename against the requested file arguments. If an attacker crafts an archive where the first entry’s name is literally *, the library treats this as an “exact match” because it matches the default MASKALL argument via direct string comparison (wcsicompc("*", "*") == 0).
// third_party/unrar/src/cmdfilter.cpp
if (ExactMatch!=NULL)
*ExactMatch=wcsicompc(ArgName,FileHead.FileName)==0;
In third_party/unrar/src/extract.cpp, there is a logic check intended to optimize extraction by stopping once all specifically requested files have been found:
// third_party/unrar/src/extract.cpp
if (!Cmd->Recurse && MatchedArgs>=Cmd->FileArgs.ItemsCount() && AllMatchesExact)
return false;
When processing an archive starting with a file named *:
- The
*entry is processed successfully. BecauseEqualNameswas evaluated as true,AllMatchesExactremainstrue. At the end of processing,MatchedArgsis incremented to1. - On the next loop iteration,
ExtractNextEntry()attempts to process the second file (e.g.,malware.exe). ExtractCurrentFile()evaluates the early-exit condition. BecauseMatchedArgs(1)>=Cmd->FileArgs.ItemsCount()(1), andAllMatchesExactistrue, the condition is met.- Extraction terminates prematurely, returning
falseto the caller.
Impact
RarAnalyzer::ResumeExtraction relies on reader_.ExtractNextEntry() returning false to signal the end of the archive. Because RarReader lacks a mechanism to distinguish between a legitimate end-of-archive and an early abort, the analyzer assumes the file was fully scanned.
It sets the analysis result to ArchiveAnalysisResult::kValid. Any subsequent malicious payloads (e.g., malware.exe) are skipped entirely. Their hashes and metadata are never computed or sent to the Safe Browsing server, resulting in a silent bypass of download protection.
Potential Reproduction Steps
(Note: These are suggested steps based on code analysis; our tooling cannot run live PoCs.)
- Create a RAR archive where the first file entry is a benign file named
*. - Add a malicious executable (e.g.,
evil.exe) as the second entry in the archive. - Host the archive and download it using Chrome with Safe Browsing enabled.
- Observe that the Safe Browsing analysis only identifies the
*file and reports the archive as valid, ignoring theevil.exepayload.
Suggested Fix
The most robust fix for Chrome’s specific use case is to explicitly disable the exact-match optimization early exit, as Chrome’s analyzer always wants to scan the entire archive regardless of matches.
This can be done in third_party/unrar/google/unrar_wrapper.cc by forcing the Recurse flag to true during initialization (e.g., command_->ParseArg(const_cast<wchar_t*>(L"r"));), as !Cmd->Recurse is a requirement for the early exit to trigger. Alternatively, extract.cpp could be patched to disable the early exit branch entirely for Chrome builds.
Evaluated with Chrome root at commit: a1e33f5848218e21d4a16ae2c1bc94e815c30c7f
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.