CVE-2026-7922
Overview
Files Changed
content/browser/service_worker/service_worker_version.cccontent/common/features.cccontent/common/features.h
Patch
From 2f10b2e5550a0ab15b7a4bcfb5edb73f5cbbd187 Mon Sep 17 00:00:00 2001 From: Takashi Nakayama <[email protected]> Date: Thu, 09 Apr 2026 05:25:00 -0700 Subject: [PATCH] Wrap SW optional timeout iterator behind a feature flag This CL introduces the killswitch for the security bug fix implemented in crrev.com/c/7727546 for safe merge. Bug: 499449324 Change-Id: I319ae1653e6b344d35e4ee6044f01dfe83b10ff2 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7742380 Reviewed-by: Yoshisato Yanagisawa <[email protected]> Commit-Queue: Rakina Zata Amni <[email protected]> Reviewed-by: Rakina Zata Amni <[email protected]> Auto-Submit: Takashi Nakayama <[email protected]> Cr-Commit-Position: refs/heads/main@{#1612121} --- diff --git a/content/browser/service_worker/service_worker_version.cc b/content/browser/service_worker/service_worker_version.cc index 40c7b8b..8b934de 100644 --- a/content/browser/service_worker/service_worker_version.cc +++ b/content/browser/service_worker/service_worker_version.cc @@ -838,8 +838,14 @@ // ServiceWorkerVersion::Request TRACE_EVENT_END("ServiceWorker", perfetto::Track::FromPointer(request), "Handled", was_handled); - if (request->timeout_iter.has_value()) { - request_timeouts_.erase(*request->timeout_iter); + if (base::FeatureList::IsEnabled( + features::kServiceWorkerOptionalTimeoutIterator)) { + if (request->timeout_iter.has_value()) { + request_timeouts_.erase(*request->timeout_iter); + } + } else { + // Equivalent to the previous, non-optional iterator behavior. Maybe unsafe. + request_timeouts_.erase(request->timeout_iter.value_or({})); } inflight_requests_.Remove(request_id); // TODO(crbug.com/40864997): remove the following DCHECK when the cause @@ -2782,9 +2788,12 @@ timed_out_infos.push_back(*it); // Erase the entry from `request_timeouts_` and update `InflightRequest` // accordingly. - InflightRequest* request = inflight_requests_.Lookup(it->id); - CHECK(request); - request->timeout_iter = std::nullopt; + if (base::FeatureList::IsEnabled( + features::kServiceWorkerOptionalTimeoutIterator)) { + InflightRequest* request = inflight_requests_.Lookup(it->id); + CHECK(request); + request->timeout_iter = std::nullopt; + } it = request_timeouts_.erase(it); } diff --git a/content/common/features.cc b/content/common/features.cc index 5fc3aec..f1c295b2 100644 --- a/content/common/features.cc +++ b/content/common/features.cc @@ -721,6 +721,10 @@ BASE_FEATURE(kServiceWorkerClientUrlIsCreationUrl, base::FEATURE_ENABLED_BY_DEFAULT); +// Kill switch for crbug.com/499449324. +BASE_FEATURE(kServiceWorkerOptionalTimeoutIterator, + base::FEATURE_ENABLED_BY_DEFAULT); + BASE_FEATURE(kServiceWorkerWindowClientInitiator, base::FEATURE_ENABLED_BY_DEFAULT); diff --git a/content/common/features.h b/content/common/features.h index 9ac4403..f0a8d22 100644 --- a/content/common/features.h +++ b/content/common/features.h @@ -223,6 +223,7 @@ CONTENT_EXPORT BASE_DECLARE_FEATURE( kServiceWorkerSuppressTimeoutWhenPaymentWindowOpen); CONTENT_EXPORT BASE_DECLARE_FEATURE(kServiceWorkerClientUrlIsCreationUrl); +CONTENT_EXPORT BASE_DECLARE_FEATURE(kServiceWorkerOptionalTimeoutIterator); CONTENT_EXPORT BASE_DECLARE_FEATURE(kServiceWorkerWindowClientInitiator); CONTENT_EXPORT BASE_DECLARE_FEATURE( kServiceWorkerSoftUpdateOnFunctionalEvent);
Original Bug Report
Potential Use-After-Free in ServiceWorkerVersion via dangling timeout iterators
Flapjack, 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 security team.
Overview: A potential Use-After-Free vulnerability exists in the browser process due to improper handling of std::set iterators in ServiceWorkerVersion::OnTimeoutTimer. When multiple requests time out simultaneously, error callbacks can synchronously trigger the cancellation of other timed-out requests, leading to the use of a dangling iterator. This heap corruption occurs in the privileged browser process and could potentially lead to a sandbox escape.
Affected files:
content/browser/service_worker/service_worker_version.cccontent/browser/service_worker/service_worker_version.h
Estimated timestamp from git blame: 2025-08-26
Summary
A potential Use-After-Free (UAF) vulnerability has been identified in ServiceWorkerVersion::OnTimeoutTimer within content/browser/service_worker/service_worker_version.cc. The flaw occurs when multiple Service Worker requests time out simultaneously. During timeout processing, std::set iterators are invalidated early, but can still be accessed if an error callback synchronously finishes another timed-out request.
Because this vulnerability triggers a UAF on internal libc++ tree nodes, it bypasses MiraclePtr (BRP) protections. Successful exploitation could lead to arbitrary memory read/write and a sandbox escape from the browser process.
Root Cause Analysis
The ServiceWorkerVersion class tracks active requests in inflight_requests_ (a base::IDMap) and their expiration times in request_timeouts_ (a std::set). Each InflightRequest stores a timeout_iter pointing to its corresponding entry in the request_timeouts_ set.
When the timeout timer fires, ServiceWorkerVersion::OnTimeoutTimer executes in two phases:
- First Loop (Iterator Invalidation): It iterates through
request_timeouts_, copies the data of all expired requests into a vector, and immediately erases them from the set (it = request_timeouts_.erase(it)). This frees the underlying red-black tree nodes and invalidates thetimeout_iterpointers stored in the correspondingInflightRequestobjects. - Second Loop (Callback Execution): It loops through the copied data and calls
MaybeTimeoutRequest, which runs theerror_callbackfor each timed-out request.
A developer comment explicitly acknowledges that these error callbacks can “synchronously finish another request”. If the error callback for Request A synchronously triggers the completion or cancellation of Request B (e.g., an external keep-alive request or an associated fetch/sync request), ServiceWorkerVersion::FinishRequest(B) is invoked.
If Request B also timed out in the same cycle, it has not yet been processed by the second loop. Therefore, it still exists in inflight_requests_. When FinishRequest (via FinishRequestWithFetchCount) handles Request B, it executes request_timeouts_.erase(request->timeout_iter);.
Since Request B’s timeout_iter was already invalidated during the first loop, this results in a UAF. The std::set::erase function attempts to read from and rebalance the freed node, corrupting the heap.
Potential Attack Steps
Note: These are suggested steps based on code analysis; our tooling agent does not yet have the ability to run working exploit code.
- From a compromised renderer or malicious website, register a Service Worker.
- Trigger two concurrent requests that are coupled in a way where the failure of one triggers the cancellation of the other (e.g., an Extension event and its associated
EXTERNAL_REQUESTkeep-alive). - Intentionally stall the Service Worker’s execution (e.g., via infinite loops or unresolved Promises) to ensure both requests hit their maximum expiration time simultaneously.
- The browser’s timeout timer fires.
OnTimeoutTimererases both requests from therequest_timeouts_set, freeing both nodes. OnTimeoutTimerfires the error callback for the first request.- The callback synchronously finishes the second request.
FinishRequestattempts to erase the second request from therequest_timeouts_set using its dangling iterator. - By grooming the heap beforehand, the attacker reclaims the freed
std::setnode. When theerasetree-rebalancing algorithm executes, it processes the attacker-controlled memory, leading to arbitrary memory read/write and eventual Remote Code Execution (RCE).
Suggested Fix
To prevent the dangling iterator dereference, ensure that when an element is erased from request_timeouts_, the timeout_iter in the corresponding InflightRequest is properly invalidated.
- Modify
InflightRequestto handle an invalid iterator state (e.g., by setting it torequest_timeouts_.end()). - In
OnTimeoutTimer, when erasing elements from the set, look up theInflightRequestininflight_requests_and update itstimeout_itertorequest_timeouts_.end(). - In
FinishRequestWithFetchCount, check ifrequest->timeout_iter != request_timeouts_.end()before attempting to erase it.
Evaluated with Chrome root at commit: 09ec9e7cc4d24823d20b6d37cf3d282734f6bf0f
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.