CVE-2026-10982
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
forthird_party/blink/renderer/modules/xr/xr_frame.cc |
modified | |
ifthird_party/blink/renderer/modules/xr/xr_frame.cc |
modified |
Files Changed
third_party/blink/renderer/modules/xr/xr_frame.cc
Patch
From 0bf582443baa115943b71303e89869f41acd5c88 Mon Sep 17 00:00:00 2001
From: Alexander Cooper <alcooper@chromium.org>
Date: Tue, 26 May 2026 13:47:06 -0700
Subject: [PATCH] [WebXR] Check buffer detachment in XRFrame::fillPoses
During XRFrame::fillPoses, calling XRSpace::getPose() can synchronously
execute script event handlers (e.g., reset events). This script may
detach the passed-in Float32Array transforms buffer. This change validates
that the array buffer is not detached prior to writing pose matrices.
Fixed: 513774197
Change-Id: Iac568599933c366e88e56659b783eff92f67948c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7872178
Reviewed-by: Brandon Jones <bajones@chromium.org>
Commit-Queue: Brandon Jones <bajones@chromium.org>
Auto-Submit: Alexander Cooper <alcooper@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1636464}
---
diff --git a/third_party/blink/renderer/modules/xr/xr_frame.cc b/third_party/blink/renderer/modules/xr/xr_frame.cc
index 0d391336..f0d6b07 100644
--- a/third_party/blink/renderer/modules/xr/xr_frame.cc
+++ b/third_party/blink/renderer/modules/xr/xr_frame.cc
@@ -44,6 +44,9 @@
const char kMismatchedBufferSizes[] = "Buffer sizes must be equal";
+const char kTransformsDetached[] =
+ "The transforms array was detached during fillPoses().";
+
std::optional<device::PlaneId> GetPlaneId(
const device::mojom::blink::XRNativeOriginInformation& native_origin) {
if (native_origin.is_plane_id()) {
@@ -570,7 +573,16 @@
auto transforms_data = transforms->AsSpan();
for (const auto& space : spaces) {
auto current_transform = transforms_data.take_first<kFloatsPerTransform>();
- if (const XRPose* pose = space->getPose(base_space)) {
+ const XRPose* pose = space->getPose(base_space);
+ // getPose() can synchronously dispatch a reset event if the space requires
+ // updating, which could detach the buffer in a JavaScript listener.
+ if (transforms->IsDetached()) {
+ exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
+ kTransformsDetached);
+ return false;
+ }
+
+ if (pose) {
current_transform.copy_from(pose->transform()->matrix()->AsSpan());
} else {
std::ranges::fill(current_transform, NAN);
Original Bug Report
Write-After-Free in XRFrame::fillPoses via Synchronous JS execution
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 XRFrame::fillPoses method in Blink’s WebXR implementation is vulnerable to a potential write-after-free. The function captures a base::span from a JavaScript-owned Float32Array and subsequently triggers synchronous JavaScript execution via the XRSpace::getPose method. An attacker can detach the underlying ArrayBuffer during this execution, causing subsequent writes to the stale span to corrupt memory.
Affected files:
third_party/blink/renderer/modules/xr/xr_frame.ccthird_party/blink/renderer/modules/xr/xr_bounded_reference_space.ccthird_party/blink/renderer/modules/xr/xr_space.ccthird_party/blink/renderer/modules/xr/xr_reference_space.cc
Estimated timestamp from git blame: 2024-10-21
Root Cause Analysis
In third_party/blink/renderer/modules/xr/xr_frame.cc, the fillPoses method retrieves a base::span from the provided Float32Array before entering a loop to populate it with pose data:
// xr_frame.cc:570
auto transforms_data = transforms->AsSpan();
for (const auto& space : spaces) {
auto current_transform = transforms_data.take_first<kFloatsPerTransform>();
if (const XRPose* pose = space->getPose(base_space)) {
current_transform.copy_from(pose->transform()->matrix()->AsSpan());
} else {
std::ranges::fill(current_transform, NAN);
}
}
The transforms_data span is a snapshot of the array’s backing store. Within the loop, space->getPose(base_space) is invoked. If the space is an XRBoundedReferenceSpace, this call leads to XRBoundedReferenceSpace::EnsureUpdated() in third_party/blink/renderer/modules/xr/xr_bounded_reference_space.cc (line 104), which dispatches a synchronous reset event:
mutable_this->DispatchEvent(
*XRReferenceSpaceEvent::Create(event_type_names::kReset, mutable_this));
DispatchEvent is a synchronous call that executes JavaScript event listeners. An attacker can use this callback to detach the transforms buffer (e.g., via postMessage). When the callback returns, the transforms_data span in fillPoses still holds a raw pointer to the now-detached or freed memory. Subsequent calls to copy_from or std::ranges::fill result in a write-after-free.
Potential Impact
This vulnerability allows an attacker to perform a controlled write-after-free within the sandboxed renderer process. The attacker can control the contents of the write (via the poses of the spaces) and the location of the write within the ArrayBuffer partition. Since ArrayBuffer backing stores are not protected by MiraclePtr (BackupRefPtr) and often reside in the V8 sandbox cage, this primitive could be leveraged to corrupt V8 objects and achieve remote code execution (RCE) within the renderer.
Suggested Potential Reproduction Steps
- Establish an immersive VR session with ‘bounded-floor’ support.
- Create a sequence of
XRBoundedReferenceSpaceobjects. - Attach a
resetevent listener to one of the spaces. - In the listener, transfer the
Float32Arraybuffer intended forfillPosesto a Worker to detach it. - Call
xrFrame.fillPoses()with the space sequence and the target array. - Observe a crash or heap corruption when
fillPosesattempts to write to the second or subsequent spaces in the sequence.
Recommended Fix
The base::span should not be held across points of synchronous JavaScript execution. The code should be modified to either:
- Re-acquire the span from the
DOMFloat32Arrayinside the loop after the call togetPose. - Validate that the buffer has not been detached after returning from
getPosebefore performing the write. - Pre-calculate all poses before capturing the span for the output buffer.
Evaluated with Chrome root at commit: 1a8d40fc44df2088d5945c0bf53584038aa1614a
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.