Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper state validation in Safebrowsing
DescriptionImproper state validation in Safebrowsing
ComponentSafebrowsing
Bug ClassLogic Error
Tracker517369256
Fix commit1955b6410bd6 (chromium/src) +37/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-09-08

Changed Functions

FunctionChangeNotes
TEST_F
chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
modified

Files Changed

  • chrome/services/file_util/public/cpp/sandboxed_rar_analyzer_unittest.cc
  • chrome/test/data/safe_browsing/rar/large_dictionary.rar
  • third_party/unrar/google/unrar_wrapper.cc
From 1955b6410bd6d5ab09b9513ca55eebdb2c4a63cd Mon Sep 17 00:00:00 2001
From: thefrog <thefrog@chromium.org>
Date: Wed, 29 Jul 2026 14:28:29 -0700
Subject: [PATCH] [Safe Browsing] Continue RAR enumeration past large-dict entries

UnRAR's CmdExtract::ExtractCurrentFile() refuses to unpack an entry
whose declared dictionary size exceeds the default 4 GiB limit and
returns false. RarReader::ExtractNextEntry() propagated that as
end-of-archive, so any entries following the large-dictionary header
were never reported by the analyzer.

Handle this the same way as encrypted and split-volume entries: when
extraction is refused for an over-limit dictionary, skip the entry but
still report its metadata and continue enumerating.

See the bug description for details on the contents of the archive
large_dictionary.rar.

Reviewed in https://crrev.com/i/9596797.

TAG=agy
CONV=4b630017-10db-4e6b-ba3e-fbfa9dd5e2b0

Fixed: 517369256
Change-Id: I3e8559e2b84ee7eae13da8bd720201738edc1d35
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8162728
Auto-Submit: thefrog <thefrog@chromium.org>
Commit-Queue: thefrog <thefrog@chromium.org>
SLSA-Policy-Verified: SLSA Policy Verification Service <devtools-gerritcodereview-exitgate@google.com>
Reviewed-by: Yaw Frempong <yawfrempong@google.com>
Cr-Commit-Position: refs/heads/main@{#1670599}
---

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 2993aa2..9004060 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
@@ -463,6 +463,34 @@
               testing::Eq("evil.exe"))));
 }
 
+TEST_F(SandboxedRarAnalyzerTest, AnalyzeRarWithLargeDictionaryEntry) {
+  // Verifies that an entry whose declared dictionary size exceeds UnRAR's
+  // default limit doesn't cause subsequent entries to be skipped.
+  // large_dictionary.rar contains: "readme.txt", "dummy.bin" (declares an
+  // 8 GiB dictionary), "evil.exe".
+  base::FilePath path;
+  ASSERT_NO_FATAL_FAILURE(path = GetFilePath("large_dictionary.rar"));
+
+  safe_browsing::ArchiveAnalyzerResults results;
+  AnalyzeFile(path, &results);
+
+  ASSERT_TRUE(results.success);
+  EXPECT_TRUE(results.has_executable);
+  // All three entries should be enumerated.
+  EXPECT_THAT(
+      results.archived_binary,
+      testing::UnorderedElementsAre(
+          testing::Property(
+              &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path,
+              testing::Eq("readme.txt")),
+          testing::Property(
+              &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path,
+              testing::Eq("dummy.bin")),
+          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/large_dictionary.rar b/chrome/test/data/safe_browsing/rar/large_dictionary.rar
new file mode 100644
index 0000000..16ef55fc
--- /dev/null
+++ b/chrome/test/data/safe_browsing/rar/large_dictionary.rar
Binary files differ
diff --git a/third_party/unrar/google/unrar_wrapper.cc b/third_party/unrar/google/unrar_wrapper.cc
index 762925a..7ea986f 100644
--- a/third_party/unrar/google/unrar_wrapper.cc
+++ b/third_party/unrar/google/unrar_wrapper.cc
@@ -139,6 +139,15 @@
         archive_->SeekToNext();
         return true;
       }
+
+      if (archive_->FileHead.WinSize > command_->WinSizeLimit &&
+          archive_->FileHead.WinSize > command_->WinSize) {
+        // The entry's declared dictionary size exceeds the configured limit,
+        // so unpacking was refused. Skip over this file but report the
+        // metadata we do have.
+        archive_->SeekToNext();
+        return true;
+      }
     }
   }
 
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 2993aa2..9004060 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
@@ -463,6 +463,34 @@
               testing::Eq("evil.exe"))));
 }
 
+TEST_F(SandboxedRarAnalyzerTest, AnalyzeRarWithLargeDictionaryEntry) {
+  // Verifies that an entry whose declared dictionary size exceeds UnRAR's
+  // default limit doesn't cause subsequent entries to be skipped.
+  // large_dictionary.rar contains: "readme.txt", "dummy.bin" (declares an
+  // 8 GiB dictionary), "evil.exe".
+  base::FilePath path;
+  ASSERT_NO_FATAL_FAILURE(path = GetFilePath("large_dictionary.rar"));
+
+  safe_browsing::ArchiveAnalyzerResults results;
+  AnalyzeFile(path, &results);
+
+  ASSERT_TRUE(results.success);
+  EXPECT_TRUE(results.has_executable);
+  // All three entries should be enumerated.
+  EXPECT_THAT(
+      results.archived_binary,
+      testing::UnorderedElementsAre(
+          testing::Property(
+              &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path,
+              testing::Eq("readme.txt")),
+          testing::Property(
+              &safe_browsing::ClientDownloadRequest_ArchivedBinary::file_path,
+              testing::Eq("dummy.bin")),
+          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/large_dictionary.rar b/chrome/test/data/safe_browsing/rar/large_dictionary.rar
new file mode 100644
index 0000000..16ef55fc
--- /dev/null
+++ b/chrome/test/data/safe_browsing/rar/large_dictionary.rar
Binary files differ
Loading diff…

Original Bug Report

reported by vm...@google.com

Safe Browsing RAR analyzer bypass via large dictionary size header

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 flaw in Chromium’s Safe Browsing RAR analyzer allows a crafted RAR archive to completely bypass scanning. By declaring a large dictionary size that exceeds the 4 GB internal extraction limit, the analyzer terminates extraction early but erroneously reports a valid scan status. This allows any subsequent malicious payloads in the archive to completely escape detection.

Affected files:

  • third_party/unrar/src/extract.cpp
  • third_party/unrar/google/unrar_wrapper.cc
  • chrome/utility/safe_browsing/rar_analyzer.cc

Estimated timestamp from git blame: 2024-05-21

Potential Safe Browsing RAR Scan Bypass via Crafted File Headers

Within Chromium’s Safe Browsing RAR analyzer, a potential logic flaw in handling large dictionary sizes (FileHead.WinSize) can be exploited to terminate archive parsing prematurely while still reporting a successful analysis (ArchiveAnalysisResult::kValid). This enables subsequent files inside the archive (which may contain malicious payloads) to completely bypass Safe Browsing inspection.

Root Cause Analysis

When a RAR5/RAR7 archive file header declares a dictionary size (FileHead.WinSize) exceeding the default limit of 4 GB (WinSizeLimit), CmdExtract::ExtractCurrentFile() invokes CheckWinLimit(), which in turn calls uiDictLimit():

// third_party/unrar/src/extract.cpp
bool CmdExtract::CheckWinLimit(Archive &Arc,std::wstring &ArcFileName) {
  if (Arc.FileHead.WinSize<=Cmd->WinSizeLimit || Arc.FileHead.WinSize<=Cmd->WinSize)
    return true;
  if (uiDictLimit(Cmd,ArcFileName,Arc.FileHead.WinSize,Max(Cmd->WinSizeLimit,Cmd->WinSize)))
    Cmd->WinSizeLimit=Arc.FileHead.WinSize;
  else {
    ErrHandler.SetErrorCode(RARX_FATAL);
    Arc.SeekToNext();
    return false;
  }
  return true;
}

Since Chromium compiles unrar with the SILENT macro defined, the uiDictLimit implementation in third_party/unrar/src/uisilent.cpp unconditionally returns false:

// third_party/unrar/src/uisilent.cpp
bool uiDictLimit(CommandData *Cmd,const std::wstring &FileName,uint64 DictSize,uint64 MaxDictSize) {
  return false; // Stop extracting.
}

Consequently, CheckWinLimit returns false, causing CmdExtract::ExtractCurrentFile to return false early (at third_party/unrar/src/extract.cpp:712).

An attacker can easily craft a dictionary size that is valid according to the RAR specification (which supports up to 64 GB via UNPACK_MAX_DICT) but exceeds Chromium’s internal 4 GB threshold. For instance, setting CompInfo to 0x4001 inside a RAR7 file header declares an 8 GB dictionary size. This bypasses the validation in arcread.cpp but triggers the early abort in CheckWinLimit.

Error Propagation Failure

When ExtractCurrentFile fails, the extraction loop in RarReader::ExtractNextEntry (third_party/unrar/google/unrar_wrapper.cc) terminates early and returns false:

bool RarReader::ExtractNextEntry() {
  write_error_ = false;
  bool success = true, repeat = true;
  while (success || repeat) {
    ...
    success = extractor_->ExtractCurrentFile(*archive_, header_size, repeat);
    ...
    if (success) {
      return true;
    }
    ...
  }
  return false; // Returns false because success is false.
}

Crucially, in RarAnalyzer::ResumeExtraction (chrome/utility/safe_browsing/rar_analyzer.cc), the analyzer loop interprets ExtractNextEntry() == false as having successfully reached the end of the archive (EOF), without verifying if an extraction error occurred:

bool RarAnalyzer::ResumeExtraction() {
  while (reader_.ExtractNextEntry()) {
    ...
  }

  if (reader_.HasWriteError()) {
    results()->analysis_result = ArchiveAnalysisResult::kDiskError;
    results()->success = false;
    return false;
  }
  ...
  results()->success = true;
  results()->analysis_result = ArchiveAnalysisResult::kValid;
  return true;
}

Because write_error_ remains false (as no write/disk error occurred), the entire analyzer marks the scan as successful (ArchiveAnalysisResult::kValid, success = true). Any files stored after the large dictionary entry in the RAR archive are completely skipped and ignored during inspection, while the scan is reported as fully valid.

At the same time, standard desktop extraction utilities (like 64-bit WinRAR or 7-Zip) running on a user’s machine do not have this rigid, silent 4 GB limit and will successfully unpack all files in the archive (including any subsequent malicious binaries).

Potential Step-by-Step Reproduction Flow

(Please note: the following steps are based on static analysis of the source code. Our tooling agent does not have the ability to compile or run the code dynamically to verify with an active exploit POC.)

  1. Archive Construction: Construct a sequential RAR5/RAR7 archive containing:
    • Entry 0: readme.txt (a benign text file).
    • Entry 1: dummy.bin (a crafted file header with CompInfo = 0x4001, declaring an 8 GB dictionary size).
    • Entry 2: evil.exe (the malicious payload).
  2. Download: Download this RAR archive in Google Chrome.
  3. Safe Browsing Scan: Chrome sends the archive to the sandboxed utility process for analysis via RarAnalyzer.
  4. Early Exit: Extraction fails on dummy.bin because of the 8 GB dictionary size. RarAnalyzer interprets the resulting false from ExtractNextEntry as successful completion of the archive, completely skipping over evil.exe.
  5. Clean Result: Safe Browsing reports a valid scan with readme.txt as the only content, permitting the download without any warning.

Suggested Fix

To resolve this issue, the Safe Browsing RAR analyzer should explicitly distinguish between a normal EOF and an extraction/iteration failure.

We suggest modifying RarReader to expose whether any fatal errors were recorded by ErrHandler (such as ErrHandler.GetErrorCode() != RARX_SUCCESS) after iteration finishes. If a fatal extraction error is detected, ResumeExtraction() should propagate the failure (e.g., reporting ArchiveAnalysisResult::kFailedDuringIteration or ArchiveAnalysisResult::kUnknown) and set success = false, rather than treating the early abort as a clean, successful scan.

Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8


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