[JSC] Promise jobs must not run with the realm of a cross-realm settle site
// JSTests/stress/cross-realm-await-thenable-resolve-realm.js
const other = createGlobalObject();
other.MainFunction = Function;
other.subject = promise; // cross-realm pending promise
new other.Function(`
(async () => {
await subject;
await { then(f) {
// Before patch: f.constructor === MainFunction (foreign realm leaked)
// After patch: f.constructor === Function (own realm)
f(0);
} };
})();
`)();
Multi-realm 환경(iframe, ShadowRealm, worker)에서는 각 realm이 자체적인 built-in constructor를 보유합니다. ECMAScript 명세는 각 microtask job의 realm을 생성 시점에 고정하도록 요구하는데, Await의 경우 이는 settle site가 아닌 Await 알고리즘의 4단계에서 결정됩니다. JSC는 같은 realm 내의 일반적인 경우를 위해 resolveWithInternalMicrotaskForAsyncAwait에 fast path를 마련해 두었습니다. 이 fast path는 PromiseResolve가 통상 요구하는 중간 promise 생성을 건너뜁니다.
이 commit은 두 가지 결함을 수정합니다. 먼저 await fast path에 realm 일치 여부 확인이 추가되어, vanilla promise를 직접 채택하기 전에 realm이 같은지 검증하게 됩니다. Cross-realm인 경우에는 thenable path로 처리되며(microtask가 1회 추가되고, V8과 호환됩니다), 자신의 realm이 유지됩니다. 한편 runInternalMicrotask 내부의 microtask handler는 이제 settle site의 globalObject 대신, 구동 대상 객체(generator, result promise, 또는 module)에서 realm을 가져오도록 변경되었습니다.
Significance
Cross-realm realm leakage가 발생하면, 이후 await된 thenable의 then(f,j)로 전달되는 resolving function이 예상치 못한 외부 realm의 constructor를 담게 될 가능성이 있습니다. 이는 JavaScript에서 감지 가능한 명세 위반이며, cross-realm 공격 시나리오에서 primitive로 활용될 수 있습니다.
Audit directions
resolvePromise는 의도적으로 realm check 없는 fast path를 유지합니다. commit 메시지는 명세가 이를 요구하지 않으며, thenable-job enqueue 시 promise의 realm을 사용하도록 수정하는 것으로 충분하다고 설명합니다. 다만 관찰 가능한 realm escape가 완전히 제거되었는지는 V8과의 differential testing을 통해 확인할 필요가 있습니다.- GlobalObject pointer-equality 검사. 모든 realm guard는
globalObject의 pointer equality 비교에 기반합니다. 동일한 논리적 realm이 복수의globalObject표현을 갖는 경우(embedder 재활용, 커스텀 backing global을 사용하는 ShadowRealm, Wasm promising path 등)에는 이 검사를 조용히 우회할 가능성이 있습니다. - 변경 파일이 28개에 달하며, WebCore binding, Wasm promising(
WebAssemblyPromising.cpp), 모듈 로딩(CyclicModuleRecord,JSModuleRecord,ModuleRegistryEntry),CodeGeneratorJS.pm등에 걸쳐 있습니다.globalObject파라미터 대신 구동 대상 객체에서 realm을 가져오도록 변경된 각 지점은 anchor가 잘못 적용될 수 있는 독립적인 위험 지점입니다. - Cross-realm path에서 microtask가 1회 추가되어 비동기 실행 순서의 의미가 달라집니다. ShadowRealm이나 embedder 수준의 promise consumer에서 microtask 순서에 의존하는 로직이 있다면 예기치 않은 동작을 보일 수 있습니다.