High watch 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 Browser
DescriptionUse after free in Browser
ComponentBrowser
Sub-componentDTC Consent
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.
Tracker534862220
Fix commite6bca4df6bc1 (chromium/src) +6/-1
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
CreditedGoogle
Reported2026-07-14 (per vendor advisory)
Fix landed2026-08-12 (public git)
Disclosed2026-08-18
Patch gap6 days — fix public in source before the advisory

Background

`ConsentDialogCoordinator`
browser-process UI code (chrome/browser/ui/views/device_signals_consent/) that shows the Device Trust Connector (“DTC”) device-signals consent dialog. This is an enterprise feature used when policy asks the user to consent to sharing device signals.
`chrome::ShowBrowserModal`
creates and shows a browser-modal dialog views::Widget for a Browser and returns that widget pointer.
Nested run loop
according to the commit message, showing a modal dialog on macOS can spin a nested message loop inside the call. Arbitrary queued tasks, including teardown of the browser, profile or coordinator, can run before the call returns.
`base::WeakPtr`
a non-owning pointer, made by weak_ptr_factory_.GetWeakPtr(), that becomes null when its owner is destroyed. It lets code check whether this still exists after re-entrant work.

Root Cause Analysis

According to the commit subject, ConsentDialogCoordinator::Show() assigned the result of chrome::ShowBrowserModal(browser_, CreateDeviceSignalsConsentDialogModel()) directly to the member dialog_widget_. The call is evaluated first, and the store to this->dialog_widget_ happens only after it returns. On macOS, ShowBrowserModal can spin a nested run loop, and the owning Browser, Profile or the coordinator itself may be destroyed during that loop. When control came back, the assignment wrote into a freed ConsentDialogCoordinator, which is a use-after-free write in the browser process. The code assumed that this would outlive a call that can re-enter the message loop.

The fix takes a weak_this before the call, stores the returned views::Widget* in a local variable, and assigns dialog_widget_ only if weak_this is still valid. The write is skipped if the coordinator was destroyed during the show.

Key insight
A member was written after a call that can run a nested message loop and destroy the object, so this could be dangling at the time of the write. Checking a WeakPtr taken before the call, and writing only if the object is still alive, removes the write-after-free.

Attack Path

  1. Enterprise consent flow A managed profile with the Device Trust Connector device-signals consent feature enabled by policy causes ConsentDialogCoordinator::Show() to run. Ordinary web content cannot directly cause this.
  2. Nested loop during show On macOS, chrome::ShowBrowserModal spins a nested run loop while it shows the dialog, according to the commit message.
  3. Teardown inside the loop During that loop, the browser window, profile or coordinator is torn down (for example, by window close, profile shutdown, or another queued task), which frees the ConsentDialogCoordinator (inferred trigger; the commit names only the possibility).
  4. Write to freed memory When ShowBrowserModal returns, the pre-patch code writes the widget pointer into the freed object’s dialog_widget_ field. If the freed memory has been reallocated, this could corrupt browser-process heap data (inferred).

Impact Assessment

The patch shows a pointer-sized use-after-free write into a freed ConsentDialogCoordinator in the privileged browser process. This could at worst corrupt heap memory (inferred), but the attacker has little control over the value written (a views::Widget*) and over when it happens. Reaching it needs macOS, an enterprise-managed profile where the DTC consent dialog is shown, and teardown racing with the nested run loop. The bug is not shown to be reachable from web content. The vendor advisory rates this high severity and does not report in-the-wild exploitation; NVD scores it CVSS 8.8.

Changed Functions

FunctionChangeNotes
if
chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc
modified

Files Changed

  • chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc

Audit Directions

  • Member writes after modal/show calls
    Search for x_ = chrome::ShowBrowserModal(...), ShowWebModal, constrained_window::Show*, views::Widget::Show, RunMenuAt and similar calls whose result is assigned straight to a member. On macOS these can spin nested loops that destroy this.
  • Re-entrancy through nested run loops
    Review coordinators and controllers owned by Browser, Profile or tab helpers for any use of this after calls that may pump messages (modal sheets, file pickers, RunLoop().Run()), and require a WeakPtr or AutoReset-style guard.
  • Enterprise and consent UI lifecycles
    Audit other policy-driven dialogs (device trust, enterprise sign-in, management disclosure) for coordinators whose lifetime is tied to a browser or profile that can shut down while the dialog is opening.
From e6bca4df6bc1e28a27e0dbf2921b3b5502a9310b Mon Sep 17 00:00:00 2001
From: Zonghan Xu <[email protected]>
Date: Wed, 12 Aug 2026 05:20:23 -0700
Subject: [PATCH] [DTC Consent] [Fortify] Fix potential UAF in ConsentDialogCoordinator::Show()

Showing a modal dialog can spin a nested run loop on macOS, during
which the owning browser, profile, or coordinator may be torn down.
Guard the post-show write with a weak pointer to avoid a
use-after-free write if `this` is destroyed during ShowBrowserModal.

Bug: 534862220
Change-Id: Ia3536e34be051089bfea7633415ed0939ef933ab
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/8242507
Reviewed-by: Dominique Fauteux-Chapleau <[email protected]>
Commit-Queue: Dominique Fauteux-Chapleau <[email protected]>
Auto-Submit: Zonghan Xu <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1677948}
---
diff --git a/chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc b/chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc
index 349b320..86ef5bba 100644
--- a/chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc
+++ b/chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc
@@ -152,8 +152,13 @@
     return;
   }
   base::RecordAction(base::UserMetricsAction("DeviceSignalsConsent_Shown"));
-  dialog_widget_ = chrome::ShowBrowserModal(
+
+  auto weak_this = weak_ptr_factory_.GetWeakPtr();
+  views::Widget* widget = chrome::ShowBrowserModal(
       browser_, CreateDeviceSignalsConsentDialogModel());
+  if (weak_this) {
+    dialog_widget_ = widget;
+  }
 }
 
 void ConsentDialogCoordinator::OnConsentDialogAccept() {
Loading diff…

Original Bug Report

reported by [email protected]

Potential UAF write in ConsentDialogCoordinator::Show due to macOS nested run loop

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 write vulnerability exists in ConsentDialogCoordinator::Show on macOS. Calling chrome::ShowBrowserModal synchronously runs a nested run loop that can process incoming tasks or IPCs, potentially destroying the ConsentDialogCoordinator object before the function returns. This could allow a heap write-after-free when assigning the resulting widget pointer to a member of the freed coordinator.

Affected files:

  • chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc
  • chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.h
  • chrome/browser/enterprise/connectors/device_trust/navigation_throttle.cc
  • chrome/browser/enterprise/connectors/device_trust/navigation_throttle.h

Estimated timestamp from git blame: 2023-05-25

Description

A potential use-after-free (UAF) write vulnerability has been identified in ConsentDialogCoordinator::Show() on macOS. The root cause is a synchronous nested run loop spawned during the modal sheet presentation, which permits arbitrary task processing (including frame destruction) while ConsentDialogCoordinator::Show() is suspended on the call stack.

Root Cause Analysis

In chrome/browser/ui/views/device_signals_consent/consent_dialog_coordinator.cc, the Show() method is defined as follows:

void ConsentDialogCoordinator::Show() {
  if (dialog_widget_ && !dialog_widget_->IsClosed()) {
    ...
    return;
  }
  base::RecordAction(base::UserMetricsAction("DeviceSignalsConsent_Shown"));
  dialog_widget_ = chrome::ShowBrowserModal(
      browser_, CreateDeviceSignalsConsentDialogModel());
}

When chrome::ShowBrowserModal is called:

  1. It delegates to BrowserModalHelper::Show, which creates a views::BubbleDialogModelHost with ModalType::kWindow and calls widget->Show() (components/constrained_window/constrained_window_views.cc:215).
  2. On macOS, this triggers the in-process Cocoa window bridge (NativeWidgetNSWindowBridge::ShowAsModalSheet), which synchronously invokes -[NSWindow beginSheet:completionHandler:] on the UI thread’s stack (components/remote_cocoa/app_shim/native_widget_ns_window_bridge.mm:2232).
  3. beginSheet: enters a synchronous nested run loop to perform the sheet’s slide-down presentation animation.

During this nested run loop, the UI thread continues to pump events, allowing processing of arbitrary Mojo IPCs or timers. If a renderer-initiated navigation or window-close event occurs for the deferred frame:

  1. The pending NavigationRequest is aborted and synchronously destroyed.
  2. The owned DeviceTrustNavigationThrottle is deleted (chrome/browser/enterprise/connectors/device_trust/navigation_throttle.cc:129).
  3. The throttle deletes its owned consent_requester_ (std::unique_ptr<ConsentRequester>), invoking ~ConsentDialogCoordinator().
  4. Since dialog_widget_ is still nullptr (as the assignment has not yet executed), the destructor’s safety checks do not perform any widget cleanup, and the ConsentDialogCoordinator heap allocation is freed.

Once the AppKit nested loop yields and chrome::ShowBrowserModal returns the Widget* pointer, execution resumes at the call site. The code then attempts to write the pointer to this->dialog_widget_. Since this has already been deleted, this results in a write-after-free (and a potential reference count decrement inside raw_ptr::operator= if the slot has been reallocated to attacker-influenced data).

Potential Trigger Steps

Note: These are suggested potential steps, as our testing tools do not currently have the capability to execute code.

  1. Establish an enterprise-managed macOS environment where Device Trust consent collection is enabled (ShouldCollectConsent() returns true).
  2. Navigate a main frame to an attacker-controlled page, which spawns a popup (w = window.open(...)).
  3. Have the user perform a gesture in the popup to navigate to a monitored Device Trust URL, triggering the deferred DeviceTrustNavigationThrottle and posting the task to show the consent dialog.
  4. Within the popup origin, use a timed script (e.g., setTimeout) to close the popup or navigate it to about:blank within the ~200ms slide-down animation window.
  5. The browser processes the navigation cancel/abort IPC, destroying the deferred NavigationRequest, the throttle, and the coordinator object.
  6. The nested loop returns, triggering the UAF write to this->dialog_widget_ on the freed coordinator’s address.

Suggested Fix

To prevent this issue, we should use a base::WeakPtr to guard the assignment to dialog_widget_ after chrome::ShowBrowserModal returns, ensuring the coordinator object is still alive:

void ConsentDialogCoordinator::Show() {
  if (dialog_widget_ && !dialog_widget_->IsClosed()) {
    if (!dialog_widget_->IsVisible()) {
      dialog_widget_->Show();
    }
    return;
  }
  base::RecordAction(base::UserMetricsAction("DeviceSignalsConsent_Shown"));
  
  auto weak_this = weak_ptr_factory_.GetWeakPtr();
  views::Widget* widget = chrome::ShowBrowserModal(
      browser_, CreateDeviceSignalsConsentDialogModel());
  if (weak_this) {
    dialog_widget_ = widget;
  }
}

Evaluated with Chrome root at commit: b5b015ea5f690560237d1f0cff1405844cd12b8d


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