Medium chrome Uninitialized Memory 📄 Reporter bug report 🔧 Commit mapped

Overview

Medium
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUninitialized Use in WebNN
DescriptionUninitialized Use in WebNN
ComponentWebNN
Bug ClassUninitialized Memory
Tracker520191468
Fix commit6c250b17eb7c (chromium/src) +11/-65
CISA KEVNot listed
CreditedGoogle
Disclosed2026-07-29

Changed Functions

FunctionChangeNotes
if
services/webnn/ort/context_impl_ort.cc
modified
ep_name_
services/webnn/ort/device_allocator.cc
modified
can_access_on_cpu_
services/webnn/ort/tensor_impl_ort.cc
modified
size_
services/webnn/ort/tensor_impl_ort.cc
modified
if
services/webnn/ort/tensor_impl_ort.cc
modified

Files Changed

  • services/webnn/ort/context_impl_ort.cc
  • services/webnn/ort/device_allocator.cc
  • services/webnn/ort/device_allocator.h
  • services/webnn/ort/tensor_impl_ort.cc
From 6c250b17eb7c45c79f3f95d958b40e27ad3e32b3 Mon Sep 17 00:00:00 2001
From: Wei Wang <wei4.wang@intel.com>
Date: Wed, 10 Jun 2026 13:06:59 -0700
Subject: [PATCH] [WebNN] Remove WebGPU EP device tensor support for ORT

WebGPU EP device tensors can not be initialized, because
GetTensorMutableData() returns an opaque device handle rather than a
CPU-dereferenceable pointer. This means uninitialized memory contents
could be leaked, posing an information disclosure risk.

This CL removes support for WebGPU EP device tensor until they can be
properly initialized.

Bug: 520191468
Change-Id: I43c910f9129abbc19a8ba3120d183224511b12cd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7917174
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Hu, Ningxin <ningxin.hu@intel.com>
Reviewed-by: Hu, Ningxin <ningxin.hu@intel.com>
Cr-Commit-Position: refs/heads/main@{#1644836}
---

diff --git a/services/webnn/ort/context_impl_ort.cc b/services/webnn/ort/context_impl_ort.cc
index ada5d73..877ad879 100644
--- a/services/webnn/ort/context_impl_ort.cc
+++ b/services/webnn/ort/context_impl_ort.cc
@@ -507,12 +507,10 @@
   const OrtApi* ort_api = PlatformFunctions::GetInstance()->ort_api();
 
   OrtAllocator* allocator = nullptr;
-  bool can_access_on_cpu = true;
-  // Use the device allocator if it's present and should be used. Otherwise, use
-  // the default allocator which is CPU based and non-arena.
-  if (device_allocator_ && device_allocator_->ShouldUse(tensor_info)) {
+  // Use the device allocator if it's present. Otherwise, use the default
+  // allocator which is CPU based and non-arena.
+  if (device_allocator_) {
     allocator = device_allocator_->get();
-    can_access_on_cpu = device_allocator_->CanAccessOnCPU();
   } else {
     // `GetAllocatorWithDefaultOptions()` always returns the same pointer to the
     // same default allocator and its returned value should NOT be freed.
@@ -543,7 +541,7 @@
 
   return base::MakeRefCounted<TensorImplOrt>(
       std::move(receiver), *this, std::move(tensor_info), size,
-      std::move(tensor), can_access_on_cpu, device_allocator_);
+      std::move(tensor), device_allocator_);
 }
 
 base::expected<scoped_refptr<WebNNTensorImpl>, mojom::ErrorPtr>
diff --git a/services/webnn/ort/device_allocator.cc b/services/webnn/ort/device_allocator.cc
index 9820aa5d..34a21ab 100644
--- a/services/webnn/ort/device_allocator.cc
+++ b/services/webnn/ort/device_allocator.cc
@@ -10,7 +10,6 @@
 #include "services/webnn/ort/ort_status.h"
 #include "services/webnn/ort/platform_functions_ort.h"
 #include "services/webnn/public/cpp/execution_providers_info.h"
-#include "services/webnn/public/mojom/webnn_tensor.mojom.h"
 #include "third_party/windows_app_sdk_headers/src/inc/abi/winml/winml/onnxruntime_c_api.h"
 
 namespace webnn::ort {
@@ -23,8 +22,7 @@
 constexpr size_t kIntelNpuStandardPageSize = 4096;
 
 // Creates memory info for a specific EP. Currently, the device allocator only
-// supports OpenVINO and WebGPU EPs. Returns an invalid memory info if not
-// supported.
+// supports OpenVINO. Returns an invalid memory info if not supported.
 ScopedOrtMemoryInfo CreateMemoryInfo(const OrtApi* ort_api,
                                      base::cstring_view ep_name) {
   ScopedOrtMemoryInfo memory_info;
@@ -37,11 +35,6 @@
         /*alignment*/ kIntelNpuStandardPageSize, OrtDeviceAllocator,
         ScopedOrtMemoryInfo::Receiver(memory_info).get()));
     CHECK(memory_info.get());
-  } else if (ep_name == kWebGpuExecutionProvider) {
-    CHECK_STATUS(ort_api->CreateMemoryInfo(
-        "WebGPU_Buffer", OrtDeviceAllocator, /*id*/ 0, OrtMemTypeDefault,
-        ScopedOrtMemoryInfo::Receiver(memory_info).get()));
-    CHECK(memory_info.get());
   }
 
   return memory_info;
@@ -93,36 +86,19 @@
   }
   CHECK(device_allocator.get());
 
-  // SAFETY: ORT guarantees that `ep_name` is valid and null-terminated.
   return base::MakeRefCounted<DeviceAllocator>(
       base::PassKey<DeviceAllocator>(), std::move(env),
-      std::move(trivial_session), std::move(device_allocator),
-      UNSAFE_BUFFERS(base::cstring_view(ep_name)));
+      std::move(trivial_session), std::move(device_allocator));
 }
 
 DeviceAllocator::DeviceAllocator(base::PassKey<DeviceAllocator>,
                                  scoped_refptr<Environment> env,
                                  ScopedOrtSession trivial_session,
-                                 ScopedOrtAllocator device_allocator,
-                                 base::cstring_view ep_name)
+                                 ScopedOrtAllocator device_allocator)
     : env_(std::move(env)),
       trivial_session_(std::move(trivial_session)),
-      device_allocator_(std::move(device_allocator)),
-      ep_name_(ep_name) {}
+      device_allocator_(std::move(device_allocator)) {}
 
 DeviceAllocator::~DeviceAllocator() = default;
 
-bool DeviceAllocator::ShouldUse(const mojom::TensorInfoPtr& tensor_info) const {
-  // Since the WebGPU EP does not allow clients to access underlying tensors
-  // directly, only use it when WebNN developers do not need to access the
-  // underlying data.
-  if (ep_name_ == kWebGpuExecutionProvider &&
-      (tensor_info->usage.Has(MLTensorUsageFlags::kRead) ||
-       tensor_info->usage.Has(MLTensorUsageFlags::kWrite))) {
-    return false;
-  }
-
-  return true;
-}
-
 }  // namespace webnn::ort
diff --git a/services/webnn/ort/device_allocator.h b/services/webnn/ort/device_allocator.h
index bde208c0..b089b227 100644
--- a/services/webnn/ort/device_allocator.h
+++ b/services/webnn/ort/device_allocator.h
@@ -5,15 +5,10 @@
 #ifndef SERVICES_WEBNN_ORT_DEVICE_ALLOCATOR_H_
 #define SERVICES_WEBNN_ORT_DEVICE_ALLOCATOR_H_
 
-#include <string>
-
 #include "base/memory/ref_counted.h"
 #include "base/memory/scoped_refptr.h"
-#include "base/strings/cstring_view.h"
 #include "services/webnn/ort/ort_session_options.h"
 #include "services/webnn/ort/scoped_ort_types.h"
-#include "services/webnn/public/cpp/execution_providers_info.h"
-#include "services/webnn/public/mojom/webnn_tensor.mojom-forward.h"
 
 namespace webnn::ort {
 
@@ -35,20 +30,13 @@
   DeviceAllocator(base::PassKey<DeviceAllocator>,
                   scoped_refptr<Environment> env,
                   ScopedOrtSession trivial_session,
-                  ScopedOrtAllocator device_allocator,
-                  base::cstring_view ep_name);
+                  ScopedOrtAllocator device_allocator);
 
   DeviceAllocator(const DeviceAllocator&) = delete;
   DeviceAllocator& operator=(const DeviceAllocator&) = delete;
 
   OrtAllocator* get() const { return device_allocator_.get(); }
 
-  // Whether to use this device allocator depends on the tensor's
-  // usage in `tensor_info`.
-  bool ShouldUse(const mojom::TensorInfoPtr& tensor_info) const;
-  // Whether the underlying tensor data can be accessed on CPU directly.
-  bool CanAccessOnCPU() const { return ep_name_ != kWebGpuExecutionProvider; }
-
  private:
   friend class base::RefCounted<DeviceAllocator>;
 
@@ -67,9 +55,6 @@
   // does.
   ScopedOrtSession trivial_session_;
   ScopedOrtAllocator device_allocator_;
-
-  // The name of the EP associated with this allocator.
-  std::string ep_name_;
 };
 
 }  // namespace webnn::ort
diff --git a/services/webnn/ort/tensor_impl_ort.cc b/services/webnn/ort/tensor_impl_ort.cc
index 64e482ec..d3e7327 100644
--- a/services/webnn/ort/tensor_impl_ort.cc
+++ b/services/webnn/ort/tensor_impl_ort.cc
@@ -21,20 +21,16 @@
     mojom::TensorInfoPtr tensor_info,
     size_t size,
     ScopedOrtValue tensor,
-    bool can_access_on_cpu,
     scoped_refptr<DeviceAllocator> device_allocator)
     : WebNNTensorImpl(std::move(receiver), context, std::move(tensor_info)),
       device_allocator_((std::move(device_allocator))),
       tensor_(std::move(tensor)),
-      size_(size),
-      can_access_on_cpu_(can_access_on_cpu) {
+      size_(size) {
   // Initialize the tensor with zeros, otherwise, reading uninitialized memory
   // will get random values.
   // TODO(crbug.com/461303833): check whether fast HW clears can be used
   // instead.
-  if (can_access_on_cpu) {
-    std::ranges::fill(AsSpan(), 0);
-  }
Loading diff…

Original Bug Report

reported by vm...@google.com

Potential WebNN ORT WebGPU-EP device tensor zero-fill bypass leading to uninitialized memory leak

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: A potential vulnerability in the WebNN ONNX Runtime (ORT) backend allows WebGPU Execution Provider device tensors to skip zero-initialization. This happens because the zero-filling implementation in TensorImplOrt is restricted to CPU-accessible tensors, with no hardware-clear fallback. An attacker could potentially leverage this to read uninitialized GPU-arena memory via a graph dispatch laundering technique.

Affected files:

  • services/webnn/ort/tensor_impl_ort.cc
  • services/webnn/ort/device_allocator.h
  • services/webnn/ort/context_impl_ort.cc
  • services/webnn/webnn_context_impl.cc
  • services/webnn/ort/graph_impl_ort.cc
  • services/webnn/public/mojom/tensor_usage_mojom_traits.h

Estimated timestamp from git blame: 2025-11-25

Description

A potential uninitialized GPU-process memory leak exists in Chromium’s WebNN ONNX Runtime (ORT) backend implementation. Specifically, in services/webnn/ort/tensor_impl_ort.cc, TensorImplOrt only zero-initializes backing memory when the tensor is directly CPU-accessible (can_access_on_cpu is true):

TensorImplOrt::TensorImplOrt(
    mojo::PendingAssociatedReceiver<mojom::WebNNTensor> receiver,
    WebNNContextImpl& context,
    mojom::TensorInfoPtr tensor_info,
    size_t size,
    ScopedOrtValue tensor,
    bool can_access_on_cpu,
    scoped_refptr<DeviceAllocator> device_allocator)
    : WebNNTensorImpl(std::move(receiver), context, std::move(tensor_info)),
      device_allocator_((std::move(device_allocator))),
      tensor_(std::move(tensor)),
      size_(size),
      can_access_on_cpu_(can_access_on_cpu) {
  // Initialize the tensor with zeros, otherwise, reading uninitialized memory
  // will get random values.
  // TODO(crbug.com/461303833): check whether fast HW clears can be used
  // instead.
  if (can_access_on_cpu) {
    std::ranges::fill(AsSpan(), 0);
  }
}

When a tensor is created with default empty usage flags (i.e., readable and writable are omitted or set to false), the WebGPU Execution Provider (EP) allocator is selected as the device allocator (DeviceAllocator::ShouldUse returns true).

Because this allocator operates on GPU device buffers, CanAccessOnCPU() returns false. This causes can_access_on_cpu to be set to false, bypassing the CPU-based std::ranges::fill() zero-initialization logic. Since there is currently no GPU-side hardware-clear fallback implemented (as noted in the code’s TODO), the newly allocated GPU buffer contains recycled, uninitialized memory from the ORT WebGPU buffer arena.

An attacker can potentially bypass read restrictions on this uninitialized device tensor by passing it as an input to a graph dispatch operation that performs a bitwise identity transformation (such as reshape) into a CPU-readable output tensor, allowing the uninitialized GPU-process arena contents to be exfiltrated.

Suggested/Potential Steps to Reproduce

Note: These are suggested and potential steps based on source code analysis; our tooling does not currently have the capability to run code to confirm a live proof of concept.

  1. Enable the WebNN API (e.g., via --enable-features=WebMachineLearningNeuralNetwork on Windows supporting the ORT WebGPU EP).
  2. Create a WebNN GPU context:
    const ctx = await navigator.ml.createContext({deviceType: 'gpu'});
    
  3. Allocate an input tensor without specified usage parameters, defaulting its usage to empty {}:
    const A = await ctx.createTensor({dataType: 'float32', shape: [1048576]});
    
    This tensor gets allocated from the WebGPU EP device allocator arena and skips zero-initialization.
  4. Allocate a second, readable CPU-backed tensor to act as the destination:
    const B = await ctx.createTensor({dataType: 'float32', shape: [1048576], readable: true});
    
  5. Construct a graph with a reshape operator mapping an input x to y of the same shape:
    const builder = new MLGraphBuilder(ctx);
    const x = builder.input('x', {dataType: 'float32', shape: [1048576]});
    const graph = await builder.build({'y': builder.reshape(x, [1048576])});
    
  6. Dispatch the graph, using the uninitialized tensor A as input and the readable tensor B as output:
    ctx.dispatch(graph, {'x': A}, {'y': B});
    
  7. Read back the contents of B to exfiltrate the uninitialized GPU-arena memory:
    const leaked = await ctx.readTensor(B);
    

Impact

Because the OrtEnv is a process-wide singleton shared across WebNN contexts, the recycled buffer arena may contain sensitive neural network model weights, intermediate layers, and inputs/outputs from WebNN sessions of other origins running in the same GPU process, leading to a potential cross-origin information leak.

Suggested Fix

Implement a GPU-side (hardware-clear) buffer clear fallback in the TensorImplOrt constructor (or during the WebGPU device tensor creation flow in ContextImplOrt) when can_access_on_cpu is false. The backing WebGPU buffer must be fully zero-initialized on the GPU device before it can be used or referenced by any graph execution operations.

Evaluated with Chrome root at commit: d8b226a3be7c9c1ac9240c09e14698866c82e4ac


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.

View on issue tracker