Medium chrome Logic Error 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in WebXR
DescriptionInsufficient validation of untrusted input in WebXR
ComponentWebXR
Bug ClassLogic Error
Tracker517598518
Fix commit3b082d7458a9 (chromium/src) +12/-0
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-30

Files Changed

  • content/browser/xr/service/vr_service_impl.cc
From 3b082d7458a9c692a550b0c04c6d7c7d1e8d769d Mon Sep 17 00:00:00 2001
From: Alexander Cooper <alcooper@chromium.org>
Date: Mon, 01 Jun 2026 17:16:38 -0700
Subject: [PATCH] [WebXR] Check page visibility during session request validation

Validates that the requesting frame's visibility state is set to
visible when processing a VRService::RequestSession Mojo call in
VRServiceImpl, maintaining proper alignment with renderer-side
session requirements.

Fixed: 517598518
Change-Id: Ibcaafa8dbdbec3a53e556a9459d3658edc3ffa24
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7885957
Auto-Submit: Alexander Cooper <alcooper@chromium.org>
Commit-Queue: Alexander Cooper <alcooper@chromium.org>
Reviewed-by: Brandon Jones <bajones@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1639800}
---

diff --git a/content/browser/xr/service/vr_service_impl.cc b/content/browser/xr/service/vr_service_impl.cc
index eb9239c9..6a7add77 100644
--- a/content/browser/xr/service/vr_service_impl.cc
+++ b/content/browser/xr/service/vr_service_impl.cc
@@ -39,6 +39,7 @@
 #include "content/public/browser/web_contents.h"
 #include "content/public/common/child_process_id_util.h"
 #include "content/public/common/origin_util.h"
+#include "content/public/common/page_visibility_state.h"
 #include "device/vr/buildflags/buildflags.h"
 #include "device/vr/public/cpp/features.h"
 #include "device/vr/public/cpp/session_mode.h"
@@ -566,6 +567,17 @@
     return;
   }
 
+  if (render_frame_host_->GetVisibilityState() !=
+      content::PageVisibilityState::kVisible) {
+    // Page visibility is verified blink-side, so this should never fail unless
+    // the requesting client is misbehaving or compromised. Treat non-visible
+    // page as unknown failure:
+    RejectSession(std::move(callback), options->trace_id,
+                  device::mojom::RequestSessionError::UNKNOWN_FAILURE,
+                  "Page is not visible.");
+    return;
+  }
+
   // The consent flow cannot differentiate between optional and required
   // features, but we don't need to block creation if an optional feature is
   // not supported. Remove all unsupported optional features from the
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential page-visibility bypass in VRServiceImpl allows background XR session takeover

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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: The browser-side WebXR implementation in VRServiceImpl::RequestSession fails to re-validate page visibility before starting an immersive session. While the Blink renderer checks both transient user activation and page visibility, the browser-side Mojo implementation only re-validates transient user activation. A compromised renderer can potentially exploit this gap to launch an immersive XR session from a background or hidden tab, leading to UI spoofing or origin confusion inside the headset.

Affected files:

  • content/browser/xr/service/vr_service_impl.cc
  • third_party/blink/renderer/modules/xr/xr_system.cc

Estimated timestamp from git blame: 2019-09-05

Summary

A defense-in-depth validation gap exists in the browser-side WebXR Mojo host implementation. The WebXR specification dictates that an immersive session request is only allowed if the requesting document is both active via transient user activation and visible (trustworthy). While the Blink renderer checks both requirements, VRServiceImpl::RequestSession in the browser process only validates the transient user activation state. A compromised renderer could potentially bypass the renderer-side visibility check and trigger an immersive session from a hidden or background tab, leading to physical headset takeover or full-screen UI spoofing.

Code Analysis

In the Blink renderer, both constraints are verified correctly in third_party/blink/renderer/modules/xr/xr_system.cc (lines 259-274):

const char* CheckImmersiveSessionRequestAllowed(LocalDOMWindow* window) {
  if (!LocalFrame::HasTransientUserActivation(window->GetFrame())) {
    return kRequestRequiresUserActivation;
  }

  // Check that the document is "trustworthy"
  if (!window->document()->IsPageVisible()) {
    return kPageNotVisible;
  }

  return nullptr;
}

However, in the browser process, the Mojo handler VRServiceImpl::RequestSession in content/browser/xr/service/vr_service_impl.cc (lines 556-567) only re-validates the transient user activation:

  const bool has_user_activation =
      render_frame_host_->HasTransientUserActivation();
  if (!has_user_activation) {
    RejectSession(std::move(callback), options->trace_id,
                  device::mojom::RequestSessionError::UNKNOWN_FAILURE,
                  "Missing user activation.");
    return;
  }

There is no validation check of the requesting frame’s visibility state (e.g., render_frame_host_->GetVisibilityState()), allowing a compromised renderer to directly request an immersive session even if the tab is hidden.

Potential Step-by-Step Attack Scenario

Note: These are potential steps based on source code analysis; our tooling agent currently lacks the ability to execute code or verify runtime behavior with a working exploit.

  1. The user visits an attacker-controlled origin and grants persistent WebXR/AR spatial tracking permissions.
  2. The user interacts with the page, activating transient user activation (which remains active for up to 5 seconds).
  3. The user switches to a different, trusted tab (e.g., a banking site) or minimizes the browser, transitioning the attacker’s tab to a hidden state (content::PageVisibilityState::kHidden).
  4. Within the active 5-second transient user activation window, the attacker leverages a separate renderer compromise (e.g., a V8 exploit) to execute shellcode in the renderer process.
  5. The shellcode bypasses Blink’s renderer-side checks in xr_system.cc and directly calls the browser’s VRService::RequestSession Mojo interface.
  6. Because the user activation is still valid in the browser process and permissions were previously granted (avoiding any consent prompt), the browser launches the immersive session on the active VR/AR device without verifying that the requesting page is visible.
  7. The physical VR/AR headset or phone screen launches the attacker’s immersive environment. Because this occurs while the user is actively viewing a different, trusted tab, the user may attribute the session takeover to the trusted foreground tab, creating an opportunity for convincing phishing and origin-spoofing attacks.

Suggested Fix

To align browser-side security checks with the renderer-side restrictions, VRServiceImpl::RequestSession should check that the requesting document is visible before proceeding with session creation, similar to other Mojo interface handlers like ImageCaptureImpl or FontAccessManager:

  if (render_frame_host_->GetVisibilityState() !=
      content::PageVisibilityState::kVisible) {
    RejectSession(std::move(callback), options->trace_id,
                  device::mojom::RequestSessionError::UNKNOWN_FAILURE,
                  "Page is not visible.");
    return;
  }

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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