Repository navigation
Conversation
…s realm A callable that a wrapped function returns must be wrapped for the realm of that wrapped function. JSC wrapped it for the target's realm when the target is not a plain JS function, so the caller received a function object whose prototype is the other realm's Function.prototype, and through its constructor the other realm's global object. Adds the cases to test/js/bun/jsc/shadow.test.js. The engine fix is in oven-sh/WebKit#590. Five of the eight tests in the file fail until the WebKit pin moves.
… target's return value for the caller's realm Pins WEBKIT_VERSION at the preview build of oven-sh/WebKit#590. A callable returned by a wrapped function whose target is not a plain JSFunction (a callable Proxy, or a built-in such as the realm's own Function) was wrapped for the target's realm instead of the caller's. The caller could read the other realm's Function constructor off the wrapper's prototype and reach that realm's global object, in both directions. The range from 2e2aa2290fac also contains oven-sh/WebKit#561, #566 and #568, already merged on oven-sh/WebKit main.
|
Warning Review limit reached
On-demand reviews are free for the next 12 days. After that, they cost $0.25 per reviewed file. Or wait 2 minutes for your next included review. View limit detailsLimit details: You’ve used all 10 included reviews currently available. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Essentials Run ID: 📒 Files selected for processing (2)
Comment |
|
Updated 12:03 PM PT - Sep 8th, 2026
✅ @robobun, your commit 99f67c1034166109d4caa76af743e5c690b518c4 passed in 🧪 To try this PR locally: bunx bun-pr 42028That installs a local version of the PR into your bun-42028 --bun |
|
Status Reproduced on bun 1.4.3 and on a debug build at WebKit pin With the engine fix from oven-sh/WebKit#590 (preview This PR depends on oven-sh/WebKit#590. Once that merges, the pin here moves from the preview tag to the merge commit. |
|
Review pass on 92515b9: the test now restores the mutated global in a |
|
Review pass on 82638d7: the target matrix uses |
|
99f67c1 spells the pin as the full release tag, |
Problem
ShadowRealm: when a wrapped function's target is not a plain JS function (a callableProxy, or a built-in such as the realm'sFunction) and it returns a callable, the returned wrapper was created in the target realm.Object.getPrototypeOf(wrapper)is then the other realm'sFunction.prototype, its.constructoris the other realm's genuineFunction, andF("return globalThis")()hands out that realm's global object. The leak runs both ways.remoteFunctionCallGeneric(Source/JavaScriptCore/runtime/JSRemoteFunction.cpp:158) ended withwrapReturnValue(globalObject, targetGlobalObject, result). The plain-function path and the JIT thunk wrap for the caller's realm, asOrdinaryWrappedFunctionCallspecifies. Node with--experimental-shadow-realmgets this right.Fix
wrapReturnValuetakes one global object, the caller's realm, on both call paths. This PR pinsWEBKIT_VERSIONat its preview buildautobuild-preview-pr-590-c7520f66.2e2aa2290facalso contains [JSC] Share one ScriptFetchParameters per type instead of allocating one per module request WebKit#561, [JSC] CodeBlock aging: refresh the execution-counter snapshot on every look, not only past the TTL WebKit#566 and [WTF] OSAllocatorPOSIX: test BUN_MACOSX with defined() WebKit#568, which are already on oven-sh/WebKitmain.test/js/bun/jsc/shadow.test.jsgains 7 cases (one per target kind, the global-object probe, and the reverse direction). 5 of 8 fail on the old pin, 8 of 8 pass on the new one. The sixtest/js/node/test/parallel/test-shadow-realm*.jsfiles andtest/regression/issue/29519.test.tspass.Background
ShadowRealmboundary must be a primitive or a callable. A callable is replaced by a wrapped function (JSRemoteFunctionin JSC) that lives in the receiving realm and forwards calls to the target.JSRemoteFunction::tryCreatedecides the wrapper's structure and[[Prototype]], so it decides which realm owns the wrapper.JSFunctiontargets (including bound functions) and a generic one for every other callable. Only the generic one had the swap, which is why test262'swrapped-function-proto-from-caller-realm.js(plain function target) did not catch it.Notes
src/andpackages/only, and the fix lives in the WebKit pin underscripts/, so it cannot reproduce the failing side. Fail-before was checked by hand:USE_SYSTEM_BUN=1 bun test test/js/bun/jsc/shadow.test.js(bun 1.4.3) andbun bd teston pin2e2aa2290facboth give 3 pass, 5 fail.test/js/bun/jsc/domjit.test.tsmillion-iteration loops time out at 5 s under the local debug ASAN build. They do not touch ShadowRealm.[policy-decision:webkit] gate passed · iteration 0 · 2 files touched
passes on PR (with fix)
diff hotspot
gate history · 4 passed · 0 rejected · iteration 0
evidence per changed file
root cause · written by the author bot
The generic ShadowRealm call path in
JSRemoteFunction::remoteFunctionCallGenericpassed the target realm's global object towrapReturnValue, so when a wrapped function's target was not a plain JS function (a callable Proxy or a built-in constructor such as the realm'sFunctionorObject) and it returned a callable, the wrapper was allocated with the target realm's remote function structure. That gave the caller a function whose[[Prototype]]was the other realm'sFunction.prototype, exposing that realm's genuineFunctionconstructor and, through it, its global object in both d…