← All issues

[JSC] `isDefinitelyNonThenable` Structure cache can go stale when the prototype belongs to another realm

8d6b112

Source/JavaScriptCore/runtime/JSPromise.cpp

- if (!proto.isObject() || asObject(proto) == globalObject->objectPrototype())
+ if (!proto.isObject() || asObject(proto) == structure->realm()->objectPrototype())
structure->setDefinitelyNonThenableState(Structure::DefinitelyNonThenableState::NonThenable);
else
structure->setDefinitelyNonThenableState(Structure::DefinitelyNonThenableState::Uncacheable);

JSC의 Promise resolution 경로는 per-structure cache를 기반으로 isDefinitelyNonThenable을 호출하여 비용이 큰 .then property 조회를 생략합니다. 이미 확인된 structure의 prototype에 .then이 없는 경우, 결과는 NonThenable로 캐싱됩니다. 이 cache를 보호하는 것이 promiseThenWatchpointSet인데, realm의 Object.prototype.then이 추가될 때 발동하여 기존 캐싱 결과를 무효화합니다. 다만 미묘한 invariant가 하나 있습니다. 이 guard는 caller의 realm이 아닌 structure->realm()에 귀속됩니다.

이 commit은 객체의 prototype이 해당 객체의 structure와 다른 realm에 속할 때 stale cache가 발생하는 문제를 수정합니다. 수정 전에는 cacheability guard가 prototype을 callerglobalObject->objectPrototype()과 비교했습니다. 그러나 무효화 watchpoint는 structure의 realm에서 발동합니다. 두 realm이 서로 다른 경우에도 cache가 잘못 유지될 수 있는 이유입니다. 이번 수정에서는 globalObject->objectPrototype()structure->realm()->objectPrototype()으로 변경하여, mixed-realm prototype chain을 가진 객체를 Uncacheable로 처리하도록 했습니다.

Before fix:
  Object: realm-A structure, prototype = realm-B Object.prototype

  Cache-populating resolve called from realm B:
    globalObject->objectPrototype() = realm-B Object.prototype  ← matches proto
    → NonThenable cached on realm-A structure
      (guarded by realm-A's promiseThenWatchpointSet)

  later: realm-B Object.prototype.then = fn
    → fires realm-B watchpoint only
    → realm-A structure cache untouched → stale NonThenable ❌

After fix:
  proto == structure->realm()->objectPrototype()?  → realm-A ≠ realm-B → Uncacheable ✓

cache 생성 이후에 .then이 추가된 진짜 thenable이 있다면, Promise resolution 알고리즘은 해당 객체를 평범한 값으로 조용히 처리합니다. 결과적으로 ECMAScript spec invariant가 깨지고, cross-realm prototype 조작을 통해 thenable 감지 자체를 우회하는 상황이 가능해집니다. realm-X의 watchpoint로 보호되는 결과를 캐싱하면서 cacheability 판단에는 다른 realm의 객체를 기준으로 삼는 코드가 JSC 안에 있다면, 동일한 문제가 발생합니다. 이런 버그 유형은 JSC 전반에서 추가로 점검할 가치가 있습니다.

🔒

The slow-path fallback and other realm-keyed caches in JSC share this pattern — audit directions included.

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