Critical watch CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

Critical
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Web Apps
DescriptionUse after free in Web Apps
ComponentWeb Apps
Sub-componentPWA
Bug ClassUAF
Action tierwatch — corruption primitive but gated (uncommon) — not default-reachable
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.
Tracker517678820
Fix commitc303de53a692 (chromium/src) +5/-1
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-05-29 (per vendor advisory)
Fix landed2026-05-29 (public git)
Disclosed2026-06-08
Patch gap10 days — fix public in source before the advisory

Background

`TabbedWebAppNavigationThrottle`
a NavigationThrottle for tabbed web-app (PWA) windows that intercepts navigations and can redirect target=_blank links aimed at the home tab.
`NavigationThrottle` lifecycle
a throttle runs synchronously inside the navigation pipeline (e.g. WillStartRequest) and the pipeline resumes using the throttle/NavigationRequest objects after the handler returns.
`content::WebContents::ClosePage()`
a call that can synchronously destroy the WebContents, and with it the active NavigationRequest and the running NavigationThrottle.
`PostTask` + `WebContents::GetWeakPtr()`
deferring work to a later task turn, guarded by a WeakPtr so the closed-page call is skipped if the WebContents is already gone.

Root Cause Analysis

Inside the throttle handler (the commit identifies it as WillStartRequest, though the @@ hunk header does not name it), a target=_blank link to the home tab opens a blank tab; when more than one tab exists and the blank tab has no valid committed URL, the code called web_contents->ClosePage() synchronously. Because the throttle executes within the navigation pipeline, synchronously closing the page destroys the WebContents and therefore the live NavigationRequest and the TabbedWebAppNavigationThrottle itself. Control then returns to the still-running handler (which proceeds to return FocusHomeTab(...)) and the pipeline resumes using the throttle/request objects that ClosePage() already freed, a use-after-free.

The fix replaces the synchronous call with base::SequencedTaskRunner::GetCurrentDefault()->PostTask(...) bound to content::WebContents::ClosePage via web_contents->GetWeakPtr(), so the close runs on a later task turn after the throttle has safely finished its lifecycle, and is skipped entirely if the WebContents no longer exists.

Key insight
The throttle destroyed its own WebContents (and thus itself and the NavigationRequest) mid-handler by calling ClosePage() synchronously; the fix defers that close to a posted task guarded by a WeakPtr, so teardown cannot race the still-executing navigation pipeline.

Attack Path

  1. Open a tabbed web app The victim uses an installed PWA that runs in tabbed mode with more than one tab open.
  2. Trigger a home-tab `_blank` navigation A target=_blank link aimed at the home tab causes the browser to open a blank tab and route the navigation through TabbedWebAppNavigationThrottle.
  3. Enter the close-blank-tab branch With count() > 1 and the blank tab’s last committed URL invalid, the throttle calls ClosePage() on the blank tab’s WebContents.
  4. Synchronous self-destruction ClosePage() synchronously destroys the WebContents, the active NavigationRequest, and the throttle while the handler is still on the stack.
  5. Use-after-free on resume The navigation pipeline resumes and uses the freed throttle/NavigationRequest; the exact reused object and any further primitive are (inferred).

Impact Assessment

An attacker whose content is loaded in a tabbed PWA window can cause a use-after-free of the NavigationThrottle/NavigationRequest in the browser process by inducing the home-tab target=_blank close path. The vendor advisory rates this critical; preconditions are an installed tabbed web app with multiple tabs and a crafted _blank link to the home tab, so it requires a PWA context rather than arbitrary stock-build web content. Any escalation beyond the freed-then-used navigation objects the patch addresses (controlled reuse, code execution) is (inferred).

Files Changed

  • chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc

Audit Directions

  • Synchronous destruction inside throttles/observers
    Audit NavigationThrottle and similar pipeline callbacks for synchronous calls (ClosePage, Close, DeleteContents) that can destroy the very WebContents/request the caller continues to use.
  • Self-owned lifetime during callbacks
    Flag handlers that may free this or an object the surrounding framework still dereferences after the handler returns; prefer PostTask+WeakPtr deferral.
  • Tab-close paths in web-app UI
    Review PWA/tab-strip code that closes auxiliary or blank tabs mid-navigation, ensuring teardown is asynchronous relative to the navigation pipeline.
From c303de53a69279cd5b7b7a79aa46303a4cce0356 Mon Sep 17 00:00:00 2001
From: Dibyajyoti Pal <[email protected]>
Date: Fri, 29 May 2026 16:08:53 -0700
Subject: [PATCH] [PWA] Fix UAF in TabbedWebAppNavigationThrottle

In TabbedWebAppNavigationThrottle::WillStartRequest, target=_blank
links to the home tab cause a blank tab to be opened. The throttle
closes this blank tab by calling web_contents->ClosePage()
synchronously.

However, calling ClosePage() synchronously during throttle execution
destroys the WebContents (and consequently the active NavigationRequest
and NavigationThrottle), which leads to a User-After-Free (UAF) when
the navigation pipeline resumes execution.

This CL fixes the UAF by deferring the ClosePage() call asynchronously.
We use PostTask with a WeakPtr to the WebContents to ensure that the
current navigation throttle can finish its lifecycle safely before the
WebContents is closed.

Fixed: 517678820
Change-Id: I06dfdc109f77ee31381948d1a2705187bc42d608
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7886484
Commit-Queue: Dibyajyoti Pal <[email protected]>
Auto-Submit: Dibyajyoti Pal <[email protected]>
Reviewed-by: Nate Chapin <[email protected]>
Commit-Queue: Nate Chapin <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1638795}
---

diff --git a/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc b/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
index 5d6ba6d7..2b20ead 100644
--- a/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
+++ b/chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
@@ -4,6 +4,8 @@
 
 #include "chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.h"
 
+#include "base/functional/bind.h"
+#include "base/task/sequenced_task_runner.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser_window/public/browser_window_interface.h"
 #include "chrome/browser/ui/tabs/tab_strip_model.h"
@@ -137,7 +139,9 @@
     if (browser_window->GetTabStripModel()->count() > 1 &&
         !web_contents->GetLastCommittedURL().is_valid()) {
       DVLOG(1) << "TabbedWebAppNavigationThrottle: Closing blank tab";
-      web_contents->ClosePage();
+      base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
+          FROM_HERE, base::BindOnce(&content::WebContents::ClosePage,
+                                    web_contents->GetWeakPtr()));
     }
     DVLOG(1) << "TabbedWebAppNavigationThrottle: Redirecting to FocusHomeTab";
     return FocusHomeTab(*app_controller, *browser_window->GetTabStripModel());
Loading diff…

Original Bug Report

reported by [email protected]

Potential Browser Process UAF in TabbedWebAppNavigationThrottle via Synchronous ClosePage

Project Fortify, 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. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.

Overview: A potential Use-After-Free (UAF) vulnerability exists in TabbedWebAppNavigationThrottle when a blank tab is navigated to a home-scope URL while a modal JavaScript dialog is active. Under this condition, calling ClosePage() synchronously destroys the WebContents, the NavigationRequest, and the throttle itself. Execution then continues into FocusHomeTab(), leading to virtual method calls on the freed throttle.

Affected files:

  • chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
  • chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.h

Estimated timestamp from git blame: 2023-07-14

Summary

A potential Use-After-Free (UAF) vulnerability has been identified in TabbedWebAppNavigationThrottle when a blank tab is navigated to a home-scope URL within a tabbed Progressive Web App (PWA). If a modal JavaScript dialog is active, calling web_contents->ClosePage() synchronously destroys the WebContents, the active NavigationRequest, and the TabbedWebAppNavigationThrottle itself. Execution then continues into FocusHomeTab(), performing virtual method calls on the freed this pointer and the deallocated NavigationRequest.

Vulnerability Analysis & Lifetime Flow

Under normal circumstances, calling web_contents->ClosePage() initiates an asynchronous teardown of the page. However, if a modal JavaScript dialog is showing on the page, the page is deemed “ready to be closed” immediately, causing RenderFrameHostImpl::ClosePage to bypass unload events and take a fully synchronous destruction path:

  1. RenderFrameHostImpl::ClosePage() calls IsPageReadyToBeClosed(), which returns true because delegate_->IsJavaScriptDialogShowing() evaluates to true.
  2. This invokes ClosePageIgnoringUnloadEvents(), which synchronously calls delegate_->Close(this).
  3. The delegate calls Browser::CloseContents(), leading to chrome::CloseWebContents() and TabStripModel::CloseWebContentsAt().
  4. The TabStripModel synchronously detaches and resets the tab’s WebContentsImpl object.
  5. Destroying the WebContentsImpl cancels the active navigation, synchronously deleting the NavigationRequest and the owned NavigationThrottleRegistryImpl.
  6. This registry destruction deletes all registered throttles, including the executing TabbedWebAppNavigationThrottle instance.

Upon returning from ClosePage() in TabbedWebAppNavigationThrottle::WillStartRequest() (line 140), the execution immediately calls:

return FocusHomeTab(*app_controller, *browser_window->GetTabStripModel());

Since this has been deallocated, FocusHomeTab is called on a dangling pointer. Inside FocusHomeTab, the code attempts to call navigation_handle(), which translates to &registry_->GetNavigationHandle(), executing a virtual method call on a deallocated registry reference.

MiraclePtr (BackupRefPtr) Bypass

MiraclePtr does not protect against this Use-After-Free because:

  1. The dangling reference is the implicit stack pointer (this) during member function execution, which is not wrapped.
  2. The raw_ref reference to registry_ has already been destructed when this was deleted, releasing its refcount and allowing the backing memory to be immediately reclaimed.

Potential Reproduction Steps

Note: These are potential/suggested steps; our tooling does not currently have the capability to execute code or run a live proof of concept.

  1. Launch a tabbed PWA window with a pinned home tab.
  2. Open a new blank tab (Tab B) in the same application window.
  3. On Tab B, trigger a modal JavaScript dialog state (e.g., via RunModalAlertDialog Mojo IPC) so that is_showing_javascript_dialog_ is set to true on the WebContents.
  4. Initiate a navigation on Tab B to a URL that is within the home tab’s scope.
  5. When TabbedWebAppNavigationThrottle::WillStartRequest() is invoked, because the tab count is greater than 1 and Tab B’s last committed URL is invalid, it executes web_contents->ClosePage().
  6. Due to the active JavaScript dialog, the synchronous destruction path is taken, deallocating the throttle.
  7. Execution resumes in WillStartRequest(), which immediately enters FocusHomeTab() and performs virtual calls on the freed memory, leading to a potential browser process crash or control flow hijack.

Suggested Fix

To fix this issue, avoid closing the page synchronously within the execution frame of the navigation throttle. Instead of calling web_contents->ClosePage() synchronously during WillStartRequest, the throttle should return content::NavigationThrottle::CANCEL_AND_IGNORE and post a task to close the page asynchronously on the UI thread task runner via a WeakPtr to the WebContents:

// In chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc:
if (browser_window->GetTabStripModel()->count() > 1 &&
    !web_contents->GetLastCommittedURL().is_valid()) {
  DVLOG(1) << "TabbedWebAppNavigationThrottle: Closing blank tab asynchronously";
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&content::WebContents::ClosePage,
                                web_contents->GetWeakPtr()));
}

This ensures the throttle can return safely and allows the navigation runner to terminate processing before the WebContents (and the throttle) are destroyed.

Evaluated with Chrome root at commit: 5133b93d189b383c37805b1cf3a9d2dbfe8d7379


Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:

  • If you are familiar with the severity guidelines, you may adjust the severity.
  • If this is a false positive, and there’s no work to be done, please close as WAI.
  • If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.

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