Closed Bug 2027992 Opened 7 months ago Closed 7 months ago

Type confusion in [@ js::ProxyGetProperty] due to GVN eliding MGuardIsProxy across JSObject::swap

Categories

(Core :: JavaScript Engine: JIT, defect)

defect

Tracking

()

RESOLVED DUPLICATE of bug 2027984

People

(Reporter: bugmon, Unassigned)

Details

(5 keywords)

Attachments

(2 files)

Bug Title: Type confusion in [@ js::ProxyGetProperty] due to GVN eliding MGuardIsProxy across JSObject::swap

Severity: sec-high

Root Cause: Ten MIR instructions had their alias_set changed from AliasSet::Load(AliasSet::ObjectFields) to AliasSet::None() in MIROps.yaml (GuardIsProxy, GuardIsNotDOMProxy, GuardIsNotProxy, GuardIsNativeObject, GuardHasProxyHandler, LoadScriptedProxyHandler, IsConstructor) and MIR.h (MHasClass, MGuardToClass, MIsCallable). With AliasSet::None, these instructions never have a dependency() set, causing GVN to treat congruent instances as mergeable even when separated by effectful calls that can mutate object class.

Exploit Chain:

  1. Create a FakeDOMObject and transplant it to another compartment, making it a CCW (proxy)
  2. Warm up f(o) which does two o.prop accesses separated by a call to cb(). The GenericProxy IC emits MGuardIsProxy → MGuardIsNotDOMProxy → MProxyGet for each access
  3. During Ion compilation, GVN sees two congruent MGuardIsProxy instructions with nullptr dependency → merges them, eliminating the second guard chain. The second MProxyGet now uses the first guard's result
  4. On the trigger run, cb() calls tp.transplant(globalThis) which swaps the object back to a native FakeDOMObject via JSObject::swap. This does NOT invalidate Ion code because the object has no ObjectFuse
  5. The second MProxyGet executes ProxyGetProperty on a NativeObject. It reads offset 16 (NativeObject::elements_) as if it were ProxyObject::handler, obtaining a pointer to emptyElementsHeader
  6. handler->hasSecurityPolicy() reads 9 bytes past emptyElementsHeader → ASAN global-buffer-overflow

Memory Layout Confusion:

  • ProxyObject offset 16 = const BaseProxyHandler* handler
  • NativeObject offset 16 = js::HeapSlot* elements_

The with ({}) in cb() prevents Ion inlining, ensuring cb() compiles to a simple MCall in f's block so GVN can merge the guards across it.

Fix: Restore alias_set: custom with AliasSet::Load(AliasSet::ObjectFields) for all ten modified MIR instructions so that AliasAnalysis correctly tracks dependencies on effectful operations that can change object class.

Flags: --fuzzing-safe --fast-warmup --no-threads

Attached file poc.js —
Attached file crash_stack.txt —
Group: core-security → javascript-core-security
Status: UNCONFIRMED → RESOLVED
Closed: 7 months ago
Duplicate of bug: 2027984
Resolution: --- → DUPLICATE

No valid actions for resolution (DUPLICATE).
Removing bugmon keyword as no further action possible. Please review the bug and re-add the keyword for further analysis.

Keywords: bugmon
Group: javascript-core-security
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: