Chrome · WebXR
CVE-2025-12443
OOB in WebXR
Overview
Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifthird_party/blink/renderer/modules/xr/xr_rigid_transform.cc |
modified |
Files Changed
third_party/blink/renderer/modules/xr/xr_rigid_transform.ccthird_party/blink/renderer/modules/xr/xr_utils.ccthird_party/blink/renderer/modules/xr/xr_utils.hthird_party/blink/renderer/modules/xr/xr_view.ccthird_party/blink/renderer/modules/xr/xr_view.h
Patch
From 6c5963ad6b4541f5fa0812607fd36a979c48c0f1 Mon Sep 17 00:00:00 2001 From: Alexander Cooper <[email protected]> Date: Thu, 16 Oct 2025 10:54:37 -0700 Subject: [PATCH] [WebXR] Update detached matrix handling Updates a few places that handle an array representation of a matrix being detached by returning a 0-length array to simply recompute the array that should be present, based on the presence of other data. This more closely matches the spec, which for both of these cases essentially state that if the value is not null to check if it's detached, and if it is not detached to return the value. The steps following both of these checks then recompute the matrix, so technically our current impl is not spec-compliant to that. Further, we remove one unused array to transform conversion helper and update another to a CHECK from a DCHECK to match best practices for invariants. Fixed: 452071845 Change-Id: Idf765fe5717d59ae63c71e8253784ff4473dea5f Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7046576 Commit-Queue: Alexander Cooper <[email protected]> Reviewed-by: Brandon Jones <[email protected]> Auto-Submit: Alexander Cooper <[email protected]> Cr-Commit-Position: refs/heads/main@{#1530947} --- diff --git a/third_party/blink/renderer/modules/xr/xr_rigid_transform.cc b/third_party/blink/renderer/modules/xr/xr_rigid_transform.cc index 68afd463..d0a8cc6 100644 --- a/third_party/blink/renderer/modules/xr/xr_rigid_transform.cc +++ b/third_party/blink/renderer/modules/xr/xr_rigid_transform.cc @@ -108,15 +108,8 @@ NotShared<DOMFloat32Array> XRRigidTransform::matrix() { EnsureMatrix(); - if (!matrix_array_) { - matrix_array_ = transformationMatrixToDOMFloat32Array(*matrix_); - } - if (!matrix_array_ || matrix_array_->IsDetached()) { - // A page may take the matrix_array_ value and detach it so matrix_array_ is - // a detached array buffer. This breaks the inspector, so return an empty - // array instead. - return NotShared<DOMFloat32Array>(DOMFloat32Array::Create(0)); + matrix_array_ = transformationMatrixToDOMFloat32Array(*matrix_); } return matrix_array_; diff --git a/third_party/blink/renderer/modules/xr/xr_utils.cc b/third_party/blink/renderer/modules/xr/xr_utils.cc index 55188dd..307b902 100644 --- a/third_party/blink/renderer/modules/xr/xr_utils.cc +++ b/third_party/blink/renderer/modules/xr/xr_utils.cc @@ -23,15 +23,10 @@ } gfx::Transform DOMFloat32ArrayToTransform(NotShared<DOMFloat32Array> m) { - DCHECK_EQ(m->length(), 16u); + CHECK_EQ(m->length(), 16u); return gfx::Transform::ColMajorF(m->Data()); } -gfx::Transform WTFFloatVectorToTransform(const Vector<float>& m) { - DCHECK_EQ(m.size(), 16u); - return gfx::Transform::ColMajorF(m.data()); -} - // Normalize to have length = 1.0 DOMPointReadOnly* makeNormalizedQuaternion(double x, double y, diff --git a/third_party/blink/renderer/modules/xr/xr_utils.h b/third_party/blink/renderer/modules/xr/xr_utils.h index 87b93d8..f61aa18d6 100644 --- a/third_party/blink/renderer/modules/xr/xr_utils.h +++ b/third_party/blink/renderer/modules/xr/xr_utils.h @@ -31,8 +31,6 @@ gfx::Transform DOMFloat32ArrayToTransform(NotShared<DOMFloat32Array>); -gfx::Transform WTFFloatVectorToTransform(const Vector<float>&); - DOMPointReadOnly* makeNormalizedQuaternion(double x, double y, double z, diff --git a/third_party/blink/renderer/modules/xr/xr_view.cc b/third_party/blink/renderer/modules/xr/xr_view.cc index af482ed..dcd02ac 100644 --- a/third_party/blink/renderer/modules/xr/xr_view.cc +++ b/third_party/blink/renderer/modules/xr/xr_view.cc @@ -81,7 +81,8 @@ // A page may take the projection matrix value and detach it so // projection_matrix_ is a detached array buffer. This breaks the // inspector, so return an empty array instead. - return NotShared<DOMFloat32Array>(DOMFloat32Array::Create(0)); + projection_matrix_ = + transformationMatrixToDOMFloat32Array(view_data_->ProjectionMatrix()); } return projection_matrix_; diff --git a/third_party/blink/renderer/modules/xr/xr_view.h b/third_party/blink/renderer/modules/xr/xr_view.h index f90f85a5..549c0895 100644 --- a/third_party/blink/renderer/modules/xr/xr_view.h +++ b/third_party/blink/renderer/modules/xr/xr_view.h @@ -74,7 +74,10 @@ // The transform from the view to the reference space requested by // XRFrame::getViewerPose. Member<XRRigidTransform> ref_space_from_view_; - NotShared<DOMFloat32Array> projection_matrix_; + // This is just a cached/converted version of the projection matrix from the + // view_data. It's mutable so that we can update it when queried if it was + // detached. + mutable NotShared<DOMFloat32Array> projection_matrix_; Member<XRViewport> viewport_; };
Loading diff…
Original Bug Report
reported by [email protected]
Potential out-of-bounds read in Transform::ColMajorF on undersized buffer
Security Bug
We have discovered a potential issue in Chromium that lets WebXR content trigger an out-of-bounds read in the renderer by feeding a zero-length buffer into gfx::Transform::ColMajorF.
Vulnerability Details
Transform::ColMajorFassumes the caller has provided 16 floats and blindly reads indices 0–15; the surrounding UNSAFE_TODO macros do not add checks.- When a page detaches the cached matrix buffer on an
XRRigidTransform, Blink currently handsXRRaya fresh zero-lengthDOMFloat32Array, which is then forwarded toTransform::ColMajorFwithout validation. - This results in a 64-byte read past the view, leaking heap data back to script via
XRRay’s origin/direction, or crashing on debug builds.
Code Snippets:
// ui/gfx/geometry/transform.cc (lines 115-130, current HEAD)
Transform Transform::ColMajorF(const float a[16]) {
if (AllTrue(Float4{UNSAFE_TODO(a[1]), UNSAFE_TODO(a[2]), UNSAFE_TODO(a[3]),
UNSAFE_TODO(a[4])} == Float4{0, 0, 0, 0} &
Float4{UNSAFE_TODO(a[6]), UNSAFE_TODO(a[7]), UNSAFE_TODO(a[8]),
UNSAFE_TODO(a[9])} == Float4{0, 0, 0, 0} &
Float4{UNSAFE_TODO(a[10]), UNSAFE_TODO(a[11]), UNSAFE_TODO(a[14]),
UNSAFE_TODO(a[15])} == Float4{1, 0, 0, 1})) {
return Transform(a[0], UNSAFE_TODO(a[5]), UNSAFE_TODO(a[12]),
UNSAFE_TODO(a[13]));
}
return Transform(a[0], UNSAFE_TODO(a[1]), UNSAFE_TODO(a[2]),
UNSAFE_TODO(a[3]), UNSAFE_TODO(a[4]), UNSAFE_TODO(a[5]),
UNSAFE_TODO(a[6]), UNSAFE_TODO(a[7]), UNSAFE_TODO(a[8]),
UNSAFE_TODO(a[9]), UNSAFE_TODO(a[10]), UNSAFE_TODO(a[11]),
UNSAFE_TODO(a[12]), UNSAFE_TODO(a[13]), UNSAFE_TODO(a[14]),
UNSAFE_TODO(a[15]));
}
// third_party/blink/renderer/modules/xr/xr_rigid_transform.cc (lines 115-120, current HEAD)
if (!matrix_array_ || matrix_array_->IsDetached()) {
// A page may take the matrix_array_ value and detach it so matrix_array_ is
// a detached array buffer. This breaks the inspector, so return an empty
// array instead.
return NotShared<DOMFloat32Array>(DOMFloat32Array::Create(0));
}
// third_party/blink/renderer/modules/xr/xr_ray.cc (lines 38-40, current HEAD)
XRRay::XRRay(XRRigidTransform* transform, ExceptionState& exception_state) {
NotShared<DOMFloat32Array> m = transform->matrix();
Set(DOMFloat32ArrayToTransform(m), exception_state);
}
// third_party/blink/renderer/modules/xr/xr_utils.cc (lines 25-28, current HEAD)
gfx::Transform DOMFloat32ArrayToTransform(NotShared<DOMFloat32Array> m) {
DCHECK_EQ(m->length(), 16u);
return gfx::Transform::ColMajorF(m->Data());
}
VERSION
- Chrome Version: Chromium 143.0.7470.0, Chromium 141.0.7390.65 built on Debian GNU/Linux 13 (trixie)
- Operating System: Debian GNU/Linux 13 (trixie)
REPRODUCTION CASE - DEBUG BUILD
- Launch Chromium/Chrome debug
- In a renderer console:
const t = new XRRigidTransform(); const arr = t.matrix; const ch = new MessageChannel(); ch.port1.postMessage(arr.buffer, [arr.buffer]); // detaches arr const ray = new XRRay(t); // triggers ColMajorF read - Observe
FATAL:third_party/blink/renderer/modules/xr/xr_utils.cc:26] DCHECK failed: m->length() == 16u (0 vs. 16)
REPRODUCTION CASE - OOB READ
- Launch Chromium/Chrome release
- Run poc-min-leak.js
- Observe leaked marked data
Similar Findings (related patterns in current code)
XRView::projectionMatrix()returns a zero-lengthDOMFloat32Arraywhen detached, mirroring theXRRigidTransform::matrix()behavior. While not currently passed toTransform::ColMajorF, this could become exploitable if used in transform code.- Location:
third_party/blink/renderer/modules/xr/xr_view.cclines 79–85
- Location:
WTFFloatVectorToTransform(const Vector<float>&)forwards rawm.data()toTransform::ColMajorFwith only a DCHECK on size. This is currently unused but should be hardened to prevent future misuse.- Location:
third_party/blink/renderer/modules/xr/xr_utils.cclines 30–33
- Location:
CREDIT INFORMATION
Reporter credit: Aisle Research
References
On This Page