High CVSS 9.8 firefox Memory Corruption 🔧 Commit mapped

Overview

High
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 134 and Thunderbird 134. 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
Tracker1939063
Fix commit465b5164acd4 (firefox) +8/-6
CWECWE-787 (Out-of-bounds write)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedThe Mozilla Fuzzing Team
Disclosed2025-02-04

Background

`nsIWidget`
Gecko’s cross-platform interface for a native windowing surface, refcounted and arranged into a parent/child tree of widgets.
`nsBaseWidget`
the shared base class implementing common widget behavior (child-list bookkeeping, compositor teardown) that per-platform subclasses like nsWindow and nsChildView derive from.
Child list
the set of child widgets a parent tracks; RemoveAllChildren and RemoveFromChildList mutate these back-references so a destroyed widget is no longer reachable from its neighbors.
`kungFuDeathGrip`
a local nsCOMPtr idiom that holds a temporary strong reference to this so the object is not freed mid-function while teardown code drops other references.

Root Cause Analysis

During widget teardown the child-disconnection step (RemoveAllChildren) and the parent-disconnection step were handled inconsistently across platforms: some backends called RemoveAllChildren in their own Destroy/OnDestroy while nsBaseWidget::Destroy cleared mParent without clearing the reverse child link at the correct point. This left a window where a widget could be removed from its parent’s child list yet its own children — or the parent itself — still referenced it (or vice versa), so navigating the now-stale widget tree during destruction could dereference an object whose last strong reference had already been dropped, producing a use-after-free. The invariant violated is that a widget’s parent and child back-references must be severed atomically and in one authoritative place before the object can be released.

The fix centralizes child disconnection inside nsBaseWidget::Destroy (calling RemoveAllChildren there and setting mParent = nullptr immediately after RemoveFromChildList), and removes the now-redundant per-platform RemoveAllChildren calls in the Android and GTK backends. It additionally adds a kungFuDeathGrip in the UIKit nsWindow::Destroy so the widget survives its own teardown, closing the memory-corruption window.

Key insight
The core mistake was scattering and mis-ordering the severing of parent/child back-references during widget destruction, allowing a stale reference into a partially-freed widget tree; the fix consolidates that unlinking into a single authoritative point in nsBaseWidget::Destroy and pins the object alive with a death-grip reference while it happens.

Attack Path

  1. Trigger widget teardown Web content or UI action closes a window/frame, invoking the platform Destroy path on an nsWidget whose tree still has live parent and child links.
  2. Race the unlink ordering Because RemoveAllChildren and the mParent clear are not sequenced atomically in one place, the widget is dropped from one side of the tree while a stale back-reference to it (or to a child) persists.
  3. Release the last reference Compositor shutdown or a nested destroy releases the final strong reference to the affected widget, freeing its memory while a dangling pointer still points at it.
  4. Dereference freed widget Continued teardown or event processing walks the stale tree link and reads/writes the freed nsWidget, corrupting memory.
  5. Escalate to code execution An attacker who can shape heap layout around the freed object turns the use-after-free write into control of a reclaimed allocation and, with effort, arbitrary code execution.

Impact Assessment

An attacker gains a use-after-free that can be leveraged into memory corruption and, with sufficient heap grooming, arbitrary code execution in the process hosting the widget (the browser/UI or content process depending on platform). The precondition is triggering the vulnerable widget-destruction sequence, which is reachable through ordinary window/frame lifecycle events driven by content or the UI. Rated high impact consistent with a presumed-exploitable memory-safety flaw.

Changed Functions

FunctionChangeNotes
if
widget/cocoa/nsChildView.mm
modified
if
widget/nsBaseWidget.cpp
modified

Files Changed

  • widget/android/nsWindow.cpp
  • widget/cocoa/nsChildView.mm
  • widget/gtk/nsWindow.cpp
  • widget/nsBaseWidget.cpp
  • widget/uikit/nsWindow.mm

Audit Directions

  • Scattered lifecycle unlinking
    audit every platform Destroy/OnDestroy for parent/child back-reference clearing that duplicates or races the base-class logic instead of deferring to a single authoritative point.
  • Missing death-grip during teardown
    look for Destroy paths that drop references (compositor shutdown, native-view teardown) without a kungFuDeathGrip, where this may be freed before the function returns.
  • Ordering of RemoveFromChildList vs mParent
    check other tree-structured refcounted objects to ensure the forward and reverse links are cleared together before the object can be released.
diff --git a/widget/android/nsWindow.cpp b/widget/android/nsWindow.cpp
index c377529e589..b7211796e42 100644
--- a/widget/android/nsWindow.cpp
+++ b/widget/android/nsWindow.cpp
@@ -2252,8 +2252,6 @@ void nsWindow::Destroy() {
   // Stuff below may release the last ref to this
   nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
 
-  RemoveAllChildren();
-
   // Ensure the compositor has been shutdown before this nsWindow is potentially
   // deleted
   nsBaseWidget::DestroyCompositor();
diff --git a/widget/cocoa/nsChildView.mm b/widget/cocoa/nsChildView.mm
index 5aeacfb2838..e8757f47bf7 100644
--- a/widget/cocoa/nsChildView.mm
+++ b/widget/cocoa/nsChildView.mm
@@ -359,7 +359,9 @@ nsCocoaWindow* nsChildView::GetAppWindowWidget() const {
 void nsChildView::Destroy() {
   NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
 
-  if (mOnDestroyCalled) return;
+  if (mOnDestroyCalled) {
+    return;
+  }
   mOnDestroyCalled = true;
 
   // Stuff below may delete the last ref to this
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index 6ac27f3ba16..908e01b8c43 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -564,8 +564,6 @@ void nsWindow::OnDestroy(void) {
   // Remove association between this object and its parent and siblings.
   nsBaseWidget::Destroy();
 
-  RemoveAllChildren();
-
   NotifyWindowDestroyed();
 }
 
diff --git a/widget/nsBaseWidget.cpp b/widget/nsBaseWidget.cpp
index 373f1b7a728..c7ec10d07fc 100644
--- a/widget/nsBaseWidget.cpp
+++ b/widget/nsBaseWidget.cpp
@@ -538,8 +538,10 @@ void nsBaseWidget::Destroy() {
   // disconnect from the parent
   if (mParent) {
     mParent->RemoveFromChildList(this);
+    mParent = nullptr;
   }
-  mParent = nullptr;
+  // disconnect from the children
+  RemoveAllChildren();
 }
 
 nsIWidget* nsIWidget::GetTopLevelWidget() {
diff --git a/widget/uikit/nsWindow.mm b/widget/uikit/nsWindow.mm
index 9e0055c893e..a62653dc165 100644
--- a/widget/uikit/nsWindow.mm
+++ b/widget/uikit/nsWindow.mm
@@ -772,6 +772,8 @@ void nsWindow::Destroy() {
 
   [mNativeView widgetDestroyed];
 
+  nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
+
   nsBaseWidget::Destroy();
 
   // ReportDestroyEvent();
Loading diff…