CVE-2026-5915
Overview
Background
- WebNN / WebML
- the browser-exposed graph API (
services/webnn) that lets web content build and run neural-network operations such asconv2dand pooling. - `OperandDescriptor`
- the type describing a tensor’s data type and shape, whose
Createenforces the per-dimension and total-element limits a valid operand may have. - Explicit padding
- the
Padding2dbegin/end values (padding.beginning,padding.ending) that convolution and pooling conceptually add around the spatial dimensions of the input before computing output sizes. - `base::CheckedNumeric`
- an overflow-checked integer wrapper whose
AssignIfValidreturnsfalseif the arithmetic overfloweduint32_t.
Root Cause Analysis
The validation routines for conv2d, convTranspose2d, and pool2d in graph_validation_utils.cc validated the user-supplied input descriptor and computed output sizes, but never validated the intermediate tensor that these operations are implemented as padding the input into. A caller could therefore supply a small, valid input together with enormous padding values (the shipped test uses padding: [0, 32767, 32766, 5] on a tiny input) so that input + padding.beginning + padding.ending either overflows uint32_t or describes a padded shape that exceeds OperandDescriptor’s limits, without any check rejecting it.
The fix adds ValidateIntermediatePaddedDescriptor, which recomputes padded_height/padded_width with base::CheckedNumeric (rejecting overflow via AssignIfValid), assembles the padded_shape according to the InputOperandLayout (kNchw/kNhwc), and runs it through OperandDescriptor::Create so an out-of-range intermediate descriptor is rejected before any downstream sizing or allocation. This tightens validation: the three operators now call the new helper (via RETURN_IF_ERROR) before ValidateAndCalculateConv2dOutputSizes runs. The diff demonstrates only a rejected-descriptor validation gap; it does not itself show a concrete overflow sink downstream.
OperandDescriptor limits before the operation proceeds.Attack Path
- Reach the validator
From a WebNN-enabled context, build a graph with a
conv2d,convTranspose2d, orpool2doperation, which routes into the corresponding validation function ingraph_validation_utils.cc. - Supply oversized padding
Provide a small valid
inputbut very largepaddingbegin/end values so the conceptual padded spatial dimensions are huge or overflowuint32_t(as in the regression test’s[0, 32767, 32766, 5]). - Bypass the pre-patch checks Because only the input and output descriptors were validated, the intermediate padded shape was never checked, so validation previously succeeded on an out-of-range padded tensor.
- Downstream effect (inferred)
An unvalidated oversized/overflowed intermediate shape could feed later sizing or buffer-allocation logic in a backend; the patch does not show the specific sink, so any memory-safety consequence beyond “invalid descriptor accepted” is
(inferred).
Impact Assessment
(inferred) since the diff only adds a validation guard.Changed Functions
| Function | Change | Notes |
|---|---|---|
switchservices/webnn/public/cpp/graph_validation_utils.cc |
modified | |
ifservices/webnn/public/cpp/graph_validation_utils.cc |
modified |
Files Changed
services/webnn/public/cpp/graph_validation_utils.ccthird_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
Audit Directions
- Unvalidated implementation intermediatesAudit operators whose implementation materializes an intermediate tensor (padding, transposes, reshapes, concatenations) and confirm each intermediate shape is run through
OperandDescriptor::Create, not just the input/output. - Overflow in shape arithmeticSearch
graph_validation_utils.ccand backends for rawuint32_tadditions/multiplications of dimensions and padding/dilation/stride that are not wrapped inbase::CheckedNumericbefore use. - Attacker-controlled padding/stride/dilationReview every operation accepting
Padding2d, strides, or dilations for cases where large attacker values can inflate derived dimensions pastOperandDescriptorlimits.
Patch
From 63c83256973f29cfe0ddb2d408267b2c2da905be Mon Sep 17 00:00:00 2001 From: Phillis Tang <[email protected]> Date: Fri, 20 Mar 2026 19:15:48 -0700 Subject: [PATCH] webnn: validate intermediate padded tensor Validate intermediate padded tensor descriptor for conv2d and pooling. Bug: 494341335 Change-Id: I2875b72a6f9c6fd5bc92692b8eeea9d3b9ea0176 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7689396 Reviewed-by: Phillis Tang <[email protected]> Reviewed-by: Reilly Grant <[email protected]> Commit-Queue: Phillis Tang <[email protected]> Cr-Commit-Position: refs/heads/main@{#1602955} --- diff --git a/services/webnn/public/cpp/graph_validation_utils.cc b/services/webnn/public/cpp/graph_validation_utils.cc index 0b685a9..1c4529a0 100644 --- a/services/webnn/public/cpp/graph_validation_utils.cc +++ b/services/webnn/public/cpp/graph_validation_utils.cc @@ -32,6 +32,13 @@ namespace { +struct Conv2dInputOutputInfo { + uint32_t batches; + uint32_t channels; + uint32_t height; + uint32_t width; +}; + // The error message labels for corresponding operands. static constexpr char kBiasParam[] = "bias"; static constexpr char kCellStateParam[] = "cellState"; @@ -57,6 +64,51 @@ static constexpr char kWeightParam[] = "weight"; static constexpr char kZeroPointParam[] = "zeroPoint"; +// Validate that the intermediate padded shape is within the limits of +// OperandDescriptor. This is useful for convolution and pooling operations +// that may be implemented by padding the input tensor first. +base::expected<void, std::string> ValidateIntermediatePaddedDescriptor( + const ContextProperties& context_properties, + const OperandDescriptor& input, + const Padding2d& padding, + const InputOperandLayout& input_layout, + const Conv2dInputOutputInfo& input_info, + std::string_view label) { + uint32_t padded_height; + uint32_t padded_width; + if (!(base::CheckedNumeric<uint32_t>(input_info.height) + + padding.beginning.height + padding.ending.height) + .AssignIfValid(&padded_height) || + !(base::CheckedNumeric<uint32_t>(input_info.width) + + padding.beginning.width + padding.ending.width) + .AssignIfValid(&padded_width)) { + return base::unexpected( + ErrorWithLabel(label, "The padded intermediate shape is too large.")); + } + + std::array<uint32_t, 4> padded_shape; + switch (input_layout) { + case InputOperandLayout::kNchw: + padded_shape = {input_info.batches, input_info.channels, padded_height, + padded_width}; + break; + case InputOperandLayout::kNhwc: + padded_shape = {input_info.batches, padded_height, padded_width, + input_info.channels}; + break; + } + + auto padded_descriptor = OperandDescriptor::Create( + context_properties, input.data_type(), padded_shape, label); + if (!padded_descriptor.has_value()) { + return base::unexpected(ErrorWithLabel( + label, base::StrCat({"The padded intermediate operand is invalid: ", + padded_descriptor.error()}))); + } + + return base::ok(); +} + // Validate and calculate the output spatial dimensions of convTranspose2d given // input sizes, filter sizes, padding, strides, dilations and output padding. base::expected<Size2d<uint32_t>, std::string> @@ -109,13 +161,6 @@ .width = output_width.value()}; } -struct Conv2dInputOutputInfo { - uint32_t batches; - uint32_t channels; - uint32_t height; - uint32_t width; -}; - // Get the input info of 2-D direct and transposed convolution // operation given input operand and attributes. Conv2dInputOutputInfo GetConv2dInputInfo( @@ -738,6 +783,10 @@ label, "The groups must evenly divide the output channels.")); } + RETURN_IF_ERROR(ValidateIntermediatePaddedDescriptor( + context_properties, input, attributes.padding, attributes.input_layout, + input_info, label)); + // Validate and calculate output sizes. ASSIGN_OR_RETURN( Size2d<double> output_sizes, @@ -844,6 +893,10 @@ } const uint32_t output_channels = checked_output_channels.ValueOrDie(); + RETURN_IF_ERROR(ValidateIntermediatePaddedDescriptor( + context_properties, input, attributes.padding, attributes.input_layout, + input_info, label)); + // Validate and calculate output sizes. uint32_t output_height, output_width; if (attributes.output_sizes) { @@ -2245,6 +2298,14 @@ window_width = attributes.window_dimensions->width; } + RETURN_IF_ERROR(ValidateIntermediatePaddedDescriptor( + context_properties, input, attributes.padding, attributes.layout, + Conv2dInputOutputInfo{.batches = input_batches, + .channels = input_channels, + .height = input_height, + .width = input_width}, + label)); + // Reuse ValidateAndCalculateConv2dOutputSizes to calculate pool2d output // sizes. ASSIGN_OR_RETURN( diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js index 1da5895b..eff4972d 100644 --- a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js +++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js @@ -534,6 +534,21 @@ label: label, }, }, + { + name: '[conv2d] Throw if the intermediate padded tensor is too large.', + input: {dataType: 'float32', shape: [5354, 5, 1, 33]}, + filter: {dataType: 'float32', shape: [1, 6, 1, 33]}, + options: { + inputLayout: 'nhwc', + filterLayout: 'ohwi', + padding: [0, 32767, 32766, 5], + dilations: [4963, 1], + strides: [5390, 21245], + bias: {dataType: 'float32', shape: [1]}, + groups: 1, + label: label, + }, + }, ]; tests.forEach(
Regression Test / PoC
diff --git a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
index 1da5895b..eff4972d 100644
--- a/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
+++ b/third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js
@@ -534,6 +534,21 @@
label: label,
},
},
+ {
+ name: '[conv2d] Throw if the intermediate padded tensor is too large.',
+ input: {dataType: 'float32', shape: [5354, 5, 1, 33]},
+ filter: {dataType: 'float32', shape: [1, 6, 1, 33]},
+ options: {
+ inputLayout: 'nhwc',
+ filterLayout: 'ohwi',
+ padding: [0, 32767, 32766, 5],
+ dilations: [4963, 1],
+ strides: [5390, 21245],
+ bias: {dataType: 'float32', shape: [1]},
+ groups: 1,
+ label: label,
+ },
+ },
];
tests.forEach(
Original Bug Report
Heap Buffer Overflow in TFLite Pad operator due to unhandled allocation failure
VULNERABILITY DETAILS
A heap buffer overflow exists in TFLite’s SimpleMemoryArena due to unhandled memory allocation failure. When WebNN processes a conv2d operation with large padding values, the resulting padded tensor dimensions cause XNNPACK to request a very large memory arena resize. The AlignedRealloc call fails and returns a null pointer, but ResizableAlignedBuffer::Resize does not check for this failure — it overwrites the buffer pointer with null, sets data_size_ to the requested (large) size, and reports success. Subsequently, SimpleMemoryArena::Commit marks the arena as committed despite having no backing memory, and XNNPACK proceeds to compute pad offsets and write fill data through the null/invalid pointer, causing an out-of-bounds write.
Triggering conv2d parameters:
- Input shape: [5354, 5, 1, 33] (NHWC layout)
- Filter shape: [1, 6, 1, 33] (OHWI layout)
- Bias shape: [1]
- Padding: [beginningHeight=0, endingHeight=32767, beginningWidth=32766, endingWidth=5]
- Dilations: [4963, 1]
- Strides: [5390, 21245]
- Groups: 1
- Data type: float32
The large padding values (32767, 32766) combined with the input dimensions produce a padded intermediate tensor with dimensions large enough that the total byte size exceeds addressable memory. When TFLite’s arena attempts to allocate this, AlignedRealloc fails but the failure is silently ignored.
VERSION Chrome Version: asan build of content_shell at commit f8ac9f305a5e120b7c9e511a113e0c56a27290e1 Operating System: Windows, Linux, Mac where WebNN TFLite backend is used
REPRODUCTION CASE On Windows
set ASAN_OPTIONS=allocator_may_return_null=1
out\asan\content_shell.exe --no-sandbox --enable-features=WebMachineLearningNeuralNetwork --disable-features=WebNNOnnxRuntime,WebNNDirectML --enable-logging=stderr poc.html
CREDIT INFORMATION @sh…@intel.com found this issue and created the reproducer and fix proposal.