Chrome · Updater
CVE-2026-14113
UAF in Updater
Overview
Low
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifchrome/browser/enterprise/connectors/device_trust/key_management/core/network/fetcher/win_network_fetcher_impl.cc |
modified | |
ifcomponents/winhttp/network_fetcher.cc |
modified | |
BindOncecomponents/winhttp/network_fetcher.cc |
modified | |
NetworkFetchercomponents/winhttp/network_fetcher.h |
modified | |
TESTcomponents/winhttp/network_fetcher_unittest.cc |
modified |
Files Changed
chrome/browser/enterprise/connectors/device_trust/key_management/core/network/fetcher/win_network_fetcher_impl.cccomponents/winhttp/network_fetcher.cccomponents/winhttp/network_fetcher.hcomponents/winhttp/network_fetcher_unittest.cc
Patch
From 98cf28d9d42fab272a35163899c7041163c0ab9e Mon Sep 17 00:00:00 2001
From: S Ganesh <ganesh@chromium.org>
Date: Tue, 19 May 2026 12:45:22 -0700
Subject: [PATCH] winhttp: Safe single-use NetworkFetcher & UAF fixes
Safely resolve Use-After-Free (UAF) and double-free security
vulnerabilities in winhttp::NetworkFetcher by avoiding object reuse,
enforcing a strict single-use contract, and disconnecting callbacks
during destruction.
Specifically:
1. Re-create the winhttp::NetworkFetcher instance in
WinNetworkFetcherImpl on every Fetch() call, pooled using a
cached SharedHInternet session.
2. Disconnect the active status callback inside ~NetworkFetcher()
by calling WinHttpSetStatusCallback with nullptr. This prevents
asynchronous HANDLE_CLOSING notifications from triggering on a
deleted NetworkFetcher instance pointer.
3. Enforce a strict single-use constraint in PostRequest and
DownloadToFile by CHECKing that url_ is empty. This makes the
API fail-closed by design. Document this constraint in
network_fetcher.h.
4. Refactor legacy CompleteFetch recursive re-entrancy by directly
bind-moving fetch_complete_callback_ inside PostTaskAndReply, and
transferring base::File ownership using std::move().
5. Resolve upstream kToolbarProfileChipResizing duplicate flag
compiler error in chrome/browser/ui/ui_features.cc/.h.
6. Add CancelledFetcherDestruction and PostRequestTwiceCHECK
unit tests to verify early cancellation safety and single-use
enforcements.
Bug: 513737335
Change-Id: Ic26476d6117b3cb1cbc9254a02369c923ff2c397
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7858339
Reviewed-by: Sorin Jianu <sorin@chromium.org>
Reviewed-by: Lina Ismail <ismaill@google.com>
Commit-Queue: S Ganesh <ganesh@chromium.org>
Reviewed-by: Sebastien Lalancette <seblalancette@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633063}
---
diff --git a/chrome/browser/enterprise/connectors/device_trust/key_management/core/network/fetcher/win_network_fetcher_impl.cc b/chrome/browser/enterprise/connectors/device_trust/key_management/core/network/fetcher/win_network_fetcher_impl.cc
index b5342c8..4833ac5 100644
--- a/chrome/browser/enterprise/connectors/device_trust/key_management/core/network/fetcher/win_network_fetcher_impl.cc
+++ b/chrome/browser/enterprise/connectors/device_trust/key_management/core/network/fetcher/win_network_fetcher_impl.cc
@@ -26,15 +26,15 @@
// TODO(b/202321214): need to pass in winhttp::ProxyInfo somehow.
// If specified use it to create an winhttp::ProxyConfiguration instance.
// Otherwise create an winhttp::AutoProxyConfiguration instance.
- if (!winhttp_network_fetcher_) {
- auto proxy_config = base::MakeRefCounted<winhttp::ProxyConfiguration>();
+ auto proxy_config = base::MakeRefCounted<winhttp::ProxyConfiguration>();
+ if (!winhttp_session_) {
winhttp_session_ = base::MakeRefCounted<winhttp::SharedHInternet>(
winhttp::CreateSessionHandle(
L"DeviceTrustKeyManagement", proxy_config->access_type(),
proxy_config->proxy(), proxy_config->proxy_bypass()));
- winhttp_network_fetcher_ = base::MakeRefCounted<winhttp::NetworkFetcher>(
- winhttp_session_, std::move(proxy_config));
}
+ winhttp_network_fetcher_ = base::MakeRefCounted<winhttp::NetworkFetcher>(
+ winhttp_session_, std::move(proxy_config));
winhttp_network_fetcher_->PostRequest(
url_, body_, std::string(), headers_,
diff --git a/components/winhttp/network_fetcher.cc b/components/winhttp/network_fetcher.cc
index f906ae8..cc92799 100644
--- a/components/winhttp/network_fetcher.cc
+++ b/components/winhttp/network_fetcher.cc
@@ -75,6 +75,10 @@
NetworkFetcher::~NetworkFetcher() {
DVLOG(3) << __func__;
+ if (request_handle_.is_valid()) {
+ ::WinHttpSetStatusCallback(request_handle_.get(), nullptr,
+ WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, 0);
+ }
}
void NetworkFetcher::HandleClosing() {
@@ -92,14 +96,17 @@
void NetworkFetcher::CompleteFetch() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ if (!fetch_complete_callback_) {
+ return;
+ }
if (!file_.IsValid()) {
std::move(fetch_complete_callback_).Run(response_code_);
return;
}
base::ThreadPool::PostTaskAndReply(
FROM_HERE, kTaskTraits,
- base::BindOnce([](base::File& file) { file.Close(); }, std::ref(file_)),
- base::BindOnce(&NetworkFetcher::CompleteFetch, this));
+ base::BindOnce([](base::File file) { file.Close(); }, std::move(file_)),
+ base::BindOnce(std::move(fetch_complete_callback_), response_code_));
}
HRESULT NetworkFetcher::QueryHeaderString(const std::wstring& name,
@@ -151,6 +158,7 @@
FetchProgressCallback fetch_progress_callback,
FetchCompleteCallback fetch_complete_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(url_.is_empty());
url_ = url;
fetch_started_callback_ = std::move(fetch_started_callback);
@@ -178,6 +186,7 @@
FetchProgressCallback fetch_progress_callback,
FetchCompleteCallback fetch_complete_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
+ CHECK(url_.is_empty());
url_ = url;
file_path_ = file_path;
diff --git a/components/winhttp/network_fetcher.h b/components/winhttp/network_fetcher.h
index ce8a6945..0feee50 100644
--- a/components/winhttp/network_fetcher.h
+++ b/components/winhttp/network_fetcher.h
@@ -35,6 +35,10 @@
// Implements a network fetcher in terms of WinHTTP. The class is ref-counted
// as it is accessed from the main sequence and the worker threads in WinHTTP.
+//
+// Each NetworkFetcher instance is strictly single-use and cannot be reused to
+// send multiple requests consecutively or concurrently. Subsequent attempts to
+// start a fetch will trigger a CHECK failure.
class NetworkFetcher : public base::RefCountedThreadSafe<NetworkFetcher> {
public:
using FetchCompleteCallback = base::OnceCallback<void(int response_code)>;
diff --git a/components/winhttp/network_fetcher_unittest.cc b/components/winhttp/network_fetcher_unittest.cc
index 93fa470..df7999a 100644
--- a/components/winhttp/network_fetcher_unittest.cc
+++ b/components/winhttp/network_fetcher_unittest.cc
@@ -12,6 +12,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
+#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "components/winhttp/scoped_hinternet.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
@@ -136,4 +137,73 @@
ASSERT_EQ(network_fetcher->GetResponseBody(), kResponse);
}
+TEST(WinHttpNetworkFetcher, CancelledFetcherDestruction) {
+ net::EmbeddedTestServer test_server;
+ test_server.RegisterRequestHandler(base::BindRepeating(
+ [](const net::test_server::HttpRequest& request)
+ -> std::unique_ptr<net::test_server::HttpResponse> {
+ auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+ response->set_content("stub response");
+ return response;
+ }));
+ ASSERT_TRUE(test_server.Start());
+
+ base::test::TaskEnvironment environment;
+ {
+ auto network_fetcher = base::MakeRefCounted<NetworkFetcher>(
+ base::MakeRefCounted<SharedHInternet>(CreateSessionHandle(
+ L"WinHttpNetworkFetcherTest.CancelledFetcherDestruction",
+ WINHTTP_ACCESS_TYPE_NO_PROXY)),
+ base::MakeRefCounted<ProxyConfiguration>());
+
+ // Initiate a request.
+ network_fetcher->PostRequest(
+ /*url=*/test_server.GetURL("/"),
+ /*content_type=*/"text/plain",
+ /*post_data=*/"request data",
+ /*post_additional_headers=*/{},
+ /*fetch_started_callback=*/base::DoNothing(),
+ /*fetch_progress_callback=*/base::DoNothing(),
+ /*fetch_complete_callback=*/base::DoNothing());
+
+ // Drop the only reference to network_fetcher while the request is
+ // in-flight. This invokes the destructor and tears down the ScopedHInternet
+ // request handle.
+ }
+
+ base::RunLoop run_loop;
+ base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
+ run_loop.Run();
+}
+
+TEST(WinHttpNetworkFetcher, PostRequestTwiceCHECK) {
+ base::test::TaskEnvironment environment;
+ auto network_fetcher = base::MakeRefCounted<NetworkFetcher>(
+ base::MakeRefCounted<SharedHInternet>(CreateSessionHandle(
+ L"WinHttpNetworkFetcherTest.PostRequestTwiceCHECK",
+ WINHTTP_ACCESS_TYPE_NO_PROXY)),
+ base::MakeRefCounted<ProxyConfiguration>());
+
+ network_fetcher->PostRequest(
+ /*url=*/GURL("http://aurl"),
+ /*content_type=*/"text/plain",
+ /*post_data=*/"request data",
+ /*post_additional_headers=*/{},
Loading diff…
Regression Test / PoC
shipped with the fix
diff --git a/components/winhttp/network_fetcher_unittest.cc b/components/winhttp/network_fetcher_unittest.cc
index 93fa470..df7999a 100644
--- a/components/winhttp/network_fetcher_unittest.cc
+++ b/components/winhttp/network_fetcher_unittest.cc
@@ -12,6 +12,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
+#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "components/winhttp/scoped_hinternet.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
@@ -136,4 +137,73 @@
ASSERT_EQ(network_fetcher->GetResponseBody(), kResponse);
}
+TEST(WinHttpNetworkFetcher, CancelledFetcherDestruction) {
+ net::EmbeddedTestServer test_server;
+ test_server.RegisterRequestHandler(base::BindRepeating(
+ [](const net::test_server::HttpRequest& request)
+ -> std::unique_ptr<net::test_server::HttpResponse> {
+ auto response = std::make_unique<net::test_server::BasicHttpResponse>();
+ response->set_content("stub response");
+ return response;
+ }));
+ ASSERT_TRUE(test_server.Start());
+
+ base::test::TaskEnvironment environment;
+ {
+ auto network_fetcher = base::MakeRefCounted<NetworkFetcher>(
+ base::MakeRefCounted<SharedHInternet>(CreateSessionHandle(
+ L"WinHttpNetworkFetcherTest.CancelledFetcherDestruction",
+ WINHTTP_ACCESS_TYPE_NO_PROXY)),
+ base::MakeRefCounted<ProxyConfiguration>());
+
+ // Initiate a request.
+ network_fetcher->PostRequest(
+ /*url=*/test_server.GetURL("/"),
+ /*content_type=*/"text/plain",
+ /*post_data=*/"request data",
+ /*post_additional_headers=*/{},
+ /*fetch_started_callback=*/base::DoNothing(),
+ /*fetch_progress_callback=*/base::DoNothing(),
+ /*fetch_complete_callback=*/base::DoNothing());
+
+ // Drop the only reference to network_fetcher while the request is
+ // in-flight. This invokes the destructor and tears down the ScopedHInternet
+ // request handle.
+ }
+
+ base::RunLoop run_loop;
+ base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
+ FROM_HERE, run_loop.QuitClosure(), base::Milliseconds(100));
+ run_loop.Run();
+}
+
+TEST(WinHttpNetworkFetcher, PostRequestTwiceCHECK) {
+ base::test::TaskEnvironment environment;
+ auto network_fetcher = base::MakeRefCounted<NetworkFetcher>(
+ base::MakeRefCounted<SharedHInternet>(CreateSessionHandle(
+ L"WinHttpNetworkFetcherTest.PostRequestTwiceCHECK",
+ WINHTTP_ACCESS_TYPE_NO_PROXY)),
+ base::MakeRefCounted<ProxyConfiguration>());
+
+ network_fetcher->PostRequest(
+ /*url=*/GURL("http://aurl"),
+ /*content_type=*/"text/plain",
+ /*post_data=*/"request data",
+ /*post_additional_headers=*/{},
+ /*fetch_started_callback=*/base::DoNothing(),
+ /*fetch_progress_callback=*/base::DoNothing(),
+ /*fetch_complete_callback=*/base::DoNothing());
+
+ // The second PostRequest call on the same instance must crash the process via
+ // CHECK!
+ EXPECT_CHECK_DEATH(network_fetcher->PostRequest(
+ /*url=*/GURL("http://aurl"),
+ /*content_type=*/"text/plain",
+ /*post_data=*/"request data",
+ /*post_additional_headers=*/{},
+ /*fetch_started_callback=*/base::DoNothing(),
+ /*fetch_progress_callback=*/base::DoNothing(),
+ /*fetch_complete_callback=*/base::DoNothing()));
+}
+
} // namespace winhttp
Loading diff…
Original Bug Report
The reporter's bug is still restricted on the tracker. Chrome de-restricts security bugs ~30–90 days after the fix ships; a later run will backfill it here.
References
On This Page