CVE-2024-8381
Overview
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
withtarget and participates in the lexical environment chain. - `FetchName`
- the inline interpreter helper that reads a resolved binding’s value out of the
holderobject into the result valuevpafter 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.
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
- Establish a `with` scope
Attacker script enters a
with (obj)block so that name lookups resolve through aWithEnvironmentObjectwrapping a chosen target object. - Trigger a name fetch
Reference an unqualified identifier that resolves to a property found on the
withenvironment, driving the interpreter intoFetchNamewith aWithEnvironmentObjectreceiver. - Reach the mismatched fast path
The property is found as a native slot, so
FetchNamecomputes apropInfoagainst the wrapped receiver but then unwraps it viaMaybeUnwrapWithEnvironment. - Confuse the types
NativeGetExistingPropertyreads thepropInfoslot out of the unwrapped object whose layout differs, so the returned value is reinterpreted as the wrong type, yielding a controlled type-confused reference. - 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
with environment and the corresponding name lookup.Files Changed
js/src/vm/Interpreter-inl.h
Audit Directions
- Shape/slot reuse across unwrapLook 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 pathsAudit environment-chain and scope-resolution helpers where
WithEnvironmentObjector other proxy-like environments can silently enter a native-object fast path intended only for ordinary objects. - Receiver vs. holder divergenceReview property-access routines that accept separate
receiverandholderobjects and ensure fast-path metadata is never keyed to the receiver’s original type while operating on a substituted object.
Patch
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;
}