CVE-2026-28905
Overview
Background
- OSR exit
- On-stack replacement from optimized JIT code back to a lower tier when a speculation fails; sunk allocations must be materialized at that point.
- PhantomNewArrayWithButterfly
- A DFG representation of an array allocation the JIT elided (‘sank’); its concrete object and butterfly storage are only built if an OSR exit needs it.
- operationPopulateObjectInOSR
- The runtime helper that fills a materialized sunk object’s storage during OSR exit.
- Unwritten slot / array hole
- An element with no corresponding store; it must be initialized to the array hole (empty), not left as an uninitialized JSValue.
Root Cause Analysis
This fixes incorrect handling of unwritten array slots when a sunk allocation is materialized on OSR exit. Per the commit (‘Fix incorrect handling of unwritten slots in operationPopulateObjectInOSR for PhantomNewArrayWithButterfly’), the DFG/FTL can sink an array-with-butterfly allocation (represented as PhantomNewArrayWithButterfly) and only materialize it if an OSR exit requires the concrete object; operationPopulateObjectInOSR is the runtime helper that fills in that object’s storage during exit.
The bug is that slots which were never written by the optimized code (‘unwritten slots’) were handled incorrectly while populating the butterfly, so an element that had no corresponding store was left in an inconsistent/uninitialized state rather than being filled with the array hole. When the materialized array is subsequently read or written (the test does ret[0]=1 after arr.map(f)), that slot holds a garbage value instead of a valid empty/hole JSValue — an uninitialized-value / type-confusion-class condition.
The fix corrects operationPopulateObjectInOSR to initialize unwritten slots properly for PhantomNewArrayWithButterfly so every element of the materialized array is a valid JSValue.
The restored invariant is that OSR-exit materialization of a sunk array leaves no element uninitialized. NOTE: the pinned commit (392f508e) contains only the regression test (JSTests/stress/ftl-osr-exit-phantom-array-unwritten-slot.js); the source change to operationPopulateObjectInOSR is described authoritatively by the commit message but is not shown line-by-line in this commit’s diff, so the exact edited lines are taken from the commit’s own description rather than a shown hunk. The test tiers a function that maps over an array with a callback that deopts, forcing the phantom array to materialize on OSR exit.
Attack Path
- Reach the FTL with a sunk array Run JS (e.g. Array.prototype.map with a callback) hot enough that the JIT sinks an array-with-butterfly allocation as PhantomNewArrayWithButterfly.
- Leave a slot unwritten Arrange the optimized code so some array element has no corresponding store before an OSR exit.
- Force OSR-exit materialization Trigger a deopt (the test uses (0)[0] throwing) so operationPopulateObjectInOSR materializes the phantom array, mishandling the unwritten slot.
- Use the uninitialized slot Read/write that element (ret[0]=1) to operate on a garbage JSValue, an uninitialized-value/type-confusion primitive in WebContent (advisory: crash).
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
operationPopulateObjectInOSR (PhantomNewArrayWithButterfly)Source/JavaScriptCore/dfg/DFGOperations.cpp |
modified | Per the commit message, corrects handling of unwritten slots when materializing a PhantomNewArrayWithButterfly on OSR exit so uninitialized elements are filled with the hole instead of garbage. (Source change described by the commit; the pinned commit carries the regression test.) |
ftl-osr-exit-phantom-array-unwritten-slot.jsJSTests/stress/ftl-osr-exit-phantom-array-unwritten-slot.js |
added | Regression test that deopts while mapping over an array, forcing OSR-exit materialization of a phantom array with an unwritten slot. |
Files Changed
JSTests/stress/ftl-osr-exit-phantom-array-unwritten-slot.js
Audit Directions
- Same helper: other phantom materializationsAudit operationPopulateObjectInOSR and sibling materialization helpers for how unwritten indices/properties are initialized across PhantomNewArray, PhantomNewArrayWithConstantSize, and object variants.
- Object-allocation sinking initGrep FTL object-allocation-sinking / OSR-exit materialization for butterfly/element fill loops that assume every slot was written.
Patch
diff --git a/JSTests/stress/ftl-osr-exit-phantom-array-unwritten-slot.js b/JSTests/stress/ftl-osr-exit-phantom-array-unwritten-slot.js
new file mode 100644
index 000000000000..a285bbf64df8
--- /dev/null
+++ b/JSTests/stress/ftl-osr-exit-phantom-array-unwritten-slot.js
@@ -0,0 +1,13 @@
+function opt() {
+ const arr = [0,0,0,0,0,0,0];
+ function f() {
+ arr[0];
+ (0)[0];
+ return arr;
+ }
+ const ret = arr.map(f)
+ ret[0] = 1
+}
+for (let i = 0; i < 200; i++) {
+ opt()
+}