← Chrome — 2026-W31

820623987e34d2f4b31621270a72f6ecb79356cb  [M144-LTS] prevent CommitDeferringConditionRunner from potential UAF

severity high class UAF CVSS 8.1 signal score 15.26 content/browser/ AI review PLAUSIBLE
Structural signals: smartptr_member_added (+6.47), null_guard (+2.59), uaf_lifetime (+2.2), msg_strong (+3.0), area:content/browser/ (+1.0)

The bug at a glance

The commit message and added CHECK/weak-pointer guards describe a genuine use-after-free of the NavigationRequest-owned CommitDeferringConditionRunner in the privileged browser process, reachable when a condition resumes synchronously; it is an M144-LTS backport, signaling real security impact.

A renderer or other actor able to drive a navigation whose commit deferring condition resumes synchronously (e.g. via a synchronous Mojo call) can cause the runner to re-enter freed state, giving a browser-process UAF that is a strong sandbox-escape primitive.

Root cause

ResumeProcessing() was designed to be posted asynchronously, but a synchronous Mojo reply can make a condition invoke its resume_closure during WillCommitNavigation(), re-entering ResumeProcessing() while ProcessConditions() is still on the stack. That nested processing can delete an earlier condition (or the whole NavigationRequest that owns the runner); when the stack unwinds to the outer ProcessConditions(A), it dereferences the already-freed condition/runner. The fix adds an is_in_will_commit_navigation_ guard so a synchronous ResumeProcessing() only sets was_resumed_synchronously_ and returns, captures a weak_self around the WillCommitNavigation() call to detect runner destruction, and treats a synchronous resume of a kDefer result as kProceed.

Key code

Reentrancy guard to prevent synchronous re-entry UAF

void CommitDeferringConditionRunner::ResumeProcessing() {
  if (is_in_will_commit_navigation_) {
    was_resumed_synchronously_ = true;
    return;
  }

Patch walkthrough

  • content/browser/renderer_host/commit_deferring_condition_runner.cc — ResumeProcessing() now bails out early (setting was_resumed_synchronously_) if it is called while a WillCommitNavigation() is in progress, preventing nested teardown. The WillCommitNavigation() call is wrapped with a weak_self capture and a post-call liveness check (CHECK_NE kProceed when the runner was deleted), and the kDefer path treats a synchronous resume as kProceed instead of marking is_deferred_. This removes the window where a freed condition/runner is used after an earlier condition or the owning NavigationRequest is destroyed.
  • content/browser/renderer_host/commit_deferring_condition_runner.h — Adds is_in_will_commit_navigation_ and was_resumed_synchronously_ state and updates the ResumeProcessing() comment to reflect that it may now run synchronously, documenting the reentrancy contract.
  • content/browser/renderer_host/commit_deferring_condition_runner_unittest.cc — Adds regression tests: BasicSyncResume (condition that runs resume_closure synchronously then returns kDefer) and DeleteRunnerDuringCall (condition deletes the runner during WillCommitNavigation and returns kCancelled), confirming the new guards handle synchronous resume and self-destruction.

Background

CommitDeferringConditionRunner — A browser-process object owned by a NavigationRequest that iterates a list of conditions that can defer a navigation commit until each is satisfied.

WillCommitNavigation / resume_closure — Each condition is handed a resume_closure to call (normally asynchronously) when it is ready; returning kDefer means the runner waits for that closure.

Synchronous Mojo call — A [Sync] Mojo method can run a reply/closure synchronously on the same stack, violating the asynchronous assumption behind ResumeProcessing().

WeakPtrFactory liveness check — weak_factory_.GetWeakPtr() lets the runner detect that WillCommitNavigation() destroyed the NavigationRequest that owns it before touching any member.

Vulnerability window

  1. Reachability — An attacker drives a navigation whose commit involves a deferring condition that completes via a synchronous Mojo reply (inferred as to the exact condition).
  2. Trigger — The condition calls resume_closure synchronously inside WillCommitNavigation(), re-entering ResumeProcessing()/ProcessConditions() before the outer call returns.
  3. Primitive — Nested processing frees an earlier condition or the owning NavigationRequest, and the unwinding outer frame dereferences freed memory, yielding a browser-process use-after-free.
  4. Escalation — Controlled reallocation of the freed region could turn the UAF into type confusion or control-flow hijack in the browser process (inferred).

Proof of concept

Conceptually, the bug is triggered by a navigation that reaches a CommitDeferringCondition which resolves synchronously (invokes its resume_closure before returning) while another condition’s teardown frees the runner; reproduction requires a condition whose completion path is synchronous, as modeled by the added SynchronousResumeCondition/DeletingCondition test doubles. No exploit code is provided.

Exploitation

  1. Setup — Arrange a navigation that engages a commit deferring condition whose resume path can fire synchronously (inferred).
  2. Trigger reentrancy — Cause the synchronous resume so ResumeProcessing() re-enters ProcessConditions() and frees an in-use condition/runner.
  3. Groom — Reclaim the freed allocation with attacker-influenced data before the stale pointer is used (inferred).
  4. Gain control — Leverage the dangling dereference for info leak or control-flow corruption in the browser process (inferred).

Detection & hunting

For defenders and detection engineers:

  • weggli/grep for closures invoked synchronously inside WillCommitNavigation overrides — Audit CommitDeferringCondition subclasses for any that may Run() the resume_closure before returning, which is the precondition this fix defends against.
  • grep for ‘DO NOT ADD CODE’ / weak_self checks around WillCommitNavigation in content/browser/renderer_host — Confirm the reentrancy guards are present on the LTS branch and not reverted; absence indicates a regression window.
  • ASAN/UAF crash reports referencing CommitDeferringConditionRunner::ProcessConditions — Hunt crash dumps for use-after-free frames in this runner during navigation commit.

Audit directions

  • All CommitDeferringCondition implementations in content/ — Review each WillCommitNavigation override for synchronous resume or self-destruction behavior that could re-enter the runner.
  • Other async-assumed callback runners in the navigation stack — Look for similar patterns where an object posts a resume callback but a synchronous Mojo reply can invoke it inline, re-entering an iteration loop.

Before / after

Loading diff…