High patch-now ✔︎ verified CVSS 8.8 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Site Isolation
DescriptionUse after free in Site Isolation
ComponentSite Isolation
Sub-componentInternals
Bug ClassUAF
Action tierpatch-now — corruption primitive, web-reachable in default config (confirmed) — 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 AIConfirmed
Reviewer notes: Analysis says the commit message makes the self-deletion explicit; the message only says to update state_ first 'to avoid an ordering issue'. The quoted wording comes from the new code comment. Mechanism otherwise matches the diff.
A second, independent AI reviewer (not a human) re-derived this analysis from the fixing commit and the vendor advisory, looking for errors.
Tracker405140652
Fix commit94ec04ec6bd8 (chromium/src) +6/-3
CWECWE-416 (Use-after-free)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedSven Dysthe (@svn-dys)
Reported2025-03-21 · bounty $4000 (per vendor advisory)
Fix landed2025-03-25 (public git)
Disclosed2025-04-08
Patch gap14 days — fix public in source before the advisory

Background

`NavigationThrottle`
a browser-process hook that can defer, resume, or cancel a navigation; Resume() continues a navigation that was previously deferred.
`SubframeHistoryNavigationThrottle`
a throttle used during subframe history (back/forward) navigations under Site Isolation, tracking progress in its state_ member.
`state_` / `State`
the throttle’s lifecycle field, transitioning among values including State::kDeferred and State::kRunningAfterResumeSignal.
Use-after-free (UAF)
dereferencing memory after it has been freed; here, touching the throttle instance after Resume() has synchronously destroyed it.

Root Cause Analysis

In SubframeHistoryNavigationThrottle::Resume(), the pre-patch code called NavigationThrottle::Resume() while state_ == State::kDeferred was true, and only afterward assigned state_ = State::kRunningAfterResumeSignal. The problem is that NavigationThrottle::Resume() can synchronously delete the throttle instance (it can drive the navigation to completion and tear down the throttle), so the subsequent write to state_ dereferences freed memory — a use-after-free. The commit message and the new comment make this explicit: “Resume() can synchronously delete this navigation throttle, so no code after this call should reference the throttle instance.”

The fix reorders the operations: it captures const bool should_resume = state_ == State::kDeferred;, updates state_ = State::kRunningAfterResumeSignal first while the object is still alive, and only then conditionally calls NavigationThrottle::Resume(), with no member access afterward. This removes the dangling write and preserves the intended state transition.

Key insight
The single mistake was writing to the member state_ after calling NavigationThrottle::Resume(), which can synchronously free the throttle; the fix updates state_ before the potentially self-destroying Resume() call and touches no members after it.

Attack Path

  1. Defer a subframe history navigation A page triggers a subframe back/forward navigation that causes SubframeHistoryNavigationThrottle to enter State::kDeferred.
  2. Signal resume The navigation is resumed, invoking SubframeHistoryNavigationThrottle::Resume() with state_ == State::kDeferred true.
  3. Synchronous self-deletion NavigationThrottle::Resume() synchronously completes/tears down the navigation and deletes the throttle instance while still inside the Resume() call.
  4. Use-after-free write Control returns to the pre-patch code, which writes state_ = State::kRunningAfterResumeSignal on the now-freed throttle, dereferencing freed memory; further exploitation (controlling the freed allocation for a read/write primitive) is (inferred).

Impact Assessment

An attacker able to orchestrate a deferred-then-resumed subframe history navigation can cause a write to a freed SubframeHistoryNavigationThrottle object in the browser process, a use-after-free per the advisory’s UAF classification for Site Isolation. Because it occurs in the browser process, a corrupted allocation could be leveraged toward memory corruption, but any progression to a controlled read/write or code execution is (inferred). The advisory reports this as a use-after-free and does not state in-the-wild exploitation.

Changed Functions

FunctionChangeNotes
if
content/browser/renderer_host/subframe_history_navigation_throttle.cc
modified

Files Changed

  • content/browser/renderer_host/subframe_history_navigation_throttle.cc

Audit Directions

  • Member access after resume/complete
    Audit throttles, callbacks, and observers that touch this or member state after invoking a resume/complete/continue method that can synchronously destroy the object.
  • Order of state update vs. reentrant call
    Look for the pattern of calling a base-class or delegate method that may free the object before finishing local bookkeeping; state should be committed before any potentially self-destroying call.
  • Navigation throttle lifetimes
    Review other NavigationThrottle subclasses’ Resume/Cancel implementations for the same synchronous-deletion hazard around NavigationThrottle::Resume()/CancelDeferredNavigation().
From 94ec04ec6bd89868fe9508a76d058243db8f0623 Mon Sep 17 00:00:00 2001
From: Takashi Toyoshima <toyoshim@chromium.org>
Date: Mon, 24 Mar 2025 23:28:14 -0700
Subject: [PATCH] SubframeHistoryNavigationThrottle: Update the state_ first

It's nice to update the `state_` before calling Resume()
to avoid an ordering issue.

Bug: 405140652
Change-Id: Ife607f8121eadcf30f86c5478a688bd7e61bccb1
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6387055
Commit-Queue: Takashi Toyoshima <toyoshim@chromium.org>
Reviewed-by: Alex Moshchuk <alexmos@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1437323}
---

diff --git a/content/browser/renderer_host/subframe_history_navigation_throttle.cc b/content/browser/renderer_host/subframe_history_navigation_throttle.cc
index 85377dac..2153c983 100644
--- a/content/browser/renderer_host/subframe_history_navigation_throttle.cc
+++ b/content/browser/renderer_host/subframe_history_navigation_throttle.cc
@@ -44,10 +44,13 @@
 }
 
 void SubframeHistoryNavigationThrottle::Resume() {
-  if (state_ == State::kDeferred) {
-    NavigationThrottle::Resume();
-  }
+  const bool should_resume = state_ == State::kDeferred;
   state_ = State::kRunningAfterResumeSignal;
+  if (should_resume) {
+    NavigationThrottle::Resume();
+    // `Resume()` can synchronously delete this navigation throttle, so no code
+    // after this call should reference the throttle instance.
+  }
 }
 
 void SubframeHistoryNavigationThrottle::Cancel() {
Loading diff…

Original Bug Report

reported by xp...@gmail.com

UAF when accessing member variable after destruction of throttle (SubframeHistoryNavigationThrottle)

Steps to reproduce the problem

I will provide a reproduction of the UAF soon. Bear with me, please. Though I think the UAF is trivial.

Problem Description

A SubframeHistoryNavigationThrottle is intended to defer subframe history navigations while the main frame commits main-frame same-document history navigations. When the Resume() method is called, it triggers a call to resume the current throttle 0. However, the call to NavigationThrottle::Resume() can synchronously delete this. This leads to a use-after-free when subsequently accessing the member variable state_ 1.

void SubframeHistoryNavigationThrottle::Resume() {
  if (state_ == State::kDeferred) {
    NavigationThrottle::Resume(); [0]
  }
  state_ = State::kRunningAfterResumeSignal; [1]
}

This issue resembles crbug.com/40063127.

Suggested Fix: Move the member variable assignment (state_ = State::kRunningAfterResumeSignal;) to before calling Resume():

void SubframeHistoryNavigationThrottle::Resume() {
  state_ = State::kRunningAfterResumeSignal;
  
  if (state_ == State::kDeferred) {
    NavigationThrottle::Resume();
    // `Resume()` can synchronously delete this navigation throttle, so no code
    // after this call should reference the throttle instance.
  }
}

Additional Comments

PoC soon.

Summary

UAF when accessing member variable after destruction of throttle (SubframeHistoryNavigationThrottle)

Custom Questions

Type of crash:

browser

Reporter credit:

Sven Dysthe @svn_dy

Additional Data

Category: Security
Chrome Channel: Canary
Regression: N/A

View on issue tracker