High firefox Logic Error 🔧 Commit mapped

Overview

High
Severity
—
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionInvalid pointer in the DOM: Bindings (WebIDL) component
ComponentDOM
Bug ClassLogic Error
Tracker2049523
Fix commit88d871e6ff23 (firefox) +2/-2
CISA KEVNot listed
Creditedfedek
Disclosed2026-07-21

Background

`ToJSValue`
a family of overloaded WebIDL binding helpers in mozilla::dom that convert native C++ values into a JavaScript JS::Value for the JSContext.
`JS::Value`
SpiderMonkey’s NaN-boxed tagged union where doubles, int32, pointers, and other types share 64 bits, so non-double payloads are encoded inside otherwise-unused NaN bit patterns.
Canonical NaN
the single blessed double NaN bit pattern that JS::Value requires for stored doubles, reserving all other NaN encodings for boxed non-double tags such as GC pointers.
`JS_NumberValue`
a SpiderMonkey API that builds a numeric JS::Value from a double while canonicalizing any NaN input before storing it.

Root Cause Analysis

The vulnerable path was the float overload of ToJSValue in dom/bindings/ToJSValue.h, which stored the incoming float directly with aValue.setNumber(aArgument). setNumber writes the widened double into the JS::Value without canonicalizing NaN, violating the core NaN-boxing invariant that every stored double must use the canonical NaN bit pattern. A float NaN (or a non-canonical NaN produced by widening) therefore landed in a JS::Value whose bit pattern overlaps the encoding space reserved for boxed pointers and other tagged types, so subsequent consumers could misinterpret the value as an invalid pointer rather than a number.

The fix replaces the call with aValue.set(JS_NumberValue(double(aArgument))), which explicitly canonicalizes any NaN before the value is stored, restoring the invariant. Because canonicalization now happens for every float conversion, the resulting JS::Value can never carry a rogue NaN payload that aliases a pointer tag.

Key insight
The single mistake was writing a WebIDL float result into a NaN-boxed JS::Value via setNumber without NaN canonicalization; the fix routes the value through JS_NumberValue, which canonicalizes the NaN so the double can never be confused with a boxed pointer or other tagged type.

Attack Path

  1. Reach the float binding Script invokes a WebIDL API whose return value or output is a native float, driving the float overload of ToJSValue.
  2. Deliver a non-canonical NaN The attacker arranges the float to be (or become) a NaN whose widened double bit pattern is not the canonical NaN.
  3. Store the poisoned value setNumber writes the non-canonical NaN into the JS::Value without canonicalization, leaving a bit pattern that overlaps the boxed-pointer encoding space.
  4. Trigger misinterpretation Downstream engine code inspects the JS::Value tag and treats the NaN payload as an invalid pointer instead of a double.
  5. Exploit the invalid pointer The mis-tagged value leads to dereference of an attacker-influenced pointer, the memory-safety fault described in the advisory.

Impact Assessment

An attacker gains an invalid-pointer / type-confusion condition inside the content process, where WebIDL bindings and SpiderMonkey run, potentially leading to memory corruption; the advisory rates the impact high. The precondition is script access to a WebIDL API that funnels a native float through ToJSValue and the ability to make that float a non-canonical NaN. No special privileges beyond running JavaScript in a page are implied by the diff.

Files Changed

  • dom/bindings/ToJSValue.h

Audit Directions

  • Uncanonicalized numeric stores
    Audit other ToJSValue overloads and binding code for setNumber/setDouble calls on floating-point inputs that bypass JS_NumberValue NaN canonicalization.
  • Float-to-double widening into `JS::Value`
    Flag any path that widens a float to a double and writes it into a NaN-boxed JS::Value without canonicalizing, since widening can yield non-canonical NaNs.
  • NaN-boxing invariant sites
    Review consumers that branch on a JS::Value tag to ensure a rogue NaN payload cannot be dispatched as a pointer or other non-double type.
diff --git a/dom/bindings/ToJSValue.h b/dom/bindings/ToJSValue.h
index 6bf17bb77e4..2444cf11018 100644
--- a/dom/bindings/ToJSValue.h
+++ b/dom/bindings/ToJSValue.h
@@ -99,13 +99,13 @@ inline bool ToJSValue(JSContext* aCx, uint64_t aArgument,
   return true;
 }
 
-// accept floating point types
+// Accept floating point types.
 inline bool ToJSValue(JSContext* aCx, float aArgument,
                       JS::MutableHandle<JS::Value> aValue) {
   // Make sure we're called in a compartment
   MOZ_ASSERT(JS::CurrentGlobalOrNull(aCx));
 
-  aValue.setNumber(aArgument);
+  aValue.set(JS_NumberValue(double(aArgument)));
   return true;
 }
 
Loading diff…