High CVSS 8.3 chrome UAF 📄 Reporter bug report 🔧 Commit mapped

Overview

High
Severity
8.3
CVSS
No
Exploited ITW
Fixed
Fix Status
ImpactUse after free in Views
DescriptionUse after free in Views
ComponentViews
Bug ClassUAF
Tracker503790201
Fix commitc24bb170bc65 (chromium/src) +33/-27
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-04-17 (per vendor advisory)
Fix landed2026-05-19 (public git)
Disclosed2026-05-27
Patch gap8 days — fix public in source before the advisory

Background

Tracking rects / `NSToolTip`
When a tooltip is registered, AppKit allocates an internal NSToolTip struct and calls the view’s addTrackingRect:owner:userData: override; the view caches the owner (_trackingRectOwner) and the AppKit-supplied userData pointer (_trackingRectUserData).
`_trackingRectUserData`
A raw pointer into AppKit’s NSToolTip struct. It was declared raw_ptr<void, DanglingUntriaged> — i.e. a known-dangling-tolerated raw pointer whose dangling state was explicitly exempted from the dangling-pointer detector.
Add/remove symmetry
The remove overrides (removeTrackingRect:, _removeTrackingRects:count:) cleared _trackingRectOwner but not _trackingRectUserData, breaking the invariant that the cached pointer is valid only while a tracking rect exists.

Root Cause Analysis

The violated invariant is that _trackingRectUserData must point at a live NSToolTip struct only for as long as the matching tracking rect is registered. The add overrides set _trackingRectUserData = data; the remove overrides reset _trackingRectOwner = nil but left _trackingRectUserData holding its old value. When a view is detached from its window and the tooltip/tracking rect is removed, AppKit frees the NSToolTip struct, yet _trackingRectUserData still pointed at that freed memory — a dangling raw pointer. The DanglingUntriaged annotation deliberately suppressed detection of exactly this state. On a later tooltip update (setToolTipAtMousePoint:), because self.window is nil AppKit skips re-creating the tracking rect (so addTrackingRect: does not refresh _trackingRectUserData), and the view’s _sendToolTipMouseEntered/_sendToolTipMouseExited paths construct a synthetic NSEvent from the stale _trackingRectUserData and hand it to NSToolTipManager, which dereferences the freed struct — a use-after-free in the browser process.

The patch makes removal clear the pointer: in removeTrackingRect: and _removeTrackingRects:count: it now sets _trackingRectUserData = nullptr; alongside _trackingRectOwner = nil;, restoring add/remove symmetry so no stale pointer survives tooltip teardown. Correspondingly the member’s type is tightened from raw_ptr<void, DanglingUntriaged> to raw_ptr<void> — the dangling exemption is removed because the pointer is now always cleared on removal, so the dangling-pointer detector will catch any regression. The remaining hunks are non-functional cleanup (DCHECK→DCHECK_EQ, pointer-style formatting, comment/Radar-link updates, brace style). The advisory’s UAF class is substantiated by the diff: the fix is exactly the “clear the dangling pointer on detach” change. (The advisory also suggested extra nil-checks in the _sendToolTip* paths; the diff does not add those, relying solely on clearing the pointer.)

Key insight
The tracking-rect add path cached AppKit’s NSToolTip pointer but the remove path cleared only the owner, leaving _trackingRectUserData dangling after the struct was freed — and a DanglingUntriaged annotation hid that dangling pointer from detection until a later tooltip update dereferenced it.

Attack Path

  1. Compromised renderer Attacker has code execution in a renderer and can drive tooltip-update IPCs to the browser (precondition per advisory; IPC plumbing not in the diff — (inferred)).
  2. Register a tooltip Cause a normal tooltip on a ToolTipBaseView/RenderWidgetHostViewCocoa, so AppKit allocates the NSToolTip struct and _trackingRectUserData is cached.
  3. Detach the view Trigger window detachment (tab tearing / window close) so self.window becomes nil; the tracking rect is removed and the NSToolTip struct freed, but _trackingRectUserData remains dangling.
  4. Send a malicious update From the renderer send SetTooltipText/UpdateTooltipUnderCursor with a non-empty string, routed to setToolTipAtMousePoint:. Because the window is nil, AppKit does not refresh the stale pointer.
  5. Dereference freed memory _sendToolTipMouseEntered/_sendToolTipMouseExited builds an NSEvent from the dangling _trackingRectUserData and passes it to NSToolTipManager, which reads the freed NSToolTip — the UAF.
  6. Reclaim and escape (inferred) Since NSToolTip lives in a macOS system malloc zone outside PartitionAlloc-Everywhere, BackupRefPtr does not protect it; the attacker sprays the browser heap to control the reallocated memory and escalate to RCE in the browser process. Heap control and RCE are plausible exploitation, not shown by the diff.

Impact Assessment

The flaw is in browser-process macOS UI code, reachable from a compromised renderer via tooltip-update IPCs, and the UAF requires a detach-then-update sequence (AC:H, scope-changed per the advisory’s CVSS). Confirmed by the diff: a use-after-free of a freed AppKit NSToolTip struct via a raw pointer not cleared on detach. The gained primitive is a dangling-pointer dereference in the browser process; escalation to RCE / sandbox escape is (inferred), contingent on controlling the reallocated system-zone allocation, which the diff does not demonstrate. The removed DanglingUntriaged annotation indicates the dangling state had previously been accepted as benign, understating the risk.

Changed Functions

FunctionChangeNotes
if
ui/base/cocoa/tool_tip_base_view.mm
modified
for
ui/base/cocoa/tool_tip_base_view.mm
modified

Files Changed

  • ui/base/cocoa/tool_tip_base_view.mm

Audit Directions

  • `DanglingUntriaged` raw_ptr inventory
    Treat every raw_ptr<..., DanglingUntriaged> as a latent UAF; audit add/clear symmetry for each, since the annotation suppresses the very detection that would catch this class.
  • AppKit tracking-rect overrides
    Review other Cocoa views overriding addTrackingRect:/removeTrackingRect:/_removeTrackingRects: (WebKit-derived code especially) for cached-pointer lifetime mismatches.
  • System-zone allocations and BRP gaps
    Flag cached pointers into AppKit/system-malloc-zone structs; PartitionAlloc-Everywhere does not cover them, so their UAFs are unprotected in the browser process.
  • Detached-window tooltip/IPC paths
    Audit the setToolTipAtMousePoint: / _sendToolTipMouseEntered / _sendToolTipMouseExited window-number safety checks for other states where a nil/zero window bypasses validation (e.g. Top Chrome WebUI bridge checks that only require the app to be active).
From c24bb170bc654b5db62de638e93a37dc38a900c0 Mon Sep 17 00:00:00 2001
From: Avi Drissman <[email protected]>
Date: Tue, 19 May 2026 14:25:54 -0700
Subject: [PATCH] Clear danging pointers

When a tool tip is detached, clear out _trackingRectUserData.

Fixed: 503790201
Change-Id: I1d24cf8bd81cdb390397903f8df76e596a6a6964
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7858673
Reviewed-by: Bryan Oltman <[email protected]>
Auto-Submit: Avi Drissman <[email protected]>
Commit-Queue: Bryan Oltman <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1633109}
---

diff --git a/ui/base/cocoa/tool_tip_base_view.mm b/ui/base/cocoa/tool_tip_base_view.mm
index dfe52a1a..a041eb2 100644
--- a/ui/base/cocoa/tool_tip_base_view.mm
+++ b/ui/base/cocoa/tool_tip_base_view.mm
@@ -5,11 +5,13 @@
 #import "ui/base/cocoa/tool_tip_base_view.h"
 
 #include "base/check.h"
+#include "base/check_op.h"
 #include "base/compiler_specific.h"
 #include "base/memory/raw_ptr.h"
 #include "base/notreached.h"
 
 // Below is the nasty tooltip stuff -- copied from WebKit's WebHTMLView.mm
+// https://github.com/WebKit/WebKit/blob/main/Source/WebKitLegacy/mac/WebView/WebHTMLView.mm
 // with minor modifications for code style and commenting.
 //
 //  The 'public' interface is -setToolTipAtMousePoint:. This differs from
@@ -54,7 +56,7 @@
 @implementation ToolTipBaseView {
   // These are part of the magic tooltip code from WebKit's WebHTMLView:
   id __weak _trackingRectOwner;
-  raw_ptr<void, DanglingUntriaged> _trackingRectUserData;
+  raw_ptr<void> _trackingRectUserData;
   NSTrackingRectTag _lastToolTipTag;
   NSString* __strong _toolTip;
 }
@@ -67,37 +69,37 @@
 // See above for rationale.
 - (NSTrackingRectTag)addTrackingRect:(NSRect)rect
                                owner:(id)owner
-                            userData:(void *)data
+                            userData:(void*)data
                         assumeInside:(BOOL)assumeInside {
-  DCHECK(_trackingRectOwner == nil);
+  DCHECK_EQ(_trackingRectOwner, nil);
   _trackingRectOwner = owner;
   _trackingRectUserData = data;
   return kTrackingRectTag;
 }
 
-// Override of (apparently) a private NSView method(!) See above for rationale.
+// Override of a private NSView method. See above for rationale.
 - (NSTrackingRectTag)_addTrackingRect:(NSRect)rect
                                 owner:(id)owner
-                             userData:(void *)data
+                             userData:(void*)data
                          assumeInside:(BOOL)assumeInside
                        useTrackingNum:(int)tag {
   DCHECK(tag == 0 || tag == kTrackingRectTag);
-  DCHECK(_trackingRectOwner == nil);
+  DCHECK_EQ(_trackingRectOwner, nil);
   _trackingRectOwner = owner;
   _trackingRectUserData = data;
   return kTrackingRectTag;
 }
 
-// Override of (apparently) a private NSView method(!) See above for rationale.
-- (void)_addTrackingRects:(NSRect *)rects
+// Override of a private NSView method. See above for rationale.
+- (void)_addTrackingRects:(NSRect*)rects
                     owner:(id)owner
-             userDataList:(void **)userDataList
-         assumeInsideList:(BOOL *)assumeInsideList
-             trackingNums:(NSTrackingRectTag *)trackingNums
+             userDataList:(void**)userDataList
+         assumeInsideList:(BOOL*)assumeInsideList
+             trackingNums:(NSTrackingRectTag*)trackingNums
                     count:(int)count {
-  DCHECK(count == 1);
+  DCHECK_EQ(count, 1);
   DCHECK(trackingNums[0] == 0 || trackingNums[0] == kTrackingRectTag);
-  DCHECK(_trackingRectOwner == nil);
+  DCHECK_EQ(_trackingRectOwner, nil);
   _trackingRectOwner = owner;
   _trackingRectUserData = userDataList[0];
   trackingNums[0] = kTrackingRectTag;
@@ -106,11 +108,13 @@
 // Override of a public NSView method, replacing the inherited functionality.
 // See above for rationale.
 - (void)removeTrackingRect:(NSTrackingRectTag)tag {
-  if (tag == 0)
+  if (tag == 0) {
     return;
+  }
 
   if (tag == kTrackingRectTag) {
     _trackingRectOwner = nil;
+    _trackingRectUserData = nullptr;
     return;
   }
 
@@ -121,19 +125,21 @@
   }
 
   // If any other tracking rect is being removed, we don't know how it was
-  // created and it's possible there's a leak involved (see Radar 3500217).
+  // created and it's possible there's a leak involved (see rdar://3500217).
   NOTREACHED();
 }
 
-// Override of (apparently) a private NSView method(!)
-- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
+// Override of a private NSView method.
+- (void)_removeTrackingRects:(NSTrackingRectTag*)tags count:(int)count {
   for (int i = 0; i < count; ++i) {
     // SAFETY: count provided by caller.
     NSTrackingRectTag tag = UNSAFE_BUFFERS(tags[i]);
-    if (tag == 0)
+    if (tag == 0) {
       continue;
-    DCHECK(tag == kTrackingRectTag);
+    }
+    DCHECK_EQ(tag, kTrackingRectTag);
     _trackingRectOwner = nil;
+    _trackingRectUserData = nullptr;
   }
 }
 
@@ -160,7 +166,7 @@
   NSInteger windowNumber = self.window.windowNumber;
 
   // Only send a fake mouse enter if the mouse is actually over the window,
-  // versus over a window which overlaps it (see http://crbug.com/883269).
+  // versus over a window which overlaps it (see https://crbug.com/40092440).
   if ([NSWindow windowNumberAtPoint:NSEvent.mouseLocation
           belowWindowWithWindowNumber:0] != windowNumber) {
     return;
@@ -207,7 +213,7 @@
 // Sets the view's current tooltip, to be displayed at the current mouse
 // location. (This does not make the tooltip appear -- as usual, it only
 // appears after a delay.) Pass null to remove the tooltip.
-- (void)setToolTipAtMousePoint:(NSString *)string {
+- (void)setToolTipAtMousePoint:(NSString*)string {
   NSString* toolTip = string.length == 0 ? nil : string;
   if ((toolTip && _toolTip && [toolTip isEqualToString:_toolTip]) ||
       (!toolTip && !_toolTip)) {
@@ -229,11 +235,11 @@
   //
   // By moving the call to -removeAllTooltips outside of the conditional,
   // we can ensure any visible tooltip will be removed from the screen.
-  // See crbug.com/1409942.
+  // See https://crbug.com/40889407.
   //
   // The strategy of removing all tooltips rather than the single one that
   // was added comes from WebKit, like the rest of the code here. It
-  // apparently works around some AppKit bug.
+  // apparently works around some AppKit bug, the same rdar://3500217 as above.
   [self removeAllToolTips];
   if (toolTip) {
     NSRect wideOpenRect = NSMakeRect(-100000, -100000, 200000, 200000);
@@ -245,10 +251,10 @@
 }
 
 // NSView calls this to get the text when displaying the tooltip.
-- (NSString *)view:(NSView *)view
-  stringForToolTip:(NSToolTipTag)tag
-             point:(NSPoint)point
-          userData:(void *)data {
+- (NSString*)view:(NSView*)view
+    stringForToolTip:(NSToolTipTag)tag
+               point:(NSPoint)point
+            userData:(void*)data {
   return [_toolTip copy];
 }
 
Loading diff…

Original Bug Report

reported by [email protected]

Potential Sandbox Escape via UAF in macOS ToolTipBaseView

Flapjack, 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 without the Chrome Security team. Please see go/chrome-ai-generated-security-bugs-faq for more information.

Overview: A Use-After-Free (UAF) in macOS tooltips can potentially be exploited by a compromised renderer. When a view is detached, tooltip cleanup fails to clear an internal pointer, which can later be dereferenced when the renderer sends a malicious tooltip update.

Affected files:

  • ui/base/cocoa/tool_tip_base_view.mm

Estimated timestamp from git blame: 2015-05-19

Description

A Use-After-Free (UAF) vulnerability exists in ui/base/cocoa/tool_tip_base_view.mm on macOS. ToolTipBaseView caches a raw pointer (_trackingRectUserData) to an internal AppKit NSToolTip struct. When a view is detached from its window and the tooltip is removed, the _trackingRectUserData pointer is not cleared, leaving it dangling. A compromised renderer can force the dereference of this dangling pointer by sending subsequent tooltip update IPC messages, potentially leading to a Sandbox Escape in the Browser process.

Technical Details

  1. Allocation: When a standard tooltip is displayed, AppKit allocates an NSToolTip struct and calls the overridden addTrackingRect:... method on ToolTipBaseView (or a subclass like RenderWidgetHostViewCocoa). Chromium caches a pointer to this struct in _trackingRectUserData.
  2. Detachment: The view is detached from its NSWindow (e.g., during tab tearing or window closure), so self.window becomes nil.
  3. Removal: The tooltip is removed (either automatically or triggered by setToolTipAtMousePoint:). AppKit calls the overridden removeTrackingRect: or _removeTrackingRects:count:. These methods correctly set _trackingRectOwner to nil but fail to clear _trackingRectUserData. AppKit then frees the NSToolTip memory, leaving _trackingRectUserData dangling.
  4. Malicious Update: A compromised renderer sends a SetTooltipText (or UpdateTooltipUnderCursor) IPC message with a non-empty string. (Note: For some views, like Top Chrome WebUI, the bridge check only requires the app to be active, not the window to be valid).
  5. Execution Path: The browser routes this to -[ToolTipBaseView setToolTipAtMousePoint:].
    • It first calls [self removeAllToolTips].
    • It then attempts to add the new tooltip. Because self.window is nil, AppKit defers tracking rect creation and bypasses addTrackingRect:, leaving the dangling _trackingRectUserData untouched.
    • It then calls _sendToolTipMouseEntered. If the mouse is over the desktop, a safety check (windowNumberAtPoint) evaluates to 0, matching self.window.windowNumber (which is also 0), and bypasses the check. Otherwise, the attacker can send a second IPC update, triggering _sendToolTipMouseExited, which completely lacks a window check.
  6. UAF: Both paths construct a fake NSEvent using the dangling _trackingRectUserData and dispatch it to NSToolTipManager. AppKit dereferences the freed memory.
  7. Exploitation: Because NSToolTip is an AppKit struct, it is allocated in a macOS system malloc zone. PartitionAlloc-Everywhere (PA-E) only intercepts the default zone, meaning this allocation is likely unprotected by BackupRefPtr (BRP). An attacker controlling the renderer can spray the browser heap to control the reallocated memory, leading to RCE in the Browser process.

Suggested Fix

Ensure _trackingRectUserData is explicitly cleared alongside _trackingRectOwner.

// ui/base/cocoa/tool_tip_base_view.mm
- (void)removeTrackingRect:(NSTrackingRectTag)tag {
  ...
  if (tag == kTrackingRectTag) {
    _trackingRectOwner = nil;
    _trackingRectUserData = nullptr; // FIX
    return;
  }
  ...
}

- (void)_removeTrackingRects:(NSTrackingRectTag *)tags count:(int)count {
  ...
    DCHECK(tag == kTrackingRectTag);
    _trackingRectOwner = nil;
    _trackingRectUserData = nullptr; // FIX
  }
}

Additionally, add robust nil-checks for self.window and _trackingRectUserData in _sendToolTipMouseEntered and _sendToolTipMouseExited.

Evaluated with Chrome root at commit: c1eba8ce379f5d218a2948fafbb5dd72cfa30529


Results so far have been promising, but there can be wrong deductions. If this proves to be a false positive, please close as WAI; 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.

Raised in root component due to access or custom field issues on 1974931

View on issue tracker