CVE-2026-28984
Overview
Background
- FTL OSR exit / object sinking
- The FTL JIT can defer (‘sink’) an object allocation and rematerialize it only if execution bails out (OSR exit) to the baseline.
- PhantomNewArrayWithButterfly
- A sunk array-with-butterfly allocation node the OSR machinery must rebuild with the correct indexing type and butterfly.
- Having a bad time
- A VM state entered when indexed accessors are defined on Array/Object prototypes, after which arrays are forced to SlowPutArrayStorage.
- Indexing type / butterfly / ArrayStorage
- An array’s storage shape (Int32/Double/Contiguous/ArrayStorage) and the backing butterfly memory; SlowPutArrayStorage handles prototype indexed setters.
Root Cause Analysis
This is an FTL OSR-exit object-materialization bug in JavaScriptCore’s handling of a PhantomNewArrayWithButterfly sink. When the FTL sinks an array allocation and later has to rematerialize it during an OSR exit, operationMaterializeObjectInOSR chose the array Structure via globalObject->arrayStructureForIndexingTypeDuringAllocation(indexingType). The invariant it relied on is that the indexing type observed at FTL compile time still describes the layout to build at exit time. But the global object can enter ‘having a bad time’ (isHavingABadTime — triggered when an indexed property is defined on Array.prototype/Object.prototype) between FTL compilation and the rematerialization; once that happens, arrayStructureForIndexingTypeDuringAllocation returns a SlowPutArrayStorage structure for every indexing type. The code then rematerialized the butterfly assuming a contiguous/non-ArrayStorage layout while the Structure said SlowPutArrayStorage — a layout mismatch. Writing rematerialized elements (and, in operationPopulateObjectInOSR, clearing ‘hole’ empty JSValues) into the wrong layout corrupts the object/butterfly.
The fix rematerializes with originalArrayStructureForIndexingType (the non-bad-time structure matching how the butterfly is built), and then, if isHavingABadTime(), explicitly calls result->switchToSlowPutArrayStorage(vm) so the final object is converted consistently; correspondingly, operationPopulateObjectInOSR adds a branch to clear a hole in arrayStorage()->m_vector[index] when the indexing type is any ArrayStorage.
The restored invariant is that materialization builds the butterfly and the Structure with a single consistent layout even if a bad-time transition raced the exit.
Attack Path
- Warm up FTL Repeatedly run a function that allocates and fills a typed (e.g. double) array so the FTL compiles it and sinks the allocation as PhantomNewArrayWithButterfly.
- Arm a bad-time trigger Prepare code (an Object.defineProperty on Array.prototype[0]) that will put the VM into ‘having a bad time’ during the optimized run.
- Force an OSR exit after bad time Trigger the prototype getter (isHavingABadTime becomes true) and cause an OSR exit that rematerializes the sunk array.
- Layout mismatch Pre-patch, the array is built with a bad-time SlowPutArrayStorage structure but populated as contiguous, so element writes/hole clears hit the wrong layout — memory corruption or a crash in WebContent.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
operationMaterializeObjectInOSR (PhantomNewArrayWithButterfly case)Source/JavaScriptCore/ftl/FTLOperations.cpp |
modified | Uses originalArrayStructureForIndexingType to build the array, then switchToSlowPutArrayStorage(vm) when isHavingABadTime(), avoiding a butterfly/Structure layout mismatch. |
operationPopulateObjectInOSRSource/JavaScriptCore/ftl/FTLOperations.cpp |
modified | Adds a branch to clear a hole via arrayStorage()->m_vector[index].clear() when the array has any ArrayStorage indexing type, matching the rematerialized SlowPutArrayStorage layout. |
Files Changed
JSTests/stress/ftl-osr-exit-phantom-new-array-with-butterfly-having-a-bad-time.jsSource/JavaScriptCore/ftl/FTLOperations.cpp
Audit Directions
- Other materialization casesReview the other Phantom* cases in operationMaterializeObjectInOSR (PhantomNewArrayBuffer, PhantomNewObject, spreads) for the same arrayStructureForIndexingTypeDuringAllocation-vs-original assumption across a possible bad-time transition.
- isHavingABadTime racesgrep JSC for arrayStructureForIndexingTypeDuringAllocation and haveABadTime()/isHavingABadTime() to find code caching an indexing-type-derived structure across points where script can run.
- Hole-clearing by layoutAudit places that write empty JSValue holes into a butterfly to ensure they branch on the actual indexing type (contiguous vs ArrayStorage) as populateObjectInOSR now does.