High CVSS 9.8 firefox Type Confusion 🔧 Commit mapped

Overview

High
Severity
9.8
CVSS
No
Exploited ITW
Fixed
Fix Status
Impacthigh
DescriptionA potentially exploitable type confusion could be triggered when looking up a property name on an object being used as the <code>with</code> environment.
ComponentSpiderMonkey
Bug ClassType Confusion
Tracker1912715
Fix commita2fe9a202377 (firefox) +3/-5
CWECWE-843 (Type confusion)
CVSS vectorCVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CISA KEVNot listed
CreditedNils Bars
Disclosed2024-09-03

Background

`with` statement
a JavaScript construct that pushes an arbitrary object onto the scope chain so that unqualified name lookups resolve against that object’s properties.
`WithEnvironmentObject`
the internal SpiderMonkey environment object that wraps the user-supplied with target and participates in the lexical environment chain.
`FetchName`
the inline interpreter helper that reads a resolved binding’s value out of the holder object into the result value vp after a name has been located on the environment chain.
`propInfo` / `Shape`
cached property metadata (such as a slot index) describing where a property lives in a specific object’s layout, valid only for the exact object whose shape produced it.

Root Cause Analysis

When the interpreter resolved a name that lived on a with environment, FetchName located the property on the holder and obtained a propInfo describing that property’s slot. On the fast path it then called MaybeUnwrapWithEnvironment(receiver) to strip the WithEnvironmentObject down to the underlying target object normalized, and passed normalized into NativeGetExistingProperty together with the propInfo that had been computed against the original (wrapped) receiver. Because the unwrapped object has a completely different shape and slot layout than the WithEnvironmentObject, applying that propInfo/slot to normalized reads memory under the wrong type interpretation, a classic type confusion.

The fix forces FetchName to take the slow, shape-agnostic GetProperty path whenever receiver->is<WithEnvironmentObject>(), and removes the unwrapping so that any remaining native fast path uses the receiver whose shape actually produced the propInfo, restoring the invariant that a propInfo is only ever applied to the object it describes.

Key insight
The single mistake was applying property metadata (propInfo/slot) derived from the WithEnvironmentObject to a different object returned by MaybeUnwrapWithEnvironment, and the fix eliminates that mismatch by routing with-environment receivers through the generic GetProperty slow path instead of the shape-dependent fast path.

Attack Path

  1. Establish a `with` scope Attacker script enters a with (obj) block so that name lookups resolve through a WithEnvironmentObject wrapping a chosen target object.
  2. Trigger a name fetch Reference an unqualified identifier that resolves to a property found on the with environment, driving the interpreter into FetchName with a WithEnvironmentObject receiver.
  3. Reach the mismatched fast path The property is found as a native slot, so FetchName computes a propInfo against the wrapped receiver but then unwraps it via MaybeUnwrapWithEnvironment.
  4. Confuse the types NativeGetExistingProperty reads the propInfo slot out of the unwrapped object whose layout differs, so the returned value is reinterpreted as the wrong type, yielding a controlled type-confused reference.
  5. Escalate The attacker leverages the type-confused value to read or write memory out of bounds, building toward arbitrary code execution in the content process.

Impact Assessment

An attacker who can run scripted JavaScript in a page gains a type confusion that surfaces attacker-influenced memory as the wrong type, a primitive that is typically escalated to out-of-bounds read/write and potentially arbitrary code execution inside the content process. Exploitation runs in the renderer/content sandbox and requires only that the victim execute attacker-controlled script that arranges the with environment and the corresponding name lookup.

Files Changed

  • js/src/vm/Interpreter-inl.h

Audit Directions

  • Shape/slot reuse across unwrap
    Look for any call site that computes a Shape, propInfo, or slot index against one object and then applies it after an unwrap/normalize step (e.g. MaybeUnwrapWithEnvironment, proxy or wrapper unwrapping) to a different object.
  • `with`/environment fast paths
    Audit environment-chain and scope-resolution helpers where WithEnvironmentObject or other proxy-like environments can silently enter a native-object fast path intended only for ordinary objects.
  • Receiver vs. holder divergence
    Review property-access routines that accept separate receiver and holder objects and ensure fast-path metadata is never keyed to the receiver’s original type while operating on a substituted object.
diff --git a/js/src/vm/Interpreter-inl.h b/js/src/vm/Interpreter-inl.h
index b70fced9d14..f77d9118477 100644
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -128,7 +128,8 @@ inline bool FetchName(JSContext* cx, HandleObject receiver, HandleObject holder,
   }
 
   /* Take the slow path if shape was not found in a native object. */
-  if (!receiver->is<NativeObject>() || !holder->is<NativeObject>()) {
+  if (!receiver->is<NativeObject>() || !holder->is<NativeObject>() ||
+      receiver->is<WithEnvironmentObject>()) {
     Rooted<jsid> id(cx, NameToId(name));
     if (!GetProperty(cx, receiver, receiver, id, vp)) {
       return false;
@@ -139,11 +140,8 @@ inline bool FetchName(JSContext* cx, HandleObject receiver, HandleObject holder,
       /* Fast path for Object instance properties. */
       vp.set(holder->as<NativeObject>().getSlot(propInfo.slot()));
     } else {
-      // Unwrap 'with' environments for reasons given in
-      // GetNameBoundInEnvironment.
-      RootedObject normalized(cx, MaybeUnwrapWithEnvironment(receiver));
       RootedId id(cx, NameToId(name));
-      if (!NativeGetExistingProperty(cx, normalized, holder.as<NativeObject>(),
+      if (!NativeGetExistingProperty(cx, receiver, holder.as<NativeObject>(),
                                      id, propInfo, vp)) {
         return false;
       }
Loading diff…