Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient policy enforcement in Safe Browsing
DescriptionInsufficient policy enforcement in Safe Browsing
ComponentSafe Browsing
Bug ClassLogic Error
Tracker506473226
Fix commitac02d64ce9f2 (chromium/src) +29/-2
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
modified
if
third_party/unrar/google/unrar_wrapper.cc
modified

Files Changed

  • chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
  • chrome/test/data/safe_browsing/rar/bypass.rar
  • third_party/unrar/google/unrar_wrapper.cc
From ac02d64ce9f2ce5a68dd21ca43d562553e94be7b Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <apaseltiner@chromium.org>
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 <apaseltiner@chromium.org>
Reviewed-by: Daniel Rubery <drubery@chromium.org>
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());
Loading diff…

Regression Test / PoC

shipped with the fix
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
Loading diff…

Original Bug Report

reported by vm...@google.com

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.cpp
  • third_party/unrar/src/cmddata.cpp
  • third_party/unrar/src/cmdfilter.cpp
  • third_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 *:

  1. The * entry is processed successfully. Because EqualNames was evaluated as true, AllMatchesExact remains true. At the end of processing, MatchedArgs is incremented to 1.
  2. On the next loop iteration, ExtractNextEntry() attempts to process the second file (e.g., malware.exe).
  3. ExtractCurrentFile() evaluates the early-exit condition. Because MatchedArgs (1) >= Cmd->FileArgs.ItemsCount() (1), and AllMatchesExact is true, the condition is met.
  4. Extraction terminates prematurely, returning false to 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.)

  1. Create a RAR archive where the first file entry is a benign file named *.
  2. Add a malicious executable (e.g., evil.exe) as the second entry in the archive.
  3. Host the archive and download it using Chrome with Safe Browsing enabled.
  4. Observe that the Safe Browsing analysis only identifies the * file and reports the archive as valid, ignoring the evil.exe payload.

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.

View on issue tracker