High CVSS 6.5 firefox UAF 🔧 Commit mapped

Overview

High
Severity
6.5
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionMemory safety bugs present in Firefox 137 and Thunderbird 137. 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 ClassUAF
Tracker1924108
Fix commit4aa23c960850 (firefox) +6/-1
CWECWE-119 (Buffer bounds error)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:N
CISA KEVNot listed
CreditedRandell Jesup, Jens Stutte, and the Mozilla Fuzzing Team
Disclosed2025-04-29

Background

GTK/GObject reference counting
GTK widgets are GObjects owned by reference count. gtk_container_remove drops the container’s reference on a child; if that was the last reference, the widget is destroyed/freed immediately. g_object_ref/g_object_unref take and release an explicit reference to keep an object alive across such operations (the GObject analogue of a Gecko RefPtr).
`gtk_drag_set_icon_widget`
Installs a widget as the drag icon; it must be given a live widget.

Root Cause Analysis

In nsDragSession::SetDragIcon, when the drag popup widget already had a parent, the code called gtk_container_remove(parent, gtkWidget) to detach it and then used gtkWidget again — OpenDragPopup() and gtk_drag_set_icon_widget(aContext, gtkWidget, ...). The container held a reference to gtkWidget, and gtk_container_remove releases that reference. If the container’s reference was the last one, gtkWidget was destroyed and freed by the remove call, and the subsequent gtk_drag_set_icon_widget operated on freed GObject memory — a use-after-free of the drag-icon widget. The patch brackets the detach-and-reuse sequence with an explicit reference: g_object_ref(gtkWidget) before the gtk_widget_get_parent/gtk_container_remove block and g_object_unref(gtkWidget) after gtk_drag_set_icon_widget, guaranteeing the widget stays alive for the whole sequence.

The companion change in nsWindow::GtkWidgetDestroyHandler addresses the destroy-ordering invariant that the UAF violated. Previously the handler hard-asserted mIsDestroyed (crashing a release build — “Releasing live widget!”) if the GTK widget was destroyed while the nsWindow was still live. The patch replaces the fatal assert with recovery: if !mIsDestroyed, it NS_WARNINGs and calls Destroy() to bring the nsWindow into a consistent destroyed state rather than crashing or leaving a live nsWindow wrapping a dead widget. Together these show a lifetime bug where a GTK widget could be freed out from under Gecko’s nsWindow/drag code. The advisory bundles this under the generic “memory safety bugs … evidence of memory corruption” wording (CWE-119); the diff substantiates specifically a GObject reference-counting use-after-free, and the escalation to arbitrary code execution is the advisory’s presumption, not demonstrated by the diff (inferred).

Key insight
gtk_container_remove can drop the last reference to the drag-icon widget, freeing it, yet the code kept using that widget afterward; the fix holds an explicit g_object_ref across the detach-and-reuse so the widget cannot be freed mid-sequence.

Attack Path

  1. Attacker Serves a page that initiates and manipulates a drag-and-drop operation (drag images / data transfer) on Linux/GTK Firefox. (web-reachable, inferred — D&D is script-and-content driven)
  2. State setup Arranges for the drag popup widget to already have a GTK parent while a drag is (re)started, e.g. by aborting/restarting a D&D operation so the widget is still attached when SetDragIcon runs.
  3. Free SetDragIcon calls gtk_container_remove(parent, gtkWidget), which releases the container’s reference; being the last reference, gtkWidget is destroyed and freed.
  4. Use-after-free gtk_drag_set_icon_widget(aContext, gtkWidget, ...) (and OpenDragPopup) dereference the freed widget.
  5. Primitive Use-after-free of a GObject in the browser/content widget code; with heap grooming this is the memory-corruption primitive the advisory presumes could be driven toward code execution (escalation inferred).

Impact Assessment

Use-after-free in the GTK widget/drag-and-drop code of the (Linux) parent/widget layer, reachable through content-driven drag operations with no special privilege. The demonstrated primitive is a UAF of a GTK widget GObject; the advisory presumes exploitability for arbitrary code execution “with enough effort,” which the diff does not prove (inferred escalation). The second hunk also converts a release-assert crash on a live-widget destroy into graceful teardown, reducing crash-based DoS from the same ordering problem. Preconditions require the specific drag state where the icon widget still has a parent.

Changed Functions

FunctionChangeNotes
if
widget/gtk/nsWindow.cpp
modified

Files Changed

  • widget/gtk/nsDragService.cpp
  • widget/gtk/nsWindow.cpp

Audit Directions

  • `gtk_container_remove` reuse patterns
    Audit all GTK widget code that calls gtk_container_remove (or otherwise drops a container’s reference) and then continues to use the removed widget without holding its own g_object_ref.
  • GObject lifetime vs. `nsWindow`
    Review other spots where a GTK widget can be destroyed independently of its owning nsWindow; the destroy-handler change hints these ordering mismatches exist elsewhere.
  • Drag-and-drop state machine
    Examine abort/restart paths of nsDragService/nsDragSession for other reuse-after-detach of popup or icon widgets.
diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp
index f95a9754c33..d0ded4d7349 100644
--- a/widget/gtk/nsDragService.cpp
+++ b/widget/gtk/nsDragService.cpp
@@ -2462,12 +2462,14 @@ void nsDragSession::SetDragIcon(GdkDragContext* aContext) {
           // When mDragPopup has a parent it's already attached to D&D context.
           // That may happens when D&D operation is aborted but not finished
           // on Gtk side yet so let's remove it now.
+          g_object_ref(gtkWidget);
           if (GtkWidget* parent = gtk_widget_get_parent(gtkWidget)) {
             gtk_container_remove(GTK_CONTAINER(parent), gtkWidget);
           }
           LOGDRAGSERVICE("  set drag popup [%p]", widget.get());
           OpenDragPopup();
           gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+          g_object_unref(gtkWidget);
           return;
         } else {
           LOGDRAGSERVICE("  NS_NATIVE_SHELLWIDGET is missing!");
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
index d5248f105fe..27c81407827 100644
--- a/widget/gtk/nsWindow.cpp
+++ b/widget/gtk/nsWindow.cpp
@@ -8390,7 +8390,10 @@ static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
 }
 
 void nsWindow::GtkWidgetDestroyHandler(GtkWidget* aWidget) {
-  MOZ_RELEASE_ASSERT(mIsDestroyed, "Releasing live widget!");
+  if (!mIsDestroyed) {
+    NS_WARNING("GtkWidgetDestroyHandler called for live nsWindow!");
+    Destroy();
+  }
   if (aWidget == mShell) {
     mShell = nullptr;
     return;
Loading diff…