CVE-2026-79261
Overview
Background
- `isTrusted`
- A DOM event flag that is true only for events dispatched by the user agent (genuine hardware/UA-generated events) and false for events synthesized by script via
dispatchEvent. - `MediaControlsRotateToFullscreenDelegate`
- A Blink media-controls delegate that watches screen
orientationchangeevents and, when the device rotates to match a playing video’s orientation, transitions the video into fullscreen. - Transient user activation
- A short-lived “user gesture” state required to authorize privileged actions such as entering fullscreen, normally granted only by genuine user interaction.
- `Invoke` / `OnScreenOrientationChange`
Invokeis the delegate’s event handler dispatch;OnScreenOrientationChangeperforms the orientation-driven fullscreen transition.
Root Cause Analysis
In media_controls_rotate_to_fullscreen_delegate.cc, the kOrientationchange branch of MediaControlsRotateToFullscreenDelegate::Invoke unconditionally called OnScreenOrientationChange() for any event of type orientationchange, without checking whether the event was genuine. Because script can dispatch a synthetic orientationchange event (isTrusted == false) at the window, page JavaScript could drive the rotate-to-fullscreen logic as though the hardware had rotated. The invariant violated is that only genuine, user-agent-generated orientation changes should be able to grant transient user activation and trigger a fullscreen transition.
The fix wraps the OnScreenOrientationChange() call in an if (event->isTrusted()) guard, so untrusted synthetic events are ignored and fall through to the return. This tightens the check: trusted events behave exactly as before, while script-forged events no longer reach the fullscreen path.
orientationchange event as a real rotation without verifying isTrusted, letting scripted events forge a user-activation-bearing fullscreen trigger; the fix gates OnScreenOrientationChange() on event->isTrusted().Attack Path
- Set up matching video/screen orientation An attacker page plays a video (e.g. a landscape video on a portrait screen) so the rotate-to-fullscreen condition would be satisfied by a rotation.
- Forge an orientation change
Page script dispatches a synthetic
orientationchangeevent at the window (isTrusted == false), optionally after adjusting reported screen orientation, without any real device rotation or user gesture. - Handler runs the fullscreen path
Pre-patch,
MediaControlsRotateToFullscreenDelegate::InvokereachedOnScreenOrientationChange()for the untrusted event, treating it as a genuine rotation. - Fullscreen entry / activation granted
The delegate transitions the video into fullscreen and, per the commit message, this path can grant transient user activation—both without user intent; the untrusted event is now rejected by the
isTrusted()guard.
Impact Assessment
orientationchange event, running in the renderer’s page context. The advisory classifies this as an AuthorizationError / “Incorrect authorization in Controls” and requires a playing video meeting the rotate-to-fullscreen orientation condition; it does not state exploitation in the wild. Any further abuse of the improperly granted transient user activation (e.g. gating other gesture-restricted APIs) is (inferred) beyond what the patch demonstrates.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc |
modified | |
TEST_Fthird_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc |
modified |
Files Changed
third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.ccthird_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
Audit Directions
- Missing `isTrusted` on gesture/privilege triggersSearch other event handlers that grant transient user activation or perform fullscreen, pointer-lock, or similar gated transitions for branches that act on an event type without checking
event->isTrusted(). - Synthetic-event-reachable delegatesAudit media-controls and other UA-facing delegates whose
Invoke/handler methods are dispatched for event types a page can also dispatch viadispatchEvent. - UA-only signals treated as authoritativeReview code that infers physical/device state (orientation, sensors, input) from DOM events and confirm it distinguishes trusted UA events from script-forged ones.
Patch
From 70b3b177f7d9116a2c181a204b9f37256fe997de Mon Sep 17 00:00:00 2001
From: Frank Liberato <liberato@chromium.org>
Date: Wed, 15 Jul 2026 10:47:58 -0700
Subject: [PATCH] Verify isTrusted for orientationchange in MediaControlsRotateToFullscreenDelegate
This CL adds an isTrusted check to the kOrientationchange event handler in MediaControlsRotateToFullscreenDelegate::Invoke, ensuring that only genuine hardware-triggered orientation changes can grant transient user activation and trigger fullscreen transitions.
Added a unit test to verify that untrusted orientationchange events are ignored.
BUG=514408247
TAG=agy
CONV=ca3e205f-82aa-4e9b-9ec6-0b80c1443cbb
Change-Id: Ibe1fde6b37867bcc6249dae5aece7ce923bc61d6
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8092753
Reviewed-by: Tommy Steimel <steimel@chromium.org>
Auto-Submit: Frank Liberato <liberato@chromium.org>
Commit-Queue: Benjamin Keen <bkeen@google.com>
Reviewed-by: Benjamin Keen <bkeen@google.com>
Cr-Commit-Position: refs/heads/main@{#1662734}
---
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc
index 1667b39..c8bfa1a 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate.cc
@@ -112,7 +112,9 @@
return;
}
if (event->type() == event_type_names::kOrientationchange) {
- OnScreenOrientationChange();
+ if (event->isTrusted()) {
+ OnScreenOrientationChange();
+ }
return;
}
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
index 538c8783..7ea0588 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
@@ -27,6 +27,7 @@
#include "third_party/blink/renderer/modules/device_orientation/device_orientation_data.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
#include "third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -155,8 +156,15 @@
GetVideo().removeAttribute(html_names::kControlsAttr);
}
- void DispatchEvent(EventTarget& target, const AtomicString& type) {
- target.DispatchEvent(*Event::Create(type));
+ void DispatchEvent(EventTarget& target,
+ const AtomicString& type,
+ bool is_trusted = false) {
+ Event* event = Event::Create(type);
+ if (is_trusted) {
+ target.DispatchEvent(*event);
+ } else {
+ target.dispatchEventForBindings(event, IGNORE_EXCEPTION);
+ }
}
void InitScreenAndVideo(
@@ -231,7 +239,8 @@
void MediaControlsRotateToFullscreenDelegateTest::RotateTo(
display::mojom::blink::ScreenOrientation new_screen_orientation) {
GetChromeClient().MockScreenInfo().orientation_type = new_screen_orientation;
- DispatchEvent(GetWindow(), event_type_names::kOrientationchange);
+ DispatchEvent(GetWindow(), event_type_names::kOrientationchange,
+ true /* is_trusted */);
test::RunPendingTasks();
}
@@ -803,4 +812,29 @@
EXPECT_TRUE(GetVideo().IsFullscreen());
}
+TEST_F(MediaControlsRotateToFullscreenDelegateTest, UntrustedEventIgnored) {
+ // Portrait screen, landscape video.
+ InitScreenAndVideo(display::mojom::blink::ScreenOrientation::kPortraitPrimary,
+ gfx::Size(640, 480));
+ EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
+ EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
+
+ // Play video.
+ PlayVideo();
+ UpdateVisibilityObserver();
+
+ EXPECT_TRUE(ObservedVisibility());
+ EXPECT_FALSE(GetVideo().IsFullscreen());
+
+ // Simulate rotation in ScreenInfo but dispatch an UNTRUSTED event.
+ GetChromeClient().MockScreenInfo().orientation_type =
+ display::mojom::blink::ScreenOrientation::kLandscapePrimary;
+ DispatchEvent(GetWindow(), event_type_names::kOrientationchange,
+ false /* is_trusted */);
+ test::RunPendingTasks();
+
+ // Should NOT enter fullscreen because the event was untrusted.
+ EXPECT_FALSE(GetVideo().IsFullscreen());
+}
+
} // namespace blink
Regression Test / PoC
diff --git a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
index 538c8783..7ea0588 100644
--- a/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
+++ b/third_party/blink/renderer/modules/media_controls/media_controls_rotate_to_fullscreen_delegate_test.cc
@@ -27,6 +27,7 @@
#include "third_party/blink/renderer/modules/device_orientation/device_orientation_data.h"
#include "third_party/blink/renderer/modules/media_controls/media_controls_impl.h"
#include "third_party/blink/renderer/modules/screen_orientation/screen_orientation_controller.h"
+#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/heap/garbage_collected.h"
#include "third_party/blink/renderer/platform/testing/empty_web_media_player.h"
#include "third_party/blink/renderer/platform/testing/runtime_enabled_features_test_helpers.h"
@@ -155,8 +156,15 @@
GetVideo().removeAttribute(html_names::kControlsAttr);
}
- void DispatchEvent(EventTarget& target, const AtomicString& type) {
- target.DispatchEvent(*Event::Create(type));
+ void DispatchEvent(EventTarget& target,
+ const AtomicString& type,
+ bool is_trusted = false) {
+ Event* event = Event::Create(type);
+ if (is_trusted) {
+ target.DispatchEvent(*event);
+ } else {
+ target.dispatchEventForBindings(event, IGNORE_EXCEPTION);
+ }
}
void InitScreenAndVideo(
@@ -231,7 +239,8 @@
void MediaControlsRotateToFullscreenDelegateTest::RotateTo(
display::mojom::blink::ScreenOrientation new_screen_orientation) {
GetChromeClient().MockScreenInfo().orientation_type = new_screen_orientation;
- DispatchEvent(GetWindow(), event_type_names::kOrientationchange);
+ DispatchEvent(GetWindow(), event_type_names::kOrientationchange,
+ true /* is_trusted */);
test::RunPendingTasks();
}
@@ -803,4 +812,29 @@
EXPECT_TRUE(GetVideo().IsFullscreen());
}
+TEST_F(MediaControlsRotateToFullscreenDelegateTest, UntrustedEventIgnored) {
+ // Portrait screen, landscape video.
+ InitScreenAndVideo(display::mojom::blink::ScreenOrientation::kPortraitPrimary,
+ gfx::Size(640, 480));
+ EXPECT_EQ(SimpleOrientation::kPortrait, ObservedScreenOrientation());
+ EXPECT_EQ(SimpleOrientation::kLandscape, ComputeVideoOrientation());
+
+ // Play video.
+ PlayVideo();
+ UpdateVisibilityObserver();
+
+ EXPECT_TRUE(ObservedVisibility());
+ EXPECT_FALSE(GetVideo().IsFullscreen());
+
+ // Simulate rotation in ScreenInfo but dispatch an UNTRUSTED event.
+ GetChromeClient().MockScreenInfo().orientation_type =
+ display::mojom::blink::ScreenOrientation::kLandscapePrimary;
+ DispatchEvent(GetWindow(), event_type_names::kOrientationchange,
+ false /* is_trusted */);
+ test::RunPendingTasks();
+
+ // Should NOT enter fullscreen because the event was untrusted.
+ EXPECT_FALSE(GetVideo().IsFullscreen());
+}
+
} // namespace blink