Medium firefox Memory Corruption 🔧 Commit mapped

Overview

Medium
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impactmoderate
DescriptionMemory safety bugs present in Firefox ESR 140.11, Thunderbird ESR 140.11, Firefox 151 and Thunderbird 151. 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
Tracker2011842
Fix commit9c267d0a950b (firefox) +82/-2
CISA KEVNot listed
CreditedChristian Holler, Jens Stutte, Nika Layzell, Randell Jesup, Tom Schuster and the Mozilla Fuzzing Team
Disclosed2026-06-16

Background

`WidgetEvent`
Gecko’s base representation of an input/UI event, tagged by an mClass (EventClassID) and an mMessage (EventMessage) that together identify its concrete subclass.
`EventClassID`
A uint8_t enum naming which WidgetEvent subclass an event object actually is (e.g. eKeyboardEventClass, eMouseEventClass).
`EventMessage`
An enum naming the specific event action (e.g. eKeyDown, eMouseMove) that must be consistent with the object’s EventClassID.
`ParamTraits<mozilla::WidgetEvent>`
The IPC serialization trait in nsGUIEventIPC.h that reads a WidgetEvent sent from another (potentially compromised) process.

Root Cause Analysis

When a WidgetEvent was deserialized from another process, ParamTraits<mozilla::WidgetEvent>::Read validated only that the incoming aResult->mClass matched the aExpectedEventClassID for the deserialization site, but never checked that aResult->mMessage was a legal value for that class. The invariant that a WidgetEvent’s mMessage must correspond to its mClass (so that downstream code can safely reinterpret the object as the subclass implied by its message) was therefore not enforced on the trust boundary. A compromised content process could send an event carrying a valid mClass but an mMessage belonging to a different class, and code later dispatching on mMessage would treat the object as the wrong subclass, producing type confusion and memory corruption.

The fix adds IsValidMessageForIPC(aMessage, aClassID), a table mapping each EventMessage to its required EventClassID, and requires it to pass in addition to the class check; any mismatched or unrecognized mMessage (the default: return false; case) is now rejected. Rejected events have their mClass reset to eEventClassUninitialized and cause Read to fail, so the tampered event is never dispatched.

Key insight
The single mistake was validating only the event’s class tag while trusting its message field across the IPC boundary, leaving mMessage/mClass free to be inconsistent; the fix closes this by rejecting any event whose mMessage is not a valid message for its declared EventClassID.

Attack Path

  1. Compromise a content process The attacker gains code execution in a sandboxed content process, giving them control over the bytes serialized into IPC messages.
  2. Forge a mismatched event They craft a WidgetEvent whose mClass equals the class expected at a deserialization site but whose mMessage belongs to a different event class.
  3. Pass the weak class check In ParamTraits<mozilla::WidgetEvent>::Read, the old aResult->mClass == aExpectedEventClassID test succeeds because only the class was validated.
  4. Trigger type confusion Parent-process code later branches on mMessage, reinterpreting the object as the subclass implied by the message and accessing fields that do not match the real object layout.
  5. Corrupt memory The mismatched interpretation reads or writes memory out of bounds or through incorrect pointers, yielding memory corruption potentially leading to code execution.

Impact Assessment

An attacker who already controls a content process gains type confusion and memory corruption in the receiving (parent) process by sending a WidgetEvent with an inconsistent mMessage/mClass pair over IPC. This is a sandbox-escape-class primitive: the precondition is a compromised content process, and the metadata rates it moderate memory-safety severity with presumed potential for arbitrary code execution.

Changed Functions

FunctionChangeNotes
switch
widget/WidgetEventImpl.cpp
modified

Files Changed

  • widget/EventForwards.h
  • widget/WidgetEventImpl.cpp
  • widget/nsGUIEventIPC.h

Audit Directions

  • Multi-field tag validation
    When deserializing tagged unions or polymorphic objects from IPC, verify that every discriminant field (both class and message here) is mutually consistent, not just one of them.
  • Cross-process type confusion
    Audit sites that downcast or branch on a message/kind field of an IPC-received object for whether that field was validated against the object’s actual type on the receiving side.
  • Default-deny enums
    Ensure validators over enum values use a default: return false; reject stance so newly added or out-of-range values cannot silently pass the trust boundary.
diff --git a/widget/EventForwards.h b/widget/EventForwards.h
index 82cba445004..907fccbf945 100644
--- a/widget/EventForwards.h
+++ b/widget/EventForwards.h
@@ -117,6 +117,15 @@ enum EventClassID : uint8_t {
 
 const char* ToChar(EventClassID aEventClassID);
 
+/**
+ * Return true if aMessage is a valid EventMessage value for aClassID when an
+ * event is read from another process.  This is used to reject events whose
+ * mMessage/mClass combination is inconsistent and therefore likely tampered
+ * with by a compromised content process.
+ */
+[[nodiscard]] bool IsValidMessageForIPC(EventMessage aMessage,
+                                        EventClassID aClassID);
+
 typedef uint16_t Modifiers;
 
 #define NS_DEFINE_KEYNAME(aCPPName, aDOMKeyName) KEY_NAME_INDEX_##aCPPName,
diff --git a/widget/WidgetEventImpl.cpp b/widget/WidgetEventImpl.cpp
index e5ee9b94d70..c4d3b451dd7 100644
--- a/widget/WidgetEventImpl.cpp
+++ b/widget/WidgetEventImpl.cpp
@@ -185,6 +185,75 @@ bool IsForbiddenDispatchingToNonElementContent(EventMessage aMessage) {
   }
 }
 
+bool IsValidMessageForIPC(EventMessage aMessage, EventClassID aClassID) {
+  switch (aMessage) {
+    case eKeyDown:
+    case eKeyUp:
+    case eKeyPress:
+      return aClassID == eKeyboardEventClass;
+    case eMouseMove:
+    case eMouseUp:
+    case eMouseDown:
+    case eMouseEnterIntoWidget:
+    case eMouseExitFromWidget:
+    case eMouseDoubleClick:
+    case eMouseActivate:
+    case eMouseOver:
+    case eMouseOut:
+    case eMouseHitTest:
+    case eMouseEnter:
+    case eMouseLeave:
+    case eMouseTouchDrag:
+    case eMouseLongTap:
+    case eMouseExploreByTouch:
+      return aClassID == eMouseEventClass;
+    case eWheel:
+    case eWheelOperationStart:
+    case eWheelOperationEnd:
+      return aClassID == eWheelEventClass;
+    case eDragEnter:
+    case eDragOver:
+    case eDragExit:
+    case eDrag:
+    case eDragEnd:
+    case eDragStart:
+    case eDrop:
+    case eDragLeave:
+      return aClassID == eDragEventClass;
+    case ePointerMove:
+    case ePointerUp:
+    case ePointerDown:
+    case ePointerOver:
+    case ePointerOut:
+    case ePointerEnter:
+    case ePointerLeave:
+    case ePointerCancel:
+    case ePointerRawUpdate:
+    case ePointerGotCapture:
+    case ePointerLostCapture:
+    case ePointerClick:
+    case ePointerAuxClick:
+    case eContextMenu:
+      return aClassID == ePointerEventClass;
+    case eTouchStart:
+    case eTouchMove:
+    case eTouchEnd:
+    case eTouchCancel:
+    case eTouchPointerCancel:
+      return aClassID == eTouchEventClass;
+    case eCompositionStart:
+    case eCompositionEnd:
+    case eCompositionChange:
+    case eCompositionCommitAsIs:
+    case eCompositionCommit:
+      return aClassID == eCompositionEventClass;
+    case eSetSelection:
+      return aClassID == eSelectionEventClass;
+    default:
+      return false;
+  }
+}
+
 const char* ToChar(EventClassID aEventClassID) {
   switch (aEventClassID) {
 #define NS_ROOT_EVENT_CLASS(aPrefix, aName) \
diff --git a/widget/nsGUIEventIPC.h b/widget/nsGUIEventIPC.h
index f9f005abac0..baf960fdb59 100644
--- a/widget/nsGUIEventIPC.h
+++ b/widget/nsGUIEventIPC.h
@@ -83,13 +83,15 @@ struct ParamTraits<mozilla::WidgetEvent> {
             ToChar(aExpectedEventClassID), ToChar(aResult->mClass),
             ToChar(aResult->mMessage))
             .c_str());
-    if (aResult->mClass == aExpectedEventClassID) [[likely]] {
+    if (aResult->mClass == aExpectedEventClassID &&
+        mozilla::IsValidMessageForIPC(aResult->mMessage, aExpectedEventClassID))
+        [[likely]] {
       return true;
     }
     // Clear mClass value to avoid the assertion failure in the destructor in
     // the debug build because it's not a fault in this process.
     aResult->mClass = mozilla::eEventClassUninitialized;
-    // Don't allow illegal mClass value.
+    // Don't allow illegal mClass/mMessage combination.
     return false;
   }
 
Loading diff…