CVE-2024-11699
Overview
Background
- `NSTrackingArea`
- a Cocoa object that defines a region of an
NSViewfor 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
NSViewthe tracking area should currently be attached to, and whose result can change over the object’s lifetime. - Manual reference counting
- Cocoa’s
retain/releaseownership 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.
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
- Install tracking area
The window computes
self.trackingAreaViewand adds anNSTrackingAreato that view viaupdateTrackingArea, keeping no record of which view was used. - Mutate the view hierarchy
Web content or chrome interaction (e.g., fullscreen transitions or title-bar reconfiguration) causes
self.trackingAreaViewto later resolve to a different view or lets the original view be deallocated. - Mismatched teardown
removeTrackingAreacallsremoveTrackingArea:on the newly resolved view, leaving the original view still holding a dangling registration or dereferencing a freed view pointer. - Trigger the dangling area
A mouse-moved or mouse-exited event is delivered against the stale
NSTrackingArea, dereferencing freed memory. - Exploit the corruption
By grooming the heap so the freed
NSViewslot is reoccupied with attacker-controlled data, the resulting use-after-free is steered toward controlled memory access.
Impact Assessment
NSView objects.Changed Functions
| Function | Change | Notes |
|---|---|---|
ifwidget/cocoa/nsCocoaWindow.mm |
modified |
Files Changed
widget/cocoa/nsCocoaWindow.hwidget/cocoa/nsCocoaWindow.mm
Audit Directions
- Recomputed teardown targetsLook 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/removeoralloc/freepairs. - Unretained Cocoa referencesAudit
NSView/NSObjectpointers stored across method calls withoutretain, where the underlying object’s lifetime is controlled elsewhere and can be deallocated before use. - Lifecycle-sensitive propertiesScrutinize computed properties like
trackingAreaViewwhose return value can change with view-hierarchy or fullscreen transitions, and ensure any resource keyed off them binds to a fixed snapshot.
Patch
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 {