← All issues

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

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

DFG speculation의 soundness 위반을 제거하는 변경이므로 High로 평가되었습니다. property-absence assumption이 해당 property를 own property로 소유하는 receiver에도 설치될 수 있었으며, regression test는 표준 fakeobj/addrof 조합으로 직결되는 controlled JSObject/Double type confusion을 제시합니다.

Graph::tryEnsureAbsence는 prototype chain에 대한 cacheability 검증은 수행하지만, head receiver 자체에 대해서는 전혀 검증하지 않았습니다. head object가 해당 property를 own property로 소유하는 경우에도, DFG는 absence를 주장하는 ObjectPropertyConditionSet을 그대로 설치했습니다. 이 invariant는 컴파일 시점부터 거짓인 상태였습니다.

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();

cacheability 및 absence 검증이 isAbsenceCacheable lambda로 분리되었습니다. 이 lambda는 generateConditionsForPropertyMissConcurrently 실행 전 headStructure에 먼저 적용되며, 이후 prototype 객체 각각에 대해서도 재사용됩니다. 추가된 주석에는 generateConditionsForPropertyMissConcurrently가 prototype chain만 순회한다는 점과, head 검증이 호출자의 책임임을 명시하고 있습니다.

DFG absence-condition builder에서 prototype chain만 검증하고 head receiver 자체에 대한 self-check가 누락되어, JIT가 receiver의 own property에 의해 모순되는 absence assumption을 설치할 수 있었던 패턴.

JSC의 optimizing tier는 런타임 property lookup을 회피하기 위해 structural invariant를 ObjectPropertyConditionSet에 기록합니다. 예를 들어 "object O에 property P가 없다"는 식의 조건을 저장하며, downstream code가 이를 machine code로 변환합니다. tryEnsureAbsence는 이 중 absence variant를 구성하는 함수입니다. generateConditionsForPropertyMissConcurrently는 prototype chain을 순회하며 각 prototype에 대한 Miss condition을 생성하지만, head structure는 검사하지 않습니다. PolyProto는 인스턴스별 prototype storage를 나타내며, 이는 prototype-chain caching을 무효화합니다. re-entrancy 지점(Promise thenable 감지, JSON.stringify toJSON, ToPrimitive)에서는 getter를 통해 사용자 코드가 호출될 수 있으며, 이 과정에서 인접한 typed storage가 변형될 수 있습니다.

패치 이전 코드는 headStructure가 null인 경우에만 중단하도록 구성되어 있었습니다. head에 대한 overridesGetOwnPropertySlot, propertyAccessesAreCacheable[ForAbsence], structure->getConcurrently(identifier.uid(), ...) 검사는 전혀 수행되지 않았습니다. Object.create를 통해 chain 상에 { toJSON: 1, a: 1 } head를 배치하면 이 조건을 통과할 수 있었습니다. DFG는 tmp.toJSON이 없다는 거짓 가정을 바탕으로 fast path를 JIT 컴파일하게 됩니다.

🔒

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.

더 확인하려면 구독해 주세요

🔒

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

더 확인하려면 구독해 주세요