CVE-2026-11147
Overview
Changed Functions
| Function | Change | Notes |
|---|---|---|
ep_name_services/webnn/ort/device_allocator.cc |
modified |
Files Changed
services/webnn/ort/device_allocator.ccservices/webnn/ort/device_allocator.h
Patch
From ead2a8591e5f13958b0f4bd28d6b22b74a925e42 Mon Sep 17 00:00:00 2001
From: mingmingtasd <mingming1.xu@intel.com>
Date: Wed, 15 Apr 2026 18:13:55 -0700
Subject: [PATCH] WebNN: Hold env reference in DeviceAllocator for ORT backend
When a WebNN context is destroyed, tensors (held in base class
WebNNContextImpl::tensor_impls_) may be destroyed after derived
ContextImplOrt members including env_. Each tensor holds a
scoped_refptr<DeviceAllocator> which owns a trivial ORT session
and a device allocator. Their destruction may call into execution
provider (EP) DLLs that could potentially be unloaded when the ORT
environment is released (the environment is ref-counted internally
and unregisters EP libraries on destruction).
Add a scoped_refptr<Environment> to DeviceAllocator so that tensors
prevent premature environment release even if they outlive the
context's own env_ member. The env_ member is declared before
trivial_session_ and device_allocator_ so it is destroyed last
within DeviceAllocator, ensuring EP DLLs remain loaded during
session and allocator cleanup.
GraphImplOrt::ComputeResources already holds its own
scoped_refptr<Environment> before its session_ member, so graphs
are already protected and need no change.
Bug: 501731689
Change-Id: I14ec20da5822aa5fad63466be79f168d604866c3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7763906
Reviewed-by: Rafael Cintron <rafael.cintron@microsoft.com>
Reviewed-by: Reilly Grant <reillyg@chromium.org>
Commit-Queue: Xu, Mingming1 <mingming1.xu@intel.com>
Cr-Commit-Position: refs/heads/main@{#1615547}
---
diff --git a/services/webnn/ort/device_allocator.cc b/services/webnn/ort/device_allocator.cc
index 2892197..9820aa5d 100644
--- a/services/webnn/ort/device_allocator.cc
+++ b/services/webnn/ort/device_allocator.cc
@@ -95,15 +95,18 @@
// SAFETY: ORT guarantees that `ep_name` is valid and null-terminated.
return base::MakeRefCounted<DeviceAllocator>(
- base::PassKey<DeviceAllocator>(), std::move(trivial_session),
- std::move(device_allocator), UNSAFE_BUFFERS(base::cstring_view(ep_name)));
+ base::PassKey<DeviceAllocator>(), std::move(env),
+ std::move(trivial_session), std::move(device_allocator),
+ UNSAFE_BUFFERS(base::cstring_view(ep_name)));
}
DeviceAllocator::DeviceAllocator(base::PassKey<DeviceAllocator>,
+ scoped_refptr<Environment> env,
ScopedOrtSession trivial_session,
ScopedOrtAllocator device_allocator,
base::cstring_view ep_name)
- : trivial_session_(std::move(trivial_session)),
+ : env_(std::move(env)),
+ trivial_session_(std::move(trivial_session)),
device_allocator_(std::move(device_allocator)),
ep_name_(ep_name) {}
diff --git a/services/webnn/ort/device_allocator.h b/services/webnn/ort/device_allocator.h
index d3c3d6c..bde208c0 100644
--- a/services/webnn/ort/device_allocator.h
+++ b/services/webnn/ort/device_allocator.h
@@ -33,6 +33,7 @@
scoped_refptr<Environment> env);
DeviceAllocator(base::PassKey<DeviceAllocator>,
+ scoped_refptr<Environment> env,
ScopedOrtSession trivial_session,
ScopedOrtAllocator device_allocator,
base::cstring_view ep_name);
@@ -53,6 +54,12 @@
~DeviceAllocator();
+ // The environment must outlive the session and allocator because their
+ // destruction may call into execution provider DLLs that could potentially
+ // be unloaded when the environment is released. Hold a reference to
+ // prevent premature environment release.
+ scoped_refptr<Environment> env_;
+
// The trivial session is only used to keep the allocator valid. It is not
// used for inference.
// It must be declared before the allocator because the allocator wraps the
Original Bug Report
Potential UAF and call into unloaded DLL in WebNN ORT context teardown
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 without the Chrome Security team.
Overview: A potential Use-After-Free and call into an unloaded module exists in the WebNN ONNX Runtime (ORT) backend. The issue arises from a C++ object destruction order flaw where the ORT environment is destroyed before its dependent tensor objects. This prematurely unloads execution provider DLLs, leading to execution in unmapped memory and potential RCE in the GPU process.
Affected files:
services/webnn/ort/context_impl_ort.hservices/webnn/ort/device_allocator.hservices/webnn/ort/tensor_impl_ort.hservices/webnn/webnn_context_impl.hservices/webnn/ort/environment.cc
Estimated timestamp from git blame: 2025-10-03
Summary
A potential Use-After-Free (UAF) and call-into-unloaded-module vulnerability exists in the WebNN ONNX Runtime (ORT) backend during context destruction. A C++ object destruction order flaw causes the Environment object to be released before the tensors and allocators that depend on it. Consequently, ORT execution provider (EP) DLLs are unloaded while objects still hold function pointers or need to call cleanup functions within those DLLs, leading to a crash or potential Remote Code Execution (RCE) in the GPU process.
Technical Details
In services/webnn/ort/context_impl_ort.h, the ContextImplOrt class inherits from WebNNContextImpl.
ContextImplOrt declares several members, importantly:
scoped_refptr<Environment> env_;
// ...
scoped_refptr<DeviceAllocator> device_allocator_;
Its base class, WebNNContextImpl (services/webnn/webnn_context_impl.h), holds the tensors:
base::flat_set<scoped_refptr<WebNNTensorImpl>, ...> tensor_impls_;
When a ContextImplOrt object is destroyed, C++ standard destruction rules dictate that derived class members are destroyed before base class members.
- Derived members destroyed:
ContextImplOrt::env_is destroyed. If this is the last reference to the globalEnvironmentsingleton,Environment::Release()is called. This invokes the ORT C APIReleaseEnv, which unloads the execution provider DLLs (e.g., OpenVINO, DirectML) from the GPU process’s address space. - Base members destroyed:
WebNNContextImpl::tensor_impls_is subsequently destroyed, which triggers the destruction of any containedTensorImplOrtinstances. - Call into unmapped memory:
TensorImplOrtholds aScopedOrtValue(tensor_) and ascoped_refptr<DeviceAllocator>.DeviceAllocatorin turn holds aScopedOrtSession. When these objects are destructed, their cleanup traits automatically call ORT C APIs likeReleaseValueandReleaseSession. These functions rely on the underlying Execution Provider to free device memory. Because the EP DLL was already unloaded in step 1, execution jumps into unmapped memory space.
Impact
This vulnerability triggers execution of code in unmapped memory regions within the unsandboxed GPU process. If an attacker can groom the GPU process address space (e.g., via WebGPU or WebGL allocations) to map attacker-controlled data into the space previously occupied by the unloaded DLL, this could potentially result in Remote Code Execution (RCE).
Potential Reproduction Steps
Note: These are suggested/potential steps based on code analysis, as our tooling agent cannot execute code to verify the PoC directly.
- Enable the WebNN feature flag (
chrome://flags#web-machine-learning-neural-network) on a supported platform (e.g., Windows with NPU/GPU). - Create a WebNN context using a device type that utilizes an execution provider DLL (e.g.,
navigator.ml.createContext({deviceType: 'npu'})). - Create at least one tensor using
context.createTensor(...)to populatetensor_impls_. - Trigger destruction of the context (e.g., by dropping references and forcing JavaScript Garbage Collection, or closing the tab).
- The GPU process will crash attempting to execute teardown functions from the unloaded DLL.
Suggested Fix
The destruction order must be corrected to ensure that tensor_impls_ (and any other ORT objects, like graphs) are destroyed before the Environment.
This can be resolved by explicitly clearing tensor_impls_ and graph_impls_ in the ~ContextImplOrt() destructor before the derived class members are automatically destroyed:
ContextImplOrt::~ContextImplOrt() {
// Ensure tensors and graphs are destroyed before the ORT environment to
// prevent calling into unloaded execution provider DLLs.
tensor_impls_.clear();
graph_impls_.clear();
}
Evaluated with Chrome root at commit: 096fc8fdbfacf2546485756d03f160a3d04fcc9b
Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.