Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect security UI in Downloads
DescriptionIncorrect security UI in Downloads
ComponentDownloads
Bug ClassLogic Error
Tracker497394061
Fix commit0b7e6fe4e01e (chromium/src) +43/-5
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-02

Changed Functions

FunctionChangeNotes
TEST_F
chrome/browser/download/chrome_download_manager_delegate_unittest.cc
modified
for
chrome/browser/download/chrome_download_manager_delegate_unittest.cc
modified

Files Changed

  • chrome/browser/download/chrome_download_manager_delegate_unittest.cc
  • chrome/browser/download/insecure_download_blocking.cc
From 0b7e6fe4e01ec65708ab649386e79401d786fe04 Mon Sep 17 00:00:00 2001
From: Andrew Paseltiner <[email protected]>
Date: Thu, 30 Apr 2026 09:08:27 -0700
Subject: [PATCH] Fix bitwise logic error in insecure download blocking

The core PageTransition values are sequential integers, not mutually
exclusive bitmasks. Using bitwise AND on these values (like TYPED,
AUTO_SUBFRAME, FORM_SUBMIT) led to incorrect classification of
navigation types, causing attacker-controlled downloads to bypass
mixed-content blocking.

This bug was introduced in crrev.com/c/1962532, which did not add test
coverage for these transition types.

This CL replaces bitwise AND operations with
ui::PageTransitionCoreTypeIs() for core transition types. Qualifiers
continue to use bitwise AND.

Fixed: 497394061
Change-Id: Ic77becef0f68497da0f744248594653aa87d2cbf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7807927
Commit-Queue: Andrew Paseltiner <[email protected]>
Reviewed-by: Xinghui Lu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1623256}
---

diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index 20fd3da0..7976113 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -1591,6 +1591,39 @@
   }
 }
 
+// Regression test for crbug.com/497394061. PageTransition core values are
+// sequential integers, not bitmasks. This test ensures that transition types
+// like AUTO_SUBFRAME and FORM_SUBMIT are not incorrectly flagged as TYPED or
+// RELOAD due to bitwise logic errors, which would cause a security downgrade
+// for mixed-content downloads.
+TEST_F(ChromeDownloadManagerDelegateTest, InsecureDownloadsBlocked_Regression) {
+  const GURL kInsecureFile("http://example.com/foo");
+  const auto kSecureOrigin = Origin::Create(GURL("https://example.org"));
+
+  // Regression test for bitwise logic error in PageTransition core types.
+  // PAGE_TRANSITION_AUTO_SUBFRAME (3) and PAGE_TRANSITION_FORM_SUBMIT (7)
+  // should not be incorrectly flagged as TYPED (1) or RELOAD (8).
+  const ui::PageTransition kTransitionTypes[] = {
+      ui::PAGE_TRANSITION_AUTO_SUBFRAME,
+      ui::PAGE_TRANSITION_FORM_SUBMIT,
+  };
+
+  for (auto transition_type : kTransitionTypes) {
+    std::unique_ptr<download::MockDownloadItem> download_item =
+        PrepareDownloadItemForInsecureBlocking(kInsecureFile, kSecureOrigin,
+                                               std::nullopt);
+    ON_CALL(*download_item, GetTransitionType())
+        .WillByDefault(::testing::Return(transition_type));
+
+    download::DownloadTargetInfo target_info =
+        DetermineDownloadTarget(download_item.get());
+
+    EXPECT_EQ(download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK,
+              target_info.insecure_download_status)
+        << "Failed for transition type: " << transition_type;
+  }
+}
+
 // Verify that insecure downloads not blocked normally are blocked when
 // HTTPS-First mode is enabled.
 TEST_F(ChromeDownloadManagerDelegateTest,
diff --git a/chrome/browser/download/insecure_download_blocking.cc b/chrome/browser/download/insecure_download_blocking.cc
index b79cb91..cfedddb9 100644
--- a/chrome/browser/download/insecure_download_blocking.cc
+++ b/chrome/browser/download/insecure_download_blocking.cc
@@ -218,12 +218,16 @@
     auto download_source = item->GetDownloadSource();
     auto transition_type = item->GetTransitionType();
     if (download_source == DownloadSource::RETRY ||
-        (transition_type & ui::PAGE_TRANSITION_RELOAD) ||
-        (transition_type & ui::PAGE_TRANSITION_TYPED) ||
+        ui::PageTransitionCoreTypeIs(transition_type,
+                                     ui::PAGE_TRANSITION_RELOAD) ||
+        ui::PageTransitionCoreTypeIs(transition_type,
+                                     ui::PAGE_TRANSITION_TYPED) ||
         (transition_type & ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) ||
         (transition_type & ui::PAGE_TRANSITION_FORWARD_BACK) ||
-        (transition_type & ui::PAGE_TRANSITION_AUTO_TOPLEVEL) ||
-        (transition_type & ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
+        ui::PageTransitionCoreTypeIs(transition_type,
+                                     ui::PAGE_TRANSITION_AUTO_TOPLEVEL) ||
+        ui::PageTransitionCoreTypeIs(transition_type,
+                                     ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
         (transition_type & ui::PAGE_TRANSITION_FROM_API) ||
         download_source == DownloadSource::OFFLINE_PAGE ||
         download_source == DownloadSource::INTERNAL_API ||
@@ -254,7 +258,8 @@
     // downloads. For example, downloads are blocked even if they're initiated
     // from the omnibox.
     if (download_source == DownloadSource::RETRY ||
-        (transition_type & ui::PAGE_TRANSITION_RELOAD) ||
+        ui::PageTransitionCoreTypeIs(transition_type,
+                                     ui::PAGE_TRANSITION_RELOAD) ||
         (transition_type & ui::PAGE_TRANSITION_FROM_API) ||
         download_source == DownloadSource::OFFLINE_PAGE ||
         download_source == DownloadSource::INTERNAL_API ||
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
index 20fd3da0..7976113 100644
--- a/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
+++ b/chrome/browser/download/chrome_download_manager_delegate_unittest.cc
@@ -1591,6 +1591,39 @@
   }
 }
 
+// Regression test for crbug.com/497394061. PageTransition core values are
+// sequential integers, not bitmasks. This test ensures that transition types
+// like AUTO_SUBFRAME and FORM_SUBMIT are not incorrectly flagged as TYPED or
+// RELOAD due to bitwise logic errors, which would cause a security downgrade
+// for mixed-content downloads.
+TEST_F(ChromeDownloadManagerDelegateTest, InsecureDownloadsBlocked_Regression) {
+  const GURL kInsecureFile("http://example.com/foo");
+  const auto kSecureOrigin = Origin::Create(GURL("https://example.org"));
+
+  // Regression test for bitwise logic error in PageTransition core types.
+  // PAGE_TRANSITION_AUTO_SUBFRAME (3) and PAGE_TRANSITION_FORM_SUBMIT (7)
+  // should not be incorrectly flagged as TYPED (1) or RELOAD (8).
+  const ui::PageTransition kTransitionTypes[] = {
+      ui::PAGE_TRANSITION_AUTO_SUBFRAME,
+      ui::PAGE_TRANSITION_FORM_SUBMIT,
+  };
+
+  for (auto transition_type : kTransitionTypes) {
+    std::unique_ptr<download::MockDownloadItem> download_item =
+        PrepareDownloadItemForInsecureBlocking(kInsecureFile, kSecureOrigin,
+                                               std::nullopt);
+    ON_CALL(*download_item, GetTransitionType())
+        .WillByDefault(::testing::Return(transition_type));
+
+    download::DownloadTargetInfo target_info =
+        DetermineDownloadTarget(download_item.get());
+
+    EXPECT_EQ(download::DownloadItem::InsecureDownloadStatus::SILENT_BLOCK,
+              target_info.insecure_download_status)
+        << "Failed for transition type: " << transition_type;
+  }
+}
+
 // Verify that insecure downloads not blocked normally are blocked when
 // HTTPS-First mode is enabled.
 TEST_F(ChromeDownloadManagerDelegateTest,
Loading diff…

Original Bug Report

reported by [email protected]

Security UI downgrade in mixed downloads due to PageTransition bitwise logic error

Project Fortify, an experimental security project, has identified the following potential security issue.

Overview: A bitwise logic error in insecure download blocking incorrectly evaluates PageTransition core values as bitmasks rather than sequential integers. This causes attacker-controlled navigations (like form submissions or subframes) to incorrectly bypass mixed-content checks. Consequently, an intended hard block (SILENT_BLOCK) for dangerous files is downgraded to a bypassable UI warning (BLOCK).

Affected files:

  • chrome/browser/download/insecure_download_blocking.cc
  • ui/base/page_transition_types.h

Estimated timestamp from git blame: 2022-12-29

Description

There is a potential security downgrade in Chrome’s mixed-content download blocking logic.

In chrome/browser/download/insecure_download_blocking.cc, the InsecureDownloadData constructor determines if a download should be exempt from mixed-content blocking by checking its transition_type against several ui::PageTransition values using bitwise AND (&) operations:

    if (download_source == DownloadSource::RETRY ||
        (transition_type & ui::PAGE_TRANSITION_RELOAD) ||
        (transition_type & ui::PAGE_TRANSITION_TYPED) ||
        // ...

However, as defined in ui/base/page_transition_types.h, the core PageTransition values (like PAGE_TRANSITION_TYPED = 1, PAGE_TRANSITION_AUTO_SUBFRAME = 3, PAGE_TRANSITION_AUTO_TOPLEVEL = 6, and PAGE_TRANSITION_FORM_SUBMIT = 7) are sequential integers, not mutually exclusive bitmasks. The core transition type is stored in the lower 8 bits (mask 0xFF).

Because of this, any core transition type that shares bits with the checked values will incorrectly evaluate to true. For example:

  • A form submission (PAGE_TRANSITION_FORM_SUBMIT, value 7) bitwise-ANDed with PAGE_TRANSITION_TYPED (value 1) evaluates to 1 (truthy).
  • An automatic subframe navigation (PAGE_TRANSITION_AUTO_SUBFRAME, value 3) bitwise-ANDed with PAGE_TRANSITION_TYPED (value 1) evaluates to 1 (truthy).

When this evaluates to true, is_mixed_content_ is incorrectly set to false. Later in GetInsecureDownloadStatusForDownload(), if is_insecure_download_ is true but is_mixed_content_ is false, the function early-returns InsecureDownloadStatus::BLOCK. This skips the stricter mixed-content logic that would otherwise return InsecureDownloadStatus::SILENT_BLOCK for dangerous file types.

Potential Attacker Steps

Note: These are potential steps to trigger the vulnerability, as our tooling agent does not have the ability to run code to produce a working PoC.

  1. An attacker sets up an HTTPS-hosted webpage that they control.
  2. The attacker embeds an iframe with a src pointing to a malicious executable payload hosted on an insecure HTTP origin (e.g., http://attacker.com/malware.exe), OR uses an HTML form targeting the same HTTP URL.
  3. The victim visits the HTTPS page. The iframe attempts to load, or the victim is tricked into submitting the form.
  4. The navigation is converted into a download. The download inherits the transition type PAGE_TRANSITION_AUTO_SUBFRAME (3) or PAGE_TRANSITION_FORM_SUBMIT (7).
  5. Due to the bitwise logic error (3 & 1 == 1 or 7 & 1 == 1), Chrome incorrectly flags is_mixed_content_ = false.
  6. The download is flagged as BLOCK instead of SILENT_BLOCK. The user sees a visible “Keep / Discard” warning and can bypass the mixed-content protection to save the malware.

Suggested Fix

Replace the bitwise AND operations for core transition types with the ui::PageTransitionCoreTypeIs() helper function, which correctly masks and compares the core type.

    if (download_source == DownloadSource::RETRY ||
        ui::PageTransitionCoreTypeIs(transition_type, ui::PAGE_TRANSITION_RELOAD) ||
        ui::PageTransitionCoreTypeIs(transition_type, ui::PAGE_TRANSITION_TYPED) ||
        ui::PageTransitionCoreTypeIs(transition_type, ui::PAGE_TRANSITION_FROM_ADDRESS_BAR) ||
        ui::PageTransitionCoreTypeIs(transition_type, ui::PAGE_TRANSITION_FORWARD_BACK) ||
        ui::PageTransitionCoreTypeIs(transition_type, ui::PAGE_TRANSITION_AUTO_TOPLEVEL) ||
        ui::PageTransitionCoreTypeIs(transition_type, ui::PAGE_TRANSITION_AUTO_BOOKMARK) ||
        // Note: PAGE_TRANSITION_FROM_API is a qualifier (0x08000000), so bitwise AND is correct here.
        (transition_type & ui::PAGE_TRANSITION_FROM_API) ||
        // ...

Ensure that this correction is applied to all instances where core page transition types are checked in insecure_download_blocking.cc.

Evaluated with Chrome root at commit: a9cbf6e8b275fe4147435aa905f3b7f5a656f5f0


Results from 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
Links in the report