← All issues

[4] [JSC] Check initial object structure in tryEnsureAbsence in DFG

Severity: High | Component: JSC DFG JIT | 78c04ea

Rated High because the diff removes a soundness violation in DFG speculation: a property-absence assumption could be installed on a receiver that owned the property as its own; the regression test sketches a controlled JSObject/Double type confusion that flows directly into the standard fakeobj/addrof recipe.

Graph::tryEnsureAbsence validated cacheability on the prototype chain but never on the head receiver itself. If the head object owned the property as an OWN property, the DFG would still install an ObjectPropertyConditionSet claiming absence — an invariant that is false from compile time.

Source/JavaScriptCore/dfg/DFGGraph.cpp

+ auto isAbsenceCacheable = [&](Structure* structure) {
+ if (structure->typeInfo().overridesGetOwnPropertySlot())
+ return false;
+ if (!structure->propertyAccessesAreCacheable())
+ return false;
+ if (!structure->propertyAccessesAreCacheableForAbsence())
+ return false;
+ unsigned attributes;
+ if (isValidOffset(structure->getConcurrently(identifier.uid(), attributes)))
+ return false;
+ if (structure->hasPolyProto())
+ return false;
+ return true;
+ };
+
+ if (!isAbsenceCacheable(headStructure))
+ return ObjectPropertyConditionSet::invalid();

The cacheability/absence checks become an isAbsenceCacheable lambda applied to headStructure before generateConditionsForPropertyMissConcurrently runs, and reused for every prototype object. The added comment notes explicitly that generateConditionsForPropertyMissConcurrently only walks the prototype chain — head validation is the caller's responsibility.

Missing self-check in a DFG absence-condition builder that only validated the prototype chain, allowing the JIT to install an absence assumption contradicted by the receiver's own property.

JSC's optimizing tiers avoid runtime property lookups by recording structural invariants — for example, "property P is absent on object O" — into an ObjectPropertyConditionSet that downstream code folds into machine code. tryEnsureAbsence builds the absent variant. generateConditionsForPropertyMissConcurrently walks the prototype chain emitting Miss conditions per prototype; it does NOT examine the head structure. PolyProto indicates per-instance prototype storage, which defeats prototype-chain caching. Re-entrancy points (Promise thenable detection, JSON.stringify toJSON, ToPrimitive) can invoke user code via getters that mutate adjacent typed storage.

The pre-fix code bailed only if headStructure was null; it never consulted overridesGetOwnPropertySlot, propertyAccessesAreCacheable[ForAbsence], or structure->getConcurrently(identifier.uid(), ...) on the head. A { toJSON: 1, a: 1 } head placed on a chain with Object.create would pass — the DFG would then JIT a fast path on the false assumption that tmp.toJSON is missing.

🔒

How a missing head-receiver check in a DFG absence helper undermines JIT speculation soundness, and how the regression test composes it into a realistic type-confusion shape.

Subscribe to read more

🔒

Multiple reusable audit patterns identified — covering chain-walking helpers, asymmetric validation lambdas, and absence-condition fast paths that re-enter JS via callbacks.

Subscribe to read more