Low watch CVSS 8.1 chrome Input Validation 📄 Reporter bug report 🔧 Commit mapped

Overview

Low
Severity
8.1
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactInsufficient validation of untrusted input in WebML
DescriptionInsufficient validation of untrusted input in WebML
ComponentWebML
Sub-componentWebnn
Bug ClassInput Validation
Action tierwatch — ub-class only — no demonstrated corruption path
Computed by PatchGap's deterministic exploitability ruler from the fixing commit. patch-now = RCE-class & attacker-reachable in a default config, or exploited in the wild; watch = real security signal below that bar; noise = no security relevance.
Tracker494341335
Fix commit63c83256973f (chromium/src) +83/-7
CWECWE-20 (Improper input validation)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:N/I:H/A:H
CISA KEVNot listed
Creditedningxin.hu@intel.com
Reported2026-03-20 (per vendor advisory)
Fix landed2026-03-21 (public git)
Disclosed2026-04-07
Patch gap17 days — fix public in source before the advisory

Background

WebNN / WebML
the browser-exposed graph API (services/webnn) that lets web content build and run neural-network operations such as conv2d and pooling.
`OperandDescriptor`
the type describing a tensor’s data type and shape, whose Create enforces the per-dimension and total-element limits a valid operand may have.
Explicit padding
the Padding2d begin/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 AssignIfValid returns false if the arithmetic overflowed uint32_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.

Key insight
The code validated the user-facing input and output shapes but skipped the implementation-detail intermediate padded tensor, so an attacker-chosen padding could create an un-vetted oversized/overflowing shape; the fix validates that intermediate descriptor with overflow-checked arithmetic and the standard OperandDescriptor limits before the operation proceeds.

Attack Path

  1. Reach the validator From a WebNN-enabled context, build a graph with a conv2d, convTranspose2d, or pool2d operation, which routes into the corresponding validation function in graph_validation_utils.cc.
  2. Supply oversized padding Provide a small valid input but very large padding begin/end values so the conceptual padded spatial dimensions are huge or overflow uint32_t (as in the regression test’s [0, 32767, 32766, 5]).
  3. 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.
  4. 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

An attacker-controlled web page (or worker) using the WebNN API could pass validation for a convolution/pooling operation whose intermediate padded tensor is oversized or arithmetically overflowed, in the process that hosts WebNN graph validation. The vendor advisory rates this low and describes it as “insufficient validation of untrusted input in WebML”; it does not report exploitation in the wild, and any corruption or escalation beyond an accepted invalid descriptor is (inferred) since the diff only adds a validation guard.

Changed Functions

FunctionChangeNotes
switch
services/webnn/public/cpp/graph_validation_utils.cc
modified
if
services/webnn/public/cpp/graph_validation_utils.cc
modified

Files Changed

  • services/webnn/public/cpp/graph_validation_utils.cc
  • third_party/blink/web_tests/external/wpt/webnn/validation_tests/conv2d.https.any.js

Audit Directions

  • Unvalidated implementation intermediates
    Audit 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 arithmetic
    Search graph_validation_utils.cc and backends for raw uint32_t additions/multiplications of dimensions and padding/dilation/stride that are not wrapped in base::CheckedNumeric before use.
  • Attacker-controlled padding/stride/dilation
    Review every operation accepting Padding2d, strides, or dilations for cases where large attacker values can inflate derived dimensions past OperandDescriptor limits.
From 63c83256973f29cfe0ddb2d408267b2c2da905be Mon Sep 17 00:00:00 2001
From: Phillis Tang <phillis@chromium.org>
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 <phillis@chromium.org>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Phillis Tang <phillis@chromium.org>
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(
Loading diff…

Regression Test / PoC

shipped with the fix
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(
Loading diff…

Original Bug Report

reported by ni...@intel.com

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.

View on issue tracker