Overview

Critical
Severity
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Web Apps
DescriptionUse after free in Web Apps
ComponentWeb Apps
Bug ClassUAF
Tracker517678820
Fix commitc303de53a692 (chromium/src) +5/-1
CISA KEVNot listed
CreditedGoogle
Disclosed2026-06-08

Files Changed

  • chrome/browser/ui/web_applications/tabbed_web_app_navigation_throttle.cc
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