CVE-2026-13910
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/xr/xr_input_source.cc |
modified |
Files Changed
third_party/blink/renderer/modules/xr/xr_input_source.cc
Patch
From 36f6ba524785efa77c93229a73e68f18846109b1 Mon Sep 17 00:00:00 2001
From: Alexander Cooper <alcooper@chromium.org>
Date: Mon, 11 May 2026 14:03:39 -0700
Subject: [PATCH] [WebXR] Improve input suppression for cross-origin DOM Overlay
Modify the DOM Overlay hit testing logic to correctly identify and
suppress input events when they intersect cross-origin frames.
Specifically, we now use the frame element's content frame directly
instead of checking for the content document. This allows properly
recognizing and handling Out-of-Process Iframes (RemoteFrames) which do
not have a local content document.
Additionally, we compare the hit frame's security origin against the
origin of the WebXR session's execution context, rather than comparing
against the outermost main frame. This correctly handles cases where the
XR session itself is running in an embedded subframe.
Fixed: 507231605
Change-Id: Ie4d5291c36f86c16d5cd8f78fc0a08a1ce783096
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7837042
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Auto-Submit: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1628794}
---
diff --git a/third_party/blink/renderer/modules/xr/xr_input_source.cc b/third_party/blink/renderer/modules/xr/xr_input_source.cc
index a186bb7..e3f08c59 100644
--- a/third_party/blink/renderer/modules/xr/xr_input_source.cc
+++ b/third_party/blink/renderer/modules/xr/xr_input_source.cc
@@ -12,6 +12,8 @@
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/events/event_dispatcher.h"
#include "third_party/blink/renderer/core/dom/events/event_path.h"
+#include "third_party/blink/renderer/core/execution_context/security_context.h"
+#include "third_party/blink/renderer/core/frame/frame.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/html/html_frame_element_base.h"
@@ -26,6 +28,7 @@
#include "third_party/blink/renderer/modules/xr/xr_system.h"
#include "third_party/blink/renderer/modules/xr/xr_target_ray_space.h"
#include "third_party/blink/renderer/modules/xr/xr_utils.h"
+#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
namespace blink {
@@ -519,13 +522,25 @@
// the common base class to cover both. (There's no intention to actively
// support framesets for DOM Overlay, but this helps prevent them from
// being used as a mechanism for information leaks.)
- HTMLFrameElementBase* frame = DynamicTo<HTMLFrameElementBase>(hit_element);
- if (frame) {
- Document* hit_document = frame->contentDocument();
- if (hit_document) {
- Frame* hit_frame = hit_document->GetFrame();
- DCHECK(hit_frame);
- if (hit_frame->IsCrossOriginToOutermostMainFrame()) {
+ HTMLFrameElementBase* frame_element =
+ DynamicTo<HTMLFrameElementBase>(hit_element);
+ if (frame_element) {
+ Frame* hit_frame = frame_element->ContentFrame();
+ if (hit_frame) {
+ bool is_cross_origin = false;
+ if (hit_frame->IsRemoteFrame()) {
+ is_cross_origin = true;
+ } else {
+ const SecurityOrigin* hit_origin =
+ hit_frame->GetSecurityContext()->GetSecurityOrigin();
+ const SecurityOrigin* session_origin =
+ session_->GetExecutionContext()->GetSecurityOrigin();
+ if (!hit_origin->IsSameOriginWith(session_origin)) {
+ is_cross_origin = true;
+ }
+ }
+
+ if (is_cross_origin) {
// Mark the input source as invisible until the primary button is
// released.
state_.is_visible = false;
Original Bug Report
WebXR DOM Overlay input suppression bypass allows cross-origin input sniffing
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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential logic flaw in WebXR DOM Overlay hit testing allows a malicious subframe to bypass cross-origin input suppression. The implementation fails to suppress events for Out-of-Process Iframes (OOPIFs) and incorrectly compares origins against the outermost main frame instead of the XR session’s origin. This could allow an attacker to sniff pointer coordinates and interactions over embedded third-party content.
Affected files:
third_party/blink/renderer/modules/xr/xr_input_source.ccthird_party/blink/renderer/modules/xr/xr_session.cc
Estimated timestamp from git blame: 2022-04-22
Description
The WebXR DOM Overlay specification requires that when a user’s pointer intersects with cross-origin content relative to the XR session’s document, controller poses and select events must be suppressed. This prevents a site from sniffing user input (e.g., precise pointer positions or click timing) over embedded third-party content.
There is a potential vulnerability in XRInputSource::ProcessOverlayHitTest() (third_party/blink/renderer/modules/xr/xr_input_source.cc) where the cross-origin detection logic fails in two scenarios, allowing input events to be leaked to the WebXR session owner.
1. OOPIF Bypass (Failing Open on Null Document) When the hit test intersects an iframe, the code attempts to retrieve its document:
HTMLFrameElementBase* frame = DynamicTo<HTMLFrameElementBase>(hit_element);
if (frame) {
Document* hit_document = frame->contentDocument();
if (hit_document) {
// ... suppression logic ...
}
}
// Visibility defaults to true if we fall through
state_.is_visible = true;
For Out-of-Process Iframes (OOPIFs), the internal frame representation is a RemoteFrame. Because HTMLFrameOwnerElement::contentDocument() attempts to cast the frame to a LocalFrame, it returns nullptr for OOPIFs. The if (hit_document) check fails, the suppression logic is skipped, and the input source remains visible.
2. Incorrect Reference Origin Check
If the hit frame is same-process, the code evaluates hit_frame->IsCrossOriginToOutermostMainFrame(). This compares the hit frame’s origin against the top-level outermost main frame, which is incorrect. If the WebXR session is running in a subframe (Origin B) embedded by a top-level document (Origin A), the attacker at Origin B can embed Origin A content in their overlay. The check will compare Origin A to Origin A, return false, and fail to suppress the input, even though Origin A is cross-origin to the XR session owner (Origin B).
Potential Reproduction Steps
Note: These are suggested steps for reproduction as our analysis tooling does not execute code to verify a live proof of concept.
- A benign top-level site (
https://host.example) embeds an attacker subframe (https://attacker.example) and delegates WebXR permissions:<iframe src="..." allow="xr-spatial-tracking; fullscreen">. - The attacker frame requests an
immersive-arsession with thedom-overlayfeature enabled. - Inside the attacker’s DOM overlay root, they embed a sensitive cross-origin iframe. This could be an OOPIF (
https://sensitive.example) or the top-level host (https://host.example/sensitive). - The user physically taps or interacts with the area over the embedded iframe during the AR session.
- Because of the logic flaws described above, the attacker frame successfully receives
selectevents and non-nulltargetRaySpaceposes corresponding to the user’s interaction with the cross-origin content.
Suggested Fix
- Handle RemoteFrames: Modify the logic to recognize
RemoteFrameintersections. If aRemoteFrameis hit, it is inherently cross-process (and thus cross-origin), so input events should be suppressed immediately without requiring acontentDocument(). - Correct Origin Comparison: Instead of checking
IsCrossOriginToOutermostMainFrame(), the hit frame’s security origin should be compared against the security origin of theExecutionContext(theDocument) that owns theXRSession.
Evaluated with Chrome root at commit: a1e33f5848218e21d4a16ae2c1bc94e815c30c7f
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.