[8] FTL ValueRepReduction type confusion in MultiGetByOffset constant cases
Severity: High | Component: JSC FTL JIT | f2c5bf8
Rated High because the observable effect is a type confusion where non-Number JSValue bit patterns are reinterpreted as IEEE 754 doubles in FTL-compiled code reachable from web content, and the confused value is deterministic (controlled by which constant appears in the MultiGetByOffset case) — exploitation via downstream array-index or bounds-check confusion is projected with confidence 0.92, with unverified claims about the specific numeric value produced and whether Load-kind cases handle conversion at emission time.
When ValueRepReductionPhase converts a MultiGetByOffset node to NodeResultDouble, Constant-kind cases must have their embedded FrozenValues converted to proper double numbers. Before the fix, the raw JSValue bit pattern of non-Number constants (e.g., undefined) was emitted as the double result instead of being converted via toNumberFromPrimitive().
Source/JavaScriptCore/dfg/DFGValueRepReductionPhase.cpp
- case MultiGetByOffset:
case GetByOffset: {
candidate->setResult(NodeResultDouble);
candidate->mergeFlags(NodeMustGenerate);
@@ ...
+ case MultiGetByOffset: {
+ MultiGetByOffsetData& data = candidate->multiGetByOffsetData();
+ for (unsigned i = 0; i < data.cases.size(); ++i) {
+ GetByOffsetMethod& method = data.cases[i].method();
+ if (method.kind() == GetByOffsetMethod::Constant) {
+ // It's possible there are non-Number constants that still predict NumberUse, e.g. undefined.
+ std::optional<double> doubleConstant = method.constant()->value().toNumberFromPrimitive();
+ method.setConstantValue(m_graph.freeze(jsDoubleNumber(*doubleConstant)));
+ }
+ }
+ candidate->setResult(NodeResultDouble);
+ resultNode = candidate;
+ break;
+ }
JSTests/stress/ftl-valuerepreduction-double-undefined.js
+for (let v0 = 0; v0 < 100; v0++) {
+ const v1 = {a: v0};
+ for (let v2 = 0; v2 < 5; v2++) {
+ v1.length += v0;
+ v0[1] = v2;
+ v2.constructor;
+ }
+ for (let v4 = 0; v4 < 50; v4++) {
+ function f5() { return v1;}
+ for (let v8 = 0; v8 < 100; v8++) {}
+ }
+}
Patch Details
The fix separates MultiGetByOffset from the generic GetByOffset case in the DoubleRepUse conversion. For MultiGetByOffset, it iterates all MultiGetByOffsetData::cases and, for each case whose method is GetByOffsetMethod::Constant, calls toNumberFromPrimitive() on the constant value and replaces it with a frozen jsDoubleNumber. Two new accessors support this mutation: GetByOffsetMethod::setConstantValue() and a non-const MultiGetByOffsetCase::method(). The fix unconditionally dereferences the std::optional<double> returned by toNumberFromPrimitive() via *doubleConstant without a nullopt check — safe if toNumberFromPrimitive() is total over all JS primitives.
Before: After:
MultiGetByOffset (NodeResultJS) MultiGetByOffset (NodeResultDouble)
case A: Load from offset case A: Load from offset (same)
case B: Constant(undefined) case B: Constant(NaN) ← converted
[raw JSValue bits emitted [jsDoubleNumber(NaN)]
as "double"]
Background
MultiGetByOffset is a DFG/FTL IR node representing a polymorphic inline cache for property loads. It contains multiple cases, each matching a set of object structures and specifying a method to retrieve the value — either loading from an offset in the object (Load) or returning a constant (Constant, e.g., undefined for an absent property).
The ValueRepReductionPhase is an FTL optimization that identifies nodes producing boxed JSValues that are only ever consumed as unboxed doubles (via DoubleRep nodes). It converts these nodes to produce NodeResultDouble directly, eliminating the box-then-unbox overhead.
A FrozenValue is a compile-time snapshot of a JSValue that the JIT can embed directly into generated code. When a MultiGetByOffset constant case is emitted, the FTL materializes the frozen value's bits as the node's output. This is why constant cases require explicit conversion — unlike loads, their values are baked into the IR rather than read from memory at runtime.
In JSC's value encoding, non-double values are encoded with tag bits in the upper portion of a 64-bit word. undefined, null, true, false, and object pointers all have distinct bit patterns that do not correspond to meaningful IEEE 754 doubles.
Analysis
Missing value-representation conversion of embedded constants when an IR node's result type is narrowed from JSValue to double in the FTL optimization pipeline.
Before the fix, when ValueRepReductionPhase converted a MultiGetByOffset node to NodeResultDouble, it changed the result representation without converting the constant values embedded in the node's cases. For Constant-kind cases, the FrozenValue is embedded directly into the IR. Without conversion, the raw JSValue bit pattern of the non-Number constant (e.g., undefined) would be emitted as the double result. This means the downstream JIT code interprets the JSValue encoding bits of undefined as a double-precision floating point number, producing an incorrect value rather than NaN as JavaScript semantics require.
For Load-kind cases, the value is read from memory at emission time, so the emitter likely handles the JSValue-to-double conversion during materialization (inferred from the fact that the fix only converts Constant-kind cases). But Constant-kind cases have their values baked into the IR, requiring explicit conversion.
Aaa Aaaa Aaaa Aaa Aaa Aaaaaaaaa Aa a Aaaaaaaaaaaaaaaa Aaaaaa Aaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaaaa Aaaaaaaa Aaaaaa Aaaaaaaa Aaaaaaa a Aaa Aaaaa Aaaa Aa a Aaaaaaaa Aaaaaa Aaa Aaaaa Aa Aaaaaaa Aaaaaa Aaaaaaaaaaaa Aa Aaaaaaaa Aaaaa Aaaaaaaaa Aaaaa Aaaaaaaaaa Aaaaaaaa Aaaaaaa Aa Aaa Aaaaaaaaaaaaaaaaaa Aaaa a Aaaaaaaaaaaa Aaaaaaa Aaaaaaa Aaaaaaa Aaaa Aaaaaaa a Aaaaaaaaaaaaa Aaaaaaaa Aaaaaa Aaaaa Aaaaaaaa Aa Aaaaa Aaaaaaa Aaaaaaa
Aa Aaaaaaaaaa Aaaa Aaaa Aaaa Aaaaaaaa Aaaaaa Aaaaa Aa Aaaaaa Aaaaaaaaaaaa Aa Aa Aa Aaaaa Aaaaaa Aaaa Aaaaa Aaaaa Aa Aaa Aaaaaaaaaa Aaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaa Aa Aaaaaaaaaaa Aa Aaaaaa Aaaaaaaa Aaaaaaaaaaaaa Aa a Aaaaaa Aaaaa Aaaaaaa a Aaaaa Aaaaaaaaaa Aa Aaaaa Aaaaaaaa Aaaaaaa Aaaaaa a Aaaaaa a Aaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaa Aaaaa Aaaaaa Aaaa Aaaaa a Aaaa Aaaaa Aaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaa Aa Aaaaaa Aaaaaaaaaaa Aaaaaaaaaaa Aaaaaaa Aaa Aaaaaaaa Aaaaaaaaaa Aaaaaaaaaa Aaaaaaa Aa Aaa Aaa Aaaaaaaa Aaaaa Aaaaaaaaa Aaaa Aaaaaaaa Aaaaaaaaaaa Aaaaaaa
Aaaa Aaaaaaaaaaaaa Aaaaaaa Aaa Aaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaaa Aaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaa Aaaa Aaaaaa Aaaaaaa Aaaa a Aaaaaaaaaaaaaaaaaa Aaaa Aaaaaaaa a Aaaaa Aaaa Aaa Aaaaaaa Aaaa a Aaaaaaaaaa Aaaaaaaaa Aaa Aaa Aaaaaaa Aa Aaaaaaa Aaaaaaaaaaaaa Aa a Aaaaaaa Aaa Aaaaaaaaaa Aaaaaaaa Aaaa Aaaaaaaa Aa a Aaaaa Aaaaa Aaaaaaa Aaaaaaaaaaaaaa Aa Aaaaaaaaaa Aa Aaaaa Aaaaaaaa Aaaaaaa Aaaaaa Aaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaa
Aaa Aaa Aaaaaa Aaaaaaaa a Aaaaaaaaa Aaaaaaaaaa Aaaaaaaa Aaa Aaaaaaaaaaaaa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aa Aaa Aaaaaaaaaaaaaaa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aaaaa Aa Aaa Aaaaaaaaa Aaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaa a Aaaaa Aaaaaaaaa Aaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaa Aaaa Aaa Aa Aaaaaaaaaaa
🔒Explores how the confused double value interacts with downstream JIT operations and whether it can be escalated beyond incorrect computation
Subscribe to read more
Audit directions
a Aaaaaaaa Aaa Aaaaaaaaaaaa Aaaaaa Aaaa Aaaaaa a Aaaaaa Aaaaaa Aaaaaaaaaaaaaa Aaaaaaaa a Aaaaaaa Aaaaaaa a Aaaaaa Aaaaaaa a Aaaaaa Aaaaaaa Aaaaaaaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaa Aaaaaa Aaaaaaaaaa Aa Aaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaa Aaaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaaaaaaaa Aaaaaaaaa Aa Aaaaaa Aaaa Aaa Aaaaaaaaaaaaa Aaaaa Aaa Aaaaaaaa Aaaaaaaaa Aaaa Aaa Aaaaaa Aaaa Aaaaaaaa Aaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaa Aaaa Aaaa Aaaaaaa Aaaaaaaa Aaaaaaaaaa
a Aaaaaaaa Aaaaaaaaaaaaaa Aaaaaaaa Aaaaaaa a Aaaaaa Aaaaaaaa Aaaaaa Aaaa Aaa Aaa Aaaaaa Aaa Aaaaaaaa Aa Aaa Aaaaaaaa Aaaaaaaaaaaaa Aaaaa Aaaaa Aaaaaaa Aaaaaa Aaaa Aaaaaaaaaa Aaaaaaaaaaa Aaaaaaaaa a Aaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaaaaaaaa a Aaa Aaaaa Aaaaa a Aaaaaaaa Aa Aaaaaaaaaa Aaaaaa a Aaaaaaaaaaaaaa Aaaaaaaa Aaaaaaa Aaaaaaaaaaa Aaaaaa Aaa Aaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaaaaaaaaaaaa Aa Aaaaaaaaaaaaaaaaa Aaaaaaaaaaaa
a Aaaaaaaa Aaaaaaaaaaa Aa Aaaaa Aaaaa Aaaa Aaaa Aaaaaaaa Aaaaaaaaaaaaa Aaaaaa Aaaaa Aaaaa Aa Aaaaaaaa Aa Aaaaaaaaaaaaaaaaaa Aaaaaaaaaaa Aaaa Aaaaaaaaaaaa Aaaaaa Aaaa Aaaaaa Aaaaaaa Aaaaaaaaa Aaaaa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaaaaaaaaaaaaa Aaa Aaaaaaa Aaaaaa Aaaaa Aaaaaaaaaaaaaaaaaaaaaaa Aaaaaaaaaaaaa Aaaaa Aaaa Aaaaaaaa Aaaaa Aaaaaaaa Aa Aaa Aaaaaa Aaaa Aaaaaaa
Aaaaaaaaa Aaaa Aaaaaaaaaaaaaa Aaaaaaa a Aaaaaaaaaaaa Aaaaaaa Aaaaaaaaa Aaaaa Aaaaaa Aaaaaaaaaa Aa Aaaaaaaa Aaaa Aaa Aaaa Aaaaaaaa Aaaaaaa Aaaaa Aaaaa Aaa Aaaaaaaa Aaaaaaaa Aaaa Aaaaaaaaaaaaa Aa Aaaa Aaa a Aaa Aaaaaaaa Aaaa Aaa Aaa Aaaaaaa Aaa Aaaaaaaaaaaaa Aaaaaaaaaa Aaa Aaaa Aaa Aaaaaaaaaaaa Aaaaaaaaa Aa a Aaaaaaaaaaaaaaaaaaaaaaa Aaaaa Aa Aaaaaaaa Aaaaaaaaa Aa Aaa Aaaa Aaa Aaaa Aaaaaaaa
🔒Multiple reusable audit patterns identified across JIT optimization phases, with concrete starting points for variant discovery
Subscribe to read more