High CVSS 8.8 firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
8.8
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 132, Firefox ESR 128.4, and Thunderbird 128.4. Some of these bugs showed evidence of memory corruption and we presume that with enough effort some of these could have been exploited to run arbitrary code.
ComponentWidget
Bug ClassMemory Corruption
Tracker1880582
Fix commitffc61add3060 (firefox) +16/-11
CWECWE-94
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedAndrew McCreight, Akmat Suleimanov
Disclosed2024-11-26

Background

`NSTrackingArea`
a Cocoa object that defines a region of an NSView for which the system delivers mouse-entered, mouse-exited, and mouse-moved events to a designated owner.
`trackingAreaView`
a computed property on Firefox’s title-bar window class that returns the NSView the tracking area should currently be attached to, and whose result can change over the object’s lifetime.
Manual reference counting
Cocoa’s retain/release ownership model, in which a view added to a tracking area must remain alive and paired with the exact view it was registered on when it is later removed.

Root Cause Analysis

The vulnerable code path is the pair updateTrackingArea / removeTrackingArea in nsCocoaWindow.mm. When installing a tracking area, updateTrackingArea read self.trackingAreaView into a local NSView* view and called [view addTrackingArea:mTrackingArea], but stored no reference to that specific view. Later, removeTrackingArea again evaluated self.trackingAreaView and called removeTrackingArea: on whatever view that expression returned at that moment, violating the invariant that a tracking area must be removed from the same view it was added to. If the title-bar view hierarchy changed between the two calls, self.trackingAreaView could return a different (or already-freed) NSView, leaving a stale NSTrackingArea registered on a view that outlived the window’s expectations and yielding a use-after-free when subsequent mouse events were dispatched.

The fix adds a retained member mViewWithTrackingArea that records the exact view the tracking area was installed on, so removal always targets that same, still-alive view and then releases it, closing the mismatch.

Key insight
The single core mistake was re-deriving the removal target from self.trackingAreaView at teardown time instead of remembering the view the tracking area was actually added to; the fix stores and retains that view in mViewWithTrackingArea, guaranteeing add and remove operate on the identical, live NSView.

Attack Path

  1. Install tracking area The window computes self.trackingAreaView and adds an NSTrackingArea to that view via updateTrackingArea, keeping no record of which view was used.
  2. Mutate the view hierarchy Web content or chrome interaction (e.g., fullscreen transitions or title-bar reconfiguration) causes self.trackingAreaView to later resolve to a different view or lets the original view be deallocated.
  3. Mismatched teardown removeTrackingArea calls removeTrackingArea: on the newly resolved view, leaving the original view still holding a dangling registration or dereferencing a freed view pointer.
  4. Trigger the dangling area A mouse-moved or mouse-exited event is delivered against the stale NSTrackingArea, dereferencing freed memory.
  5. Exploit the corruption By grooming the heap so the freed NSView slot is reoccupied with attacker-controlled data, the resulting use-after-free is steered toward controlled memory access.

Impact Assessment

An attacker who can influence title-bar view lifecycle transitions gains a use-after-free in the browser’s privileged parent (chrome/UI) process on macOS, where the Cocoa widget code runs. With reliable heap grooming this memory corruption can be leveraged toward arbitrary code execution; the precondition is triggering a view-hierarchy change between tracking-area install and removal so the two operations reference different NSView objects.

Changed Functions

FunctionChangeNotes
if
widget/cocoa/nsCocoaWindow.mm
modified

Files Changed

  • widget/cocoa/nsCocoaWindow.h
  • widget/cocoa/nsCocoaWindow.mm

Audit Directions

  • Recomputed teardown targets
    Look for cleanup code that re-evaluates a property or accessor to obtain the object to release/unregister rather than caching the object used at setup, especially across add/remove or alloc/free pairs.
  • Unretained Cocoa references
    Audit NSView/NSObject pointers stored across method calls without retain, where the underlying object’s lifetime is controlled elsewhere and can be deallocated before use.
  • Lifecycle-sensitive properties
    Scrutinize computed properties like trackingAreaView whose return value can change with view-hierarchy or fullscreen transitions, and ensure any resource keyed off them binds to a fixed snapshot.
diff --git a/widget/cocoa/nsCocoaWindow.h b/widget/cocoa/nsCocoaWindow.h
index 56112af3c1b..3ec880392d2 100644
--- a/widget/cocoa/nsCocoaWindow.h
+++ b/widget/cocoa/nsCocoaWindow.h
@@ -42,6 +42,7 @@ enum class NativeKeyBindingsType : uint8_t;
   BOOL mDisabledNeedsDisplay;
 
   NSTrackingArea* mTrackingArea;
+  NSView* mViewWithTrackingArea;
 
   NSRect mDirtyRect;
 
diff --git a/widget/cocoa/nsCocoaWindow.mm b/widget/cocoa/nsCocoaWindow.mm
index 4a8d455c6c3..18fa75869a3 100644
--- a/widget/cocoa/nsCocoaWindow.mm
+++ b/widget/cocoa/nsCocoaWindow.mm
@@ -3175,6 +3175,7 @@ static NSMutableSet* gSwizzledFrameViewClasses = nil;
   mState = nil;
   mDisabledNeedsDisplay = NO;
   mTrackingArea = nil;
+  mViewWithTrackingArea = nil;
   mDirtyRect = NSZeroRect;
   mBeingShown = NO;
   mDrawTitle = NO;
@@ -3387,25 +3388,28 @@ static const NSString* kStateWantsTitleDrawn = @"wantsTitleDrawn";
 }
 
 - (void)removeTrackingArea {
-  if (mTrackingArea) {
-    [self.trackingAreaView removeTrackingArea:mTrackingArea];
-    [mTrackingArea release];
-    mTrackingArea = nil;
-  }
+  [mViewWithTrackingArea removeTrackingArea:mTrackingArea];
+
+  [mTrackingArea release];
+  mTrackingArea = nil;
+
+  [mViewWithTrackingArea release];
+  mViewWithTrackingArea = nil;
 }
 
 - (void)updateTrackingArea {
   [self removeTrackingArea];
 
-  NSView* view = self.trackingAreaView;
+  mViewWithTrackingArea = [self.trackingAreaView retain];
   const NSTrackingAreaOptions options = NSTrackingMouseEnteredAndExited |
                                         NSTrackingMouseMoved |
                                         NSTrackingActiveAlways;
-  mTrackingArea = [[NSTrackingArea alloc] initWithRect:[view bounds]
-                                               options:options
-                                                 owner:self
-                                              userInfo:nil];
-  [view addTrackingArea:mTrackingArea];
+  mTrackingArea =
+      [[NSTrackingArea alloc] initWithRect:[mViewWithTrackingArea bounds]
+                                   options:options
+                                     owner:self
+                                  userInfo:nil];
+  [mViewWithTrackingArea addTrackingArea:mTrackingArea];
 }
 
 - (void)mouseEntered:(NSEvent*)aEvent {
Loading diff…