High patch-now ~ plausible CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in ServiceWorker
DescriptionUse after free in ServiceWorker
ComponentServiceWorker
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (plausible) — upheld by independent review by AI
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.
Independent review by AIPlausible
The mapped commit may not contain the whole fix.
Reviewer notes: This CL is only the default-enabled kill switch; commit says core fix landed in crrev.com/c/7727546, so the dangling-iterator root cause is consistent with but not fully proven by this diff.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker499449324
Fix commit2f10b2e5550a (chromium/src) +19/-5
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:H/PR:N/UI:R/S:C/C:H/I:H/A:H
CISA KEVNot listed
CreditedGoogle
Reported2026-04-04 (per vendor advisory)
Fix landed2026-04-09 (public git)
Disclosed2026-05-05
Patch gap26 days — fix public in source before the advisory

Background

`ServiceWorkerVersion`
the browser-process object tracking a running service worker’s inflight requests and their timeouts.
`request_timeouts_`
an ordered container of pending request timeout entries; each inflight request stores an iterator (timeout_iter) pointing into it.
`InflightRequest::timeout_iter`
a member that references the request’s entry in request_timeouts_; the fix changes its use so it is treated as an optional (std::optional) that can be cleared.
Kill switch feature flag
kServiceWorkerOptionalTimeoutIterator, a base::Feature enabled by default that gates the safe optional-iterator behavior so the fix can be disabled remotely if needed.

Root Cause Analysis

Verify before building a PoC

This analysis is consistent with the fixing commit but not fully provable from it: part of the vulnerable path lives in code the fix commit does not touch. Confirm the trigger and the sink against the source before relying on it for exploit development.
What the commit does not establish: This CL is only the default-enabled kill switch; commit says core fix landed in crrev.com/c/7727546, so the dangling-iterator root cause is consistent with but not fully proven by this diff.

When a request’s timeout fires, the timeout-processing loop erases the corresponding entry from request_timeouts_ (it = request_timeouts_.erase(it)), which invalidates any iterator still pointing at that entry.

Pre-patch, the matching InflightRequest::timeout_iter was left pointing at the now-erased element, so when the request later finished, the finish path executed request_timeouts_.erase(*request->timeout_iter) using that stale iterator—a use of an invalidated iterator that dereferences/erases freed container memory. This CL is the merge-safe kill switch around the real fix: under the enabled flag, the timeout loop looks up the corresponding InflightRequest and sets request->timeout_iter = std::nullopt before erasing, and the finish path only erases when request->timeout_iter.has_value(), so a timed-out request never re-erases through a dangling iterator. When the flag is disabled the code falls back to the previous behavior—request_timeouts_.erase(request->timeout_iter.value_or({}))—which the added comment explicitly marks “Maybe unsafe,” restoring the vulnerable path only if the kill switch is flipped.

Key insight
An InflightRequest’s stored timeout iterator was not cleared when the timeout loop erased its request_timeouts_ entry, so the later finish path erased through a dangling iterator; the fix (gated by kServiceWorkerOptionalTimeoutIterator) nulls out timeout_iter on timeout and only erases when it still has a value.

Attack Path

  1. Drive inflight requests to timeout Cause a service worker to have inflight requests whose entries in request_timeouts_ are processed by the timeout loop, which erases each entry and (pre-patch) leaves the request’s timeout_iter dangling.
  2. Complete the timed-out request Let the same request subsequently finish so the finish path runs request_timeouts_.erase(*request->timeout_iter) using the now-invalidated iterator.
  3. Trigger the use-after-free The erase dereferences the stale iterator into freed request_timeouts_ node memory, producing the use-after-free (inferred: exact heap layout and any resulting primitive not shown in the diff).

Impact Assessment

An attacker able to influence service-worker request timing (a compromised or malicious renderer coordinating request lifecycles) can cause a use-after-free of request_timeouts_ iterator/node memory in the browser process. The advisory classifies this as a use-after-free in ServiceWorker; no exploited-in-the-wild status is stated. This CL only adds the default-enabled kill switch for the underlying fix (landed in a separate CL), and any escalation to a read/write primitive or code execution beyond the iterator misuse is (inferred).

Files Changed

  • content/browser/service_worker/service_worker_version.cc
  • content/common/features.cc
  • content/common/features.h

Audit Directions

  • Iterators stored across mutations
    Audit any structure that caches container iterators (or pointers) as members and later erases through them; ensure the cached iterator is invalidated whenever the container element is removed elsewhere.
  • Dual removal paths for one entry
    Look for objects that can be removed by two independent code paths (timeout vs. completion, cancel vs. finish) where one path fails to notify the other that the entry is gone.
  • `value_or({})` / default-constructed handles
    Flag fallbacks that substitute a default-constructed iterator/handle for a missing value, which can silently operate on an invalid or unrelated container position.
From 2f10b2e5550a0ab15b7a4bcfb5edb73f5cbbd187 Mon Sep 17 00:00:00 2001
From: Takashi Nakayama <tnak@chromium.org>
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 <yyanagisawa@chromium.org>
Commit-Queue: Rakina Zata Amni <rakina@chromium.org>
Reviewed-by: Rakina Zata Amni <rakina@chromium.org>
Auto-Submit: Takashi Nakayama <tnak@chromium.org>
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);
Loading diff…

Original Bug Report

reported by rj...@google.com

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.cc
  • content/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:

  1. 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 the timeout_iter pointers stored in the corresponding InflightRequest objects.
  2. Second Loop (Callback Execution): It loops through the copied data and calls MaybeTimeoutRequest, which runs the error_callback for 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.

  1. From a compromised renderer or malicious website, register a Service Worker.
  2. 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_REQUEST keep-alive).
  3. 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.
  4. The browser’s timeout timer fires. OnTimeoutTimer erases both requests from the request_timeouts_ set, freeing both nodes.
  5. OnTimeoutTimer fires the error callback for the first request.
  6. The callback synchronously finishes the second request. FinishRequest attempts to erase the second request from the request_timeouts_ set using its dangling iterator.
  7. By grooming the heap beforehand, the attacker reclaims the freed std::set node. When the erase tree-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.

  1. Modify InflightRequest to handle an invalid iterator state (e.g., by setting it to request_timeouts_.end()).
  2. In OnTimeoutTimer, when erasing elements from the set, look up the InflightRequest in inflight_requests_ and update its timeout_iter to request_timeouts_.end().
  3. In FinishRequestWithFetchCount, check if request->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.

View on issue tracker