Overview

Low
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactPolicy bypass in Audio
DescriptionPolicy bypass in Audio
ComponentAudio
Bug ClassLogic Error
Tracker40064543
Fix commitad95dd50a86d (chromium/src) +41/-0
CISA KEVNot listed
CreditedLuan Herrera (@lbherrera_)
Disclosed2026-04-07

Changed Functions

FunctionChangeNotes
async_test
third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
modified
runAfterLayoutAndPaint
third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
modified

Files Changed

  • third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
  • third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
From ad95dd50a86da6f46e3ae113a487689e99f4c4cc Mon Sep 17 00:00:00 2001
From: Tommy Steimel <steimel@chromium.org>
Date: Thu, 19 Feb 2026 08:41:43 -0800
Subject: [PATCH] [Media Controls] Hide download button when sandbox flags disallow it

Currently, the default Blink media controls display a (functional)
download button, even when inside a sandboxed iframe that does not
allow downloads. This CL hides the download button when the element
is in a sandboxed iframe that disallows downloads.

Bug: 40064543
Change-Id: Ie25b48bb994ed66c8621429a3447cc279e67fbfb
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7590057
Commit-Queue: Tommy Steimel <steimel@chromium.org>
Reviewed-by: Benjamin Keen <bkeen@google.com>
Cr-Commit-Position: refs/heads/main@{#1587180}
---

diff --git a/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc b/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
index fe23dd7e8..f670bf0 100644
--- a/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
+++ b/third_party/blink/renderer/modules/media_controls/elements/media_control_download_button_element.cc
@@ -8,6 +8,7 @@
 #include "third_party/blink/public/platform/platform.h"
 #include "third_party/blink/public/platform/user_metrics_action.h"
 #include "third_party/blink/public/strings/grit/blink_strings.h"
+#include "third_party/blink/public/web/web_local_frame.h"
 #include "third_party/blink/renderer/core/dom/document.h"
 #include "third_party/blink/renderer/core/dom/events/event.h"
 #include "third_party/blink/renderer/core/execution_context/execution_context.h"
@@ -50,6 +51,14 @@
     return false;
   }
 
+  if (GetDocument().GetFrame()) {
+    WebLocalFrame* web_local_frame = WebLocalFrame::FromFrameToken(
+        GetDocument().GetFrame()->GetLocalFrameToken());
+    if (web_local_frame && !web_local_frame->IsAllowedToDownload()) {
+      return false;
+    }
+  }
+
   return true;
 }
 
diff --git a/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
new file mode 100644
index 0000000..45839b23
--- /dev/null
+++ b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>media controls download button hidden when sandboxed</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script src="../media-controls.js"></script>
+<iframe id="downloads-blocked"sandbox="allow-same-origin"></iframe>
+<iframe id="downloads-allowed"sandbox="allow-same-origin allow-downloads"></iframe>
+<script>
+async_test(function(t) {
+  const iframeBlocked = document.querySelector("#downloads-blocked");
+  const iframeAllowed = document.querySelector("#downloads-allowed");
+  const videoBlocked = iframeBlocked.contentDocument.createElement("video");
+  const videoAllowed = iframeAllowed.contentDocument.createElement("video");
+
+  [videoBlocked, videoAllowed].forEach((video) => {
+    video.controls = true;
+    video.setAttribute("preload", "none");
+    video.src = "https://someexample.example/example.mp4";
+  });
+  iframeBlocked.contentDocument.body.append(videoBlocked);
+  iframeAllowed.contentDocument.body.append(videoAllowed);
+
+  runAfterLayoutAndPaint(t.step_func_done(function() {
+    assert_false(isDownloadsButtonEnabled(videoBlocked),
+      "the download button should not show when the sandbox blocks downloads");
+    assert_true(isDownloadsButtonEnabled(videoAllowed),
+      "the download button should show when the sandbox allows downloads");
+  }));
+
+});
+</script>
Loading diff…

Regression Test / PoC

shipped with the fix
diff --git a/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
new file mode 100644
index 0000000..45839b23
--- /dev/null
+++ b/third_party/blink/web_tests/media/controls/download-button-hidden-when-sandboxed.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<title>media controls download button hidden when sandboxed</title>
+<script src="../../resources/testharness.js"></script>
+<script src="../../resources/testharnessreport.js"></script>
+<script src="../../resources/run-after-layout-and-paint.js"></script>
+<script src="../media-controls.js"></script>
+<iframe id="downloads-blocked"sandbox="allow-same-origin"></iframe>
+<iframe id="downloads-allowed"sandbox="allow-same-origin allow-downloads"></iframe>
+<script>
+async_test(function(t) {
+  const iframeBlocked = document.querySelector("#downloads-blocked");
+  const iframeAllowed = document.querySelector("#downloads-allowed");
+  const videoBlocked = iframeBlocked.contentDocument.createElement("video");
+  const videoAllowed = iframeAllowed.contentDocument.createElement("video");
+
+  [videoBlocked, videoAllowed].forEach((video) => {
+    video.controls = true;
+    video.setAttribute("preload", "none");
+    video.src = "https://someexample.example/example.mp4";
+  });
+  iframeBlocked.contentDocument.body.append(videoBlocked);
+  iframeAllowed.contentDocument.body.append(videoAllowed);
+
+  runAfterLayoutAndPaint(t.step_func_done(function() {
+    assert_false(isDownloadsButtonEnabled(videoBlocked),
+      "the download button should not show when the sandbox blocks downloads");
+    assert_true(isDownloadsButtonEnabled(videoAllowed),
+      "the download button should show when the sandbox allows downloads");
+  }));
+
+});
+</script>
Loading diff…

Original Bug Report

reported by he...@gmail.com

Audio player's download functionality allows bypassing the "allow-downloads" flag of sandboxed iframes

VULNERABILITY DETAILS
A sandboxed iframe is not supposed to be able to initiate a download unless the “allow-downloads” flag is set. However, it is possible to make the user click on the “Download” option of the audio player through a clickjacking attack, which ends up downloading a file.

An arbitrary file can be made to be downloaded by performing a server-side redirect when the user tries to download the original audio file.

The “Download” option should be checking whether the iframe is sandboxed and contains the proper flags before deciding whether the download should initiate.

For the attack to work, it requires the victim to click twice inside a malicious iframe. In a real attack, there would be buttons placed over the video player to trick the user into clicking on the “three dots” and the “Download” option. The PoC just places opacity over the iframe to demonstrate that it would be possible to do that.

For reference, this issue is similar to https://crbug.com/chromium/1100761, albeit more severe as this issue doesn’t require any additional sandbox permission to work.

I have attached a video (repro.mkv) reproducing the attack.

VERSION
Chrome Version:
113.0.5672.92 (Official Build) stable (64-bit)
114.0.5735.26 (Official Build) beta (64-bit)
115.0.5762.4 (Official Build) dev (64-bit)

Operating System:
Ubuntu 20.04

REPRODUCTION CASE

  1. Download “index.html”, “iframe.html”, “iframe2.html”, “horse.ogg”, “file.txt”, “download.php” and place them in the same directory.
  2. Run the “php -S 0:8000” command in the directory the files were downloaded to.
  3. Access http://localhost:8000/
  4. Click on the “three dots” and then on the “Download” option, which will download a file even though the iframe doesn’t have the “allow-downloads” flag set.

CREDIT INFORMATION
Reporter credit: Luan Herrera (@lbherrera_)

View on issue tracker