Medium watch CVSS 4.2 chrome Authorization Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
4.2
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactIncorrect authorization in Downloads
DescriptionIncorrect authorization in Downloads
ComponentDownloads
Sub-componentDownloads
Bug ClassAuthorization Error
Action tierwatch — logic/policy bug — security-relevant, no memory-unsafety
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.
Tracker496616790
Fix commit0d84f06e082c (chromium/src) +9/-10
CWECWE-863
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:U/C:L/I:N/A:L
CISA KEVNot listed
CreditedGoogle
Reported2026-03-26 (per vendor advisory)
Fix landed2026-08-14 (public git)
Disclosed2026-09-08
Patch gap25 days — fix public in source before the advisory

Background

`DownloadURLParams` (`frame.mojom`)
the Mojo struct a renderer sends to the browser over the DownloadURL IPC to request a download; its fields are renderer-supplied and therefore untrusted.
`is_context_menu_save` → `should_prompt_for_save_location`
the boolean flag being renamed; previously named as if it told the browser the download came from a context-menu “Save as”, now named for its sole remaining use: whether to show the file picker.
`content_initiated`
a browser-side DownloadUrlParameters property indicating the download originated from web content, used in download authorization/handling decisions.
`RenderFrameHostImpl::DownloadURL`
the browser-process IPC handler that turns a renderer’s DownloadURLParams into a download::DownloadUrlParameters and starts the download.

Root Cause Analysis

The renderer-supplied is_context_menu_save field was named and historically used as if it authoritatively indicated the download’s source (a context-menu save), but a compromised renderer can set any value it likes for it. Per the commit message, a prior change (crrev.com/c/8177729) already narrowed the field so that in the browser’s DownloadURL handler it is used only to drive parameters->set_prompt(...) (whether the save-location file picker is shown), while parameters->set_content_initiated(true) is set unconditionally for downloads arriving through this IPC — so the browser no longer trusts the field to decide the download source. This CL is the hardening follow-up: it renames the field to should_prompt_for_save_location across the Mojo definition and all callers, updates the handler to call set_prompt(blink_parameters->should_prompt_for_save_location), and rewrites the comment to drop the reference to treating the value as a source indicator. The rename works as a fix by removing the misleading name that invited the browser to (again) rely on a renderer-controlled flag as a trustworthy source signal, keeping the flag’s authority limited to the benign UI choice of prompting.
Key insight
A renderer-controlled boolean was named as though it conveyed trusted download-provenance (“from context menu”) when it can only safely mean “show the file picker”; the fix renames it to should_prompt_for_save_location so the browser is not tempted to make authorization decisions from a value the renderer can forge.

Attack Path

  1. Compromised renderer crafts the IPC An attacker with a compromised renderer builds a DownloadURLParams for the DownloadURL IPC and sets the source flag to any value, since the field is renderer-supplied.
  2. Historic reliance on the flag Before the narrowing in crrev.com/c/8177729, the browser could read is_context_menu_save as an indication of the download’s source, letting a lying renderer misrepresent a web-content-initiated download as a user-driven context-menu save (inferred as the pre-narrowing behavior the commit describes).
  3. Source misclassification affects handling Such misclassification could influence download authorization/handling that keys on whether a download is content-initiated versus user-initiated (inferred).
  4. Hardening removes the foothold This CL forces set_content_initiated(true) unconditionally for this IPC and renames the field to should_prompt_for_save_location, so the only effect a renderer retains is toggling whether the save-location picker appears.

Impact Assessment

The issue is that a renderer-supplied flag could be relied upon by the browser to indicate a download’s source, an incorrect-authorization risk in the Downloads component (browser process trusting untrusted renderer input). In this specific patch the browser already treats such downloads as content_initiated unconditionally, so the renderer’s only retained influence is whether the file-picker prompt is shown; the change is a defense-in-depth rename to prevent future misuse. The advisory classifies it as incorrect authorization in Downloads and does not report in-the-wild exploitation; the concrete pre-narrowing exploitation impact is (inferred) from the commit description rather than demonstrated by this diff.

Files Changed

  • third_party/blink/renderer/core/html/media/html_video_element_test.cc
  • content/browser/download/download_browsertest.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
  • third_party/blink/public/mojom/frame/frame.mojom
  • third_party/blink/renderer/core/frame/local_frame.cc
  • third_party/blink/renderer/core/html/media/html_video_element.cc

Audit Directions

  • Renderer-supplied flags used as trust/authorization signals
    Search Mojo structs sent from the renderer for booleans/enums named after a provenance (“from context menu”, “user initiated”) and verify the browser never uses them to make security decisions, only benign UI choices.
  • Names that imply trust the value cannot carry
    Look for fields whose names suggest a trusted source but whose data crosses a privilege boundary; misleading names invite future code to re-introduce reliance on them.
  • Download source vs. UI-prompt conflation
    Audit download-initiation paths (DownloadURL, context-menu saves, media/data-URL saves) to confirm content_initiated and similar provenance is determined by the browser, not derived from the renderer-supplied parameters.
From 0d84f06e082c9d50591ae52473a1f8afbd6985df Mon Sep 17 00:00:00 2001
From: Svend L <[email protected]>
Date: Fri, 14 Aug 2026 11:15:45 -0700
Subject: [PATCH] [Downloads] Rename is_context_menu_save to should_prompt_for_save_location

After crrev.com/c/8177729, DownloadURLParams::is_context_menu_save is
only used to determine whether the filepicker should be shown for a
download. Since the param is supplied by the renderer, a compromised
renderer can lie about whether a download is initiated from a context
menu. Rename the param to reflect its narrower scope, and to discourage
the browser from relying on the param value to indicate download source
in the future.

Bug: 496616790
Change-Id: Ic5a1277c2ded0671b3f72f9ae004d81a6a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8204664
Reviewed-by: Charlie Reis <[email protected]>
Reviewed-by: Giovanni Ortuno Urquidi <[email protected]>
Reviewed-by: Nate Chapin <[email protected]>
Reviewed-by: Ari Chivukula <[email protected]>
Reviewed-by: Min Qin <[email protected]>
Commit-Queue: Svend L <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1679710}
---
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index e038883..c8440e1 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -8003,14 +8003,13 @@
   std::unique_ptr<download::DownloadUrlParameters> parameters =
       CreateDownloadUrlParameters(blink_parameters->url, traffic_annotation);
   // Downloads arriving through this IPC handler always originate from web
-  // content, so treat them as content-initiated regardless of what the
-  // renderer reports in `is_context_menu_save`.
+  // content.
   parameters->set_content_initiated(true);
   parameters->set_has_user_gesture(blink_parameters->has_user_gesture &&
                                    HasTransientUserActivation());
   parameters->set_suggested_name(
       blink_parameters->suggested_name.value_or(std::u16string()));
-  parameters->set_prompt(blink_parameters->is_context_menu_save);
+  parameters->set_prompt(blink_parameters->should_prompt_for_save_location);
   parameters->set_cross_origin_redirects(
       blink_parameters->cross_origin_redirects);
   parameters->set_referrer(
diff --git a/third_party/blink/public/mojom/frame/frame.mojom b/third_party/blink/public/mojom/frame/frame.mojom
index 5db65454..579f2fd 100644
--- a/third_party/blink/public/mojom/frame/frame.mojom
+++ b/third_party/blink/public/mojom/frame/frame.mojom
@@ -142,8 +142,8 @@
   // Non-null when |url| is for "data:", eg. when saving an image.
   pending_remote<Blob>? data_url_blob;
 
-  // Whether the download is from context menu.
-  bool is_context_menu_save = false;
+  // Whether to prompt the user for a destination filepath.
+  bool should_prompt_for_save_location = false;
 
   // True when the download was preceded by a recent gesture from the user.
   bool has_user_gesture = false;
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
index 43eb225..f049ab6f 100644
--- a/third_party/blink/renderer/core/frame/local_frame.cc
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
@@ -3867,7 +3867,7 @@
   }
 
   auto params = mojom::blink::DownloadURLParams::New();
-  params->is_context_menu_save = true;
+  params->should_prompt_for_save_location = true;
   params->data_url_blob = DataURLToBlob(url);
   GetLocalFrameHostRemote().DownloadURL(std::move(params));
 }
diff --git a/third_party/blink/renderer/core/html/media/html_video_element.cc b/third_party/blink/renderer/core/html/media/html_video_element.cc
index d47b1d93..e07540f 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element.cc
@@ -574,7 +574,7 @@
       /*is_top_level_navigation=*/false);
 
   auto params = mojom::blink::DownloadURLParams::New();
-  params->is_context_menu_save = true;
+  params->should_prompt_for_save_location = true;
   auto timestamp_ms = base::saturated_cast<uint32_t>(
       currentTime() * base::Time::kMillisecondsPerSecond);
   params->suggested_name =
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 5f0208a..9e57315f 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -5446,7 +5446,7 @@
 
   auto params = blink::mojom::DownloadURLParams::New();
   params->url = kDownloadUrl;
-  params->is_context_menu_save = true;
+  params->should_prompt_for_save_location = true;
   static_cast<RenderFrameHostImpl*>(render_frame_host)
       ->DownloadURL(std::move(params));
 
@@ -5520,7 +5520,7 @@
 
   auto params = blink::mojom::DownloadURLParams::New();
   params->url = kDownloadUrl;
-  params->is_context_menu_save = true;
+  params->should_prompt_for_save_location = true;
   static_cast<RenderFrameHostImpl*>(fenced_frame_host)
       ->DownloadURL(std::move(params));
 
diff --git a/third_party/blink/renderer/core/html/media/html_video_element_test.cc b/third_party/blink/renderer/core/html/media/html_video_element_test.cc
index 18d3482b..e7bfeee 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element_test.cc
@@ -710,7 +710,7 @@
   ASSERT_TRUE(FrameHost()->download_url_called());
   const auto& params = FrameHost()->download_params();
   ASSERT_TRUE(params);
-  EXPECT_TRUE(params->is_context_menu_save);
+  EXPECT_TRUE(params->should_prompt_for_save_location);
   EXPECT_TRUE(params->suggested_name.starts_with("videoframe_"));
   EXPECT_TRUE(params->url.ProtocolIs("blob"));
   EXPECT_TRUE(params->blob_url_token.is_valid());
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/content/browser/download/download_browsertest.cc b/content/browser/download/download_browsertest.cc
index 5f0208a..9e57315f 100644
--- a/content/browser/download/download_browsertest.cc
+++ b/content/browser/download/download_browsertest.cc
@@ -5446,7 +5446,7 @@
 
   auto params = blink::mojom::DownloadURLParams::New();
   params->url = kDownloadUrl;
-  params->is_context_menu_save = true;
+  params->should_prompt_for_save_location = true;
   static_cast<RenderFrameHostImpl*>(render_frame_host)
       ->DownloadURL(std::move(params));
 
@@ -5520,7 +5520,7 @@
 
   auto params = blink::mojom::DownloadURLParams::New();
   params->url = kDownloadUrl;
-  params->is_context_menu_save = true;
+  params->should_prompt_for_save_location = true;
   static_cast<RenderFrameHostImpl*>(fenced_frame_host)
       ->DownloadURL(std::move(params));
diff --git a/third_party/blink/renderer/core/html/media/html_video_element_test.cc b/third_party/blink/renderer/core/html/media/html_video_element_test.cc
index 18d3482b..e7bfeee 100644
--- a/third_party/blink/renderer/core/html/media/html_video_element_test.cc
+++ b/third_party/blink/renderer/core/html/media/html_video_element_test.cc
@@ -710,7 +710,7 @@
   ASSERT_TRUE(FrameHost()->download_url_called());
   const auto& params = FrameHost()->download_params();
   ASSERT_TRUE(params);
-  EXPECT_TRUE(params->is_context_menu_save);
+  EXPECT_TRUE(params->should_prompt_for_save_location);
   EXPECT_TRUE(params->suggested_name.starts_with("videoframe_"));
   EXPECT_TRUE(params->url.ProtocolIs("blob"));
   EXPECT_TRUE(params->blob_url_token.is_valid());
Loading diff…

Original Bug Report

reported by [email protected]

BFCache and download throttle bypass via renderer-controlled is_context_menu_save IPC

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

Overview: A compromised renderer can bypass BackForwardCache (BFCache) activity checks and automatic download throttles by spoofing the is_context_menu_save flag in the DownloadURL IPC. This allows an inactive frame to trigger a Save As dialog attributed to the currently visible tab. It also enables spoofing of Mark-of-the-Web (MOTW) referrer metadata.

Affected files:

  • content/browser/download/download_manager_impl.cc
  • content/browser/renderer_host/render_frame_host_impl.cc
  • content/browser/renderer_host/ipc_utils.cc
  • third_party/blink/public/mojom/frame/frame.mojom

Estimated timestamp from git blame: 2024-12-10

Description

The DownloadURL IPC call in RenderFrameHostImpl handles download requests from the renderer. One of the parameters in blink::mojom::DownloadURLParams is is_context_menu_save, which is intended to indicate if the download was initiated via a user right-clicking and selecting ‘Save Link As…’.

In RenderFrameHostImpl::DownloadURL, the content_initiated flag is calculated as !blink_parameters->is_context_menu_save. This flag is then passed to DownloadManagerImpl::BeginDownloadInternal.

In DownloadManagerImpl::BeginDownloadInternal, several critical security checks are gated behind the content_initiated flag:

if (rfh && content_initiated) {
  if (rfh->IsInactiveAndDisallowActivation(
          RenderFrameHost::InactiveCheckAllowedReason::kBeginDownload)) {
    return;
  }
  // ... throttle checks ...
  if (!delegate_->CheckDownloadAllowed(...)) {
    return;
  }
}

A compromised renderer can arbitrarily set is_context_menu_save to true, forcing content_initiated to be false. This results in the following security bypasses:

  1. BFCache Activity Gate Bypass: The call to IsInactiveAndDisallowActivation is skipped. This allows a RenderFrameHost that is currently in the BackForwardCache (and thus should be inactive) to successfully initiate a download.
  2. Automatic Download Throttle Bypass: The delegate_->CheckDownloadAllowed call, which enforces automatic download throttles and user gesture requirements, is skipped.
  3. UI Spoofing: Because the download is initiated from a BFCached frame while a new page is visible in the tab, the ‘Save As’ dialog (triggered because prompt is set to true when is_context_menu_save is true) appears while the user is looking at a different origin. The tab_url for the download is taken from the current visible entry, leading to incorrect attribution in the download UI.
  4. Mark-of-the-Web (MOTW) Spoofing: The renderer-provided referrer URL is not validated against the frame’s origin in VerifyDownloadUrlParams. This unvalidated URL is eventually used to set the Mark-of-the-Web (MOTW) ReferrerUrl and HostUrl on Windows (e.g., via BaseFile::GetEffectiveAuthorityURL for data: URLs and then quarantine::SetInternetZoneIdentifierDirectly), allowing an attacker to falsely attribute a malicious payload to a trusted site.

Potential Reproduction Steps (AI Suggested)

Note: These are suggested steps, as this AI agent does not have the ability to run code to verify them.

  1. Obtain renderer RCE in a process hosting attacker.com.
  2. Navigate a tab from attacker.com to a trusted site like https://victim.com/. The attacker.com RenderFrameHost enters the BFCache.
  3. From the compromised attacker.com renderer process, craft a blink::mojom::DownloadURLParams struct.
  4. Set is_context_menu_save = true, url to a malicious payload (e.g., a data: URI containing an executable), and referrer to https://victim.com/.
  5. Send this struct to the browser process via the blink::mojom::LocalFrameHost::DownloadURL IPC endpoint.
  6. The browser will skip the BFCache check and prompt the user with a ‘Save As’ dialog while victim.com is in the address bar.
  7. Upon completion, the downloaded file’s Mark-of-the-Web Zone.Identifier on Windows will contain ReferrerUrl=https://victim.com/ and HostUrl=https://victim.com/.

Suggested Fix

The browser process should not trust the is_context_menu_save flag from the renderer for critical security checks like BFCache gating and download throttling. Context menu saves are typically initiated by the browser process itself. If this IPC must be used for context menu saves, the browser process should independently verify that a valid context menu action recently occurred for that frame. Additionally, the referrer provided in DownloadURLParams should be validated against the requesting frame’s origin in VerifyDownloadUrlParams to prevent MOTW spoofing.

Evaluated with Chrome root at commit: bb48272cafb7e24c93f55ef40da398cd206ee651


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. Please feel free to reach out to me if you have concerns or feedback.

View on issue tracker
Links in the report