Medium watch CVSS 5.4 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
5.4
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactImproper state validation in Safebrowsing
DescriptionImproper state validation in Safebrowsing
ComponentSafebrowsing
Sub-componentUnrar
Bug ClassInput Validation
Action tierwatch — dos-class only — no demonstrated corruption path
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker502611474
Fix commit96dfd842b141 (chromium/src) +45/-2
CWECWE-754
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedGoogle
Reported2026-04-14 (per vendor advisory)
Fix landed2026-07-30 (public git)
Disclosed2026-09-08
Patch gap40 days — fix public in source before the advisory

Background

Safe Browsing archive analysis
Chrome inspects downloaded archives (e.g. RAR) in the sandboxed file_util service to enumerate their entries and flag executables before the file is opened.
`third_party/unrar`
the bundled UnRAR decompressor, driven through Chrome’s unrar_wrapper.cc shim which sets UnRAR command options and iterates entries via ExtractNextEntry.
PBKDF2 key derivation (KDF)
RAR5 header/entry encryption derives a key with PBKDF2-HMAC-SHA256, whose cost is 2^Lg2Count iterations per distinct salt; Lg2Count = 24 means 16,777,216 iterations for each salt.
`command_->SkipEncrypted` / `FileHead.Encrypted`
an UnRAR command flag that tells UnRAR to report encrypted members as undecryptable instead of trying to decrypt them, and the per-entry header bit indicating the entry is encrypted.

Root Cause Analysis

When analyzing a header-encrypted RAR archive for which no caller password is available, unrar_wrapper.cc still fed UnRAR a dummy placeholder password and called ExtractCurrentFile for each entry. Because a placeholder password can never succeed, this ran full PBKDF2-HMAC-SHA256 key derivation for every encrypted header and entry purely to fail verification. A malicious archive that sets the maximum iteration exponent (Lg2Count = 24, i.e. 2^24 iterations) on many entries, each with a distinct salt, forces the analyzer to perform tens of millions of hash iterations per entry on the sequenced task runner, an attacker-controlled algorithmic-complexity cost with no security benefit.

The fix sets command_->SkipEncrypted = true when password_.empty() so UnRAR reports encrypted members as undecryptable without deriving keys, and additionally short-circuits the extraction loop with skip_encrypted_entry so encrypted HEAD_FILE entries take the metadata-reporting path (success = !skip_encrypted_entry && ...) instead of calling ExtractCurrentFile. The invariant restored is that no key derivation should run when there is no candidate password to verify.

Key insight
The core mistake was attempting decryption (and therefore expensive KDF) with a guaranteed-to-fail dummy password whose iteration count and salt count are fully attacker-controlled; the fix simply skips key derivation entirely when no real password exists, which is exactly when derivation could never help.

Attack Path

  1. Craft a high-cost encrypted RAR An attacker builds a RAR5 archive whose encrypted file headers carry FHEXTRA_CRYPT records set to the maximum Lg2Count = 24 (2^24 PBKDF2 iterations) with many distinct 16-byte salts, plus benign padding entries.
  2. Deliver the archive to a victim The file is downloaded (or otherwise routed through Safe Browsing download protection), triggering archive analysis in the sandboxed file_util service with no user-supplied password.
  3. Force redundant key derivation For each encrypted entry the pre-patch unrar_wrapper.cc calls ExtractCurrentFile with the dummy password, and UnRAR runs a full PBKDF2 derivation per entry/salt that can only ever fail.
  4. Exhaust CPU on the task runner The many maximum-iteration derivations consume large amounts of CPU on the sequenced task runner, delaying or effectively stalling analysis (a denial-of-service / resource-exhaustion condition) (inferred as the terminal effect from the commit’s “wastes significant CPU time” rationale and the regression test’s focus on quick enumeration).

Impact Assessment

An attacker gains an attacker-controlled CPU/resource-exhaustion (denial-of-service) primitive inside the sandboxed file_util analysis service by supplying a RAR archive with maximum KDF iteration counts and many salts, with the sole precondition that the archive reaches Safe Browsing archive analysis without a supplied password. The patch demonstrates only wasted key-derivation work; there is no memory corruption or code-execution in the diff, and any escalation beyond denial-of-service is (inferred) and not established. The advisory classifies this as improper state validation in Safe Browsing and does not report exploitation in the wild.

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
  • third_party/unrar/google/unrar_wrapper.cc
  • chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar

Audit Directions

  • Failure-guaranteed cryptographic work
    Flag any code that runs key derivation, signature checks, or other expensive crypto with a placeholder/dummy input that cannot succeed; skip the work when the required secret is absent.
  • Attacker-controlled cost parameters
    Audit parsers that honor archive/file-supplied iteration counts, salt counts, sizes, or repetition fields (e.g. Lg2Count, entry counts) without capping the total work performed during untrusted-input analysis.
  • Analyzer resource bounds
    Review Safe Browsing archive analyzers (RAR, ZIP, 7z, DMG) running on shared sequenced task runners for missing per-file or per-archive CPU/time/entry budgets that let one crafted input monopolize the runner.
From 96dfd842b1413173623925072864a326d629577d Mon Sep 17 00:00:00 2001
From: Sarah Krakowiak Criel <[email protected]>
Date: Thu, 30 Jul 2026 12:14:33 -0700
Subject: [PATCH] unrar: skip key derivation when no password is supplied

When opening header-encrypted RAR archives without a caller-supplied
password, UnRAR attempts decryption using a dummy placeholder password.
This causes UnRAR to execute PBKDF2-HMAC-SHA256 for every encrypted
header and entry, even though decryption will never succeed. On archives
specifying the maximum KDF iteration exponent (2^24) and distinct salts,
this wastes significant CPU time on the sequenced task runner.

Set UnRAR's SkipEncrypted option when no password is provided so that
encrypted archive headers are reported as undecryptable immediately
without running KDF. Also short-circuit ExtractNextEntry for encrypted
file headers when password is empty to take the metadata reporting path
directly without attempting key derivation.

Add a unit test (AnalyzeEncryptedRarMaxIterationCount) and test archive
to verify entries are enumerated quickly without executing key
derivation.

The test archive
(chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar) was
generated by constructing a RAR5 archive with 8 dummy encrypted file
entries (e0.dat .. e7.dat) and 1 trailing unencrypted executable payload
(payload.exe). Each encrypted entry's FHEXTRA_CRYPT record was set to
the maximum KDF iteration exponent (Lg2Count = 24, corresponding to 2^24
= 16,777,216 PBKDF2 iterations) with distinct 16-byte salts.

TAG=agy

Bug: 502611474
Change-Id: Idc77ca1d39970cf17c2f8f5565e66c12bf496b9e
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8160612
Reviewed-by: Yaw Frempong <[email protected]>
Commit-Queue: Sarah Krakowiak <[email protected]>
SLSA-Policy-Verified: SLSA Policy Verification Service <[email protected]>
Auto-Submit: Sarah Krakowiak <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1671306}
---
diff --git a/third_party/unrar/google/unrar_wrapper.cc b/third_party/unrar/google/unrar_wrapper.cc
index 7ea986f..6fd9e9b9 100644
--- a/third_party/unrar/google/unrar_wrapper.cc
+++ b/third_party/unrar/google/unrar_wrapper.cc
@@ -65,6 +65,14 @@
   command_->ParseArg(password_flag.data());
   command_->ParseArg(const_cast<wchar_t*>(L"t"));
   command_->ParseDone();
+  if (password_.empty()) {
+    // Without a real password there is nothing useful key derivation can
+    // produce, so tell UnRAR to skip encrypted headers and entries instead of
+    // attempting decryption with the dummy password above. The work avoided
+    // scales with the archive's KDF iteration parameter and the number of
+    // distinct salts.
+    command_->SkipEncrypted = true;
+  }
   // Disables an optimization that can allow specially crafted archives to
   // bypass analysis. See crbug.com/506473226.
   command_->Recurse = RECURSE_ALWAYS;
@@ -100,8 +108,15 @@
     temp_file_.SetLength(0);
     size_t header_size = archive_->ReadHeader();
     repeat = false;
-    success = extractor_->ExtractCurrentFile(
-        *archive_, header_size, repeat);  // |repeat| is passed by reference
+    // When no password was supplied, attempting to decrypt the entry will
+    // never succeed; skip extraction and report the available metadata.
+    bool skip_encrypted_entry = password_.empty() &&
+                                archive_->GetHeaderType() == HEAD_FILE &&
+                                archive_->FileHead.Encrypted;
+    success =
+        !skip_encrypted_entry &&
+        extractor_->ExtractCurrentFile(
+            *archive_, header_size, repeat);  // |repeat| is passed by reference
 
     if (archive_->GetHeaderType() == HEAD_FILE) {
 #if defined(OS_WIN)
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 9004060..15c3c5e 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
@@ -281,6 +281,34 @@
             EncryptionInfo::kKnownIncorrect);
 }
 
+TEST_F(SandboxedRarAnalyzerTest, AnalyzeEncryptedRarMaxIterationCount) {
+  // Can quickly enumerate entries in an archive whose encrypted file headers
+  // specify the maximum permitted KDF iteration count, even though no
+  // password is supplied. Key derivation is unnecessary in this case because
+  // there is no candidate password to verify.
+  // encrypted_high_kdf.rar contains: e0.dat .. e7.dat (encrypted), payload.exe
+  base::FilePath path;
+  ASSERT_NO_FATAL_FAILURE(path = GetFilePath("encrypted_high_kdf.rar"));
+
+  safe_browsing::ArchiveAnalyzerResults results;
+  AnalyzeFile(path, &results);
+
+  ASSERT_TRUE(results.success);
+  EXPECT_TRUE(results.has_executable);
+  ASSERT_EQ(results.archived_binary.size(), 9);
+  EXPECT_EQ(results.archived_binary[0].file_path(), "e0.dat");
+  EXPECT_TRUE(results.archived_binary[0].is_encrypted());
+  EXPECT_EQ(results.archived_binary[7].file_path(), "e7.dat");
+  EXPECT_TRUE(results.archived_binary[7].is_encrypted());
+  EXPECT_EQ(results.archived_binary[8].file_path(), "payload.exe");
+  EXPECT_TRUE(results.archived_binary[8].is_executable());
+  EXPECT_FALSE(results.archived_binary[8].is_encrypted());
+
+  EXPECT_TRUE(results.encryption_info.is_encrypted);
+  EXPECT_EQ(results.encryption_info.password_status,
+            EncryptionInfo::kKnownIncorrect);
+}
+
 TEST_F(SandboxedRarAnalyzerTest, AnalyzeRarWithPasswordMultipleFiles) {
   // Can list files inside an archive that has password protected data.
   // passwd1234_two_files.rar contains 2 files: signed.exe and text.txt
diff --git a/chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar b/chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar
new file mode 100644
index 0000000..6e215af
--- /dev/null
+++ b/chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar
Binary files differ
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 9004060..15c3c5e 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
@@ -281,6 +281,34 @@
             EncryptionInfo::kKnownIncorrect);
 }
 
+TEST_F(SandboxedRarAnalyzerTest, AnalyzeEncryptedRarMaxIterationCount) {
+  // Can quickly enumerate entries in an archive whose encrypted file headers
+  // specify the maximum permitted KDF iteration count, even though no
+  // password is supplied. Key derivation is unnecessary in this case because
+  // there is no candidate password to verify.
+  // encrypted_high_kdf.rar contains: e0.dat .. e7.dat (encrypted), payload.exe
+  base::FilePath path;
+  ASSERT_NO_FATAL_FAILURE(path = GetFilePath("encrypted_high_kdf.rar"));
+
+  safe_browsing::ArchiveAnalyzerResults results;
+  AnalyzeFile(path, &results);
+
+  ASSERT_TRUE(results.success);
+  EXPECT_TRUE(results.has_executable);
+  ASSERT_EQ(results.archived_binary.size(), 9);
+  EXPECT_EQ(results.archived_binary[0].file_path(), "e0.dat");
+  EXPECT_TRUE(results.archived_binary[0].is_encrypted());
+  EXPECT_EQ(results.archived_binary[7].file_path(), "e7.dat");
+  EXPECT_TRUE(results.archived_binary[7].is_encrypted());
+  EXPECT_EQ(results.archived_binary[8].file_path(), "payload.exe");
+  EXPECT_TRUE(results.archived_binary[8].is_executable());
+  EXPECT_FALSE(results.archived_binary[8].is_encrypted());
+
+  EXPECT_TRUE(results.encryption_info.is_encrypted);
+  EXPECT_EQ(results.encryption_info.password_status,
+            EncryptionInfo::kKnownIncorrect);
+}
+
 TEST_F(SandboxedRarAnalyzerTest, AnalyzeRarWithPasswordMultipleFiles) {
   // Can list files inside an archive that has password protected data.
   // passwd1234_two_files.rar contains 2 files: signed.exe and text.txt
diff --git a/chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar b/chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar
new file mode 100644
index 0000000..6e215af
--- /dev/null
+++ b/chrome/test/data/safe_browsing/rar/encrypted_high_kdf.rar
Binary files differ
Loading diff…

Original Bug Report

reported by [email protected]

Safe Browsing bypass via RAR PBKDF2 iteration-count exhaustion

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.

Overview: A logic flaw in Chrome’s Safe Browsing RAR analysis allows attackers to bypass security warnings. By using maximum PBKDF2 iteration counts in encrypted entries, an attacker can cause a synchronous hang in the utility process. This leads to a browser-side timeout and a fail-open result, allowing malicious files to be downloaded without a warning.

Affected files:

  • chrome/utility/safe_browsing/rar_analyzer.cc
  • chrome/services/file_util/safe_archive_analyzer.cc
  • third_party/unrar/src/crypt.hpp
  • third_party/unrar/google/unrar_wrapper.cc
  • chrome/browser/download/chrome_download_manager_delegate.cc
  • chrome/browser/safe_browsing/download_protection/check_client_download_request_base.cc
  • third_party/unrar/src/arcread.cpp
  • third_party/unrar/src/crypt5.cpp
  • third_party/unrar/src/extract.cpp
  • components/safe_browsing/content/resources/download_file_types.asciipb

Estimated timestamp from git blame: 2025-12-17

Summary

A potential vulnerability exists in the RAR archive analysis within Safe Browsing. A crafted RAR5 archive with high KDF iteration counts can block the utility process’s main thread. This prevents the analysis from completing, triggering a browser-side timeout that defaults to a safe classification (fail-open), thereby bypassing malware checks.

Technical Details

The bypass occurs due to the intersection of several behaviors across the unrar library and Chrome’s archive analysis architecture:

  1. High KDF Iteration Limit: In third_party/unrar/src/crypt.hpp, CRYPT5_KDF_LG2_COUNT_MAX is set to 24 in production builds. This allows an archive to specify $2^{24}$ (approximately 16.7 million) PBKDF2 iterations per encrypted file.
  2. Unconditional PBKDF2 Execution: When Safe Browsing analyzes an archive, third_party/unrar/google/unrar_wrapper.cc provides a default dummy password (“x”) if no password is provided by the user. The wrapper attempts to extract every entry by calling extractor_->ExtractCurrentFile. This unconditionally triggers the synchronous PBKDF2 loop in third_party/unrar/src/crypt5.cpp for every encrypted entry with a unique salt. Without hardware acceleration, this takes 10-15 seconds per entry.
  3. Blocked Synchronous Watchdog: The extraction loop runs synchronously on the utility process’s main sequence. The 10-second watchdog timer (kArchiveAnalysisTimeout in SafeArchiveAnalyzer) is also scheduled on this exact same sequence. Consequently, the timer is blocked by the pbkdf2 loop and cannot interrupt the execution.
  4. Browser Fail-Open Behavior: While the utility process is stalled, the browser-side 7-second timeout (kDownloadRequestTimeoutMs) expires. This cancels the Safe Browsing request with a result of DownloadCheckResult::UNKNOWN. Because .rar files do not have an explicit DANGEROUS level in the download_file_types.asciipb configuration, ChromeDownloadManagerDelegate defaults UNKNOWN results to DOWNLOAD_DANGER_TYPE_NOT_DANGEROUS. The download completes with no security warning.

Suggested Attacker Steps

(Note: These are suggested steps based on code analysis, as our tooling agent does not currently have the ability to run code to produce a working proof-of-concept.)

  1. Create a malicious RAR5 archive containing multiple dummy encrypted entries.
  2. For each encrypted dummy entry, modify the HEAD_CRYPT block to set the Lg2Count field to 24 and provide a unique salt (to bypass the unrar KDF cache).
  3. Append a known-malicious, unencrypted payload (e.g., a malware executable) as the final entry in the archive.
  4. Host the archive and induce a victim to download it in Chrome with Standard Safe Browsing enabled.
  5. The utility process will hang processing the dummy entries, the browser’s 7-second timeout will expire, and the malicious payload will be saved to disk without any warnings.

Suggested Fix

  1. Skip Extraction for Encrypted Files: In third_party/unrar/google/unrar_wrapper.cc, check archive_->FileHead.Encrypted before calling extractor_->ExtractCurrentFile. If the file is encrypted and no valid password was provided by the user, skip the extraction attempt entirely. This avoids triggering the expensive PBKDF2 calculation for unknown passwords.
  2. Isolate Watchdog Timer: Modify SafeArchiveAnalyzer to run its timeout_timer_ on a separate sequence or thread so that it can effectively terminate the analysis even if the main utility thread is blocked in a synchronous loop.

Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b


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