CVE-2026-65337
Overview
Background
- Polymorphic call inline cache
- A CallLinkInfo-backed cache for call sites that see multiple callees; linkPolymorphicCall updates it.
- virtualForWithFunction / host call
- Resolves a callee; for a host InternalFunction it may perform a call that runs JS, which can reset/free the inline cache.
- CallLinkInfo lifetime
- Compiled call-site metadata that JS run during resolution can invalidate/free, making later use a UAF.
Root Cause Analysis
This fixes a use-after-free of a CallLinkInfo when a polymorphic call resolves to a host (InternalFunction) callee that runs JavaScript. operationPolymorphicCall (JIT) and llint_polymorphic_call (LLInt) call virtualForWithFunction to resolve the callee, then link the polymorphic inline cache: linkPolymorphicCall(vm, owner, calleeFrame, *callLinkInfo, CallVariant(calleeAsFunctionCell)). For a host InternalFunction callee, virtualForWithFunction performs a host call that can execute arbitrary JavaScript; that JS can reset/free the inline cache, invalidating *callLinkInfo.
Pre-patch, virtualForWithFunction returned with calleeAsFunctionCell left null for the InternalFunction case, and the callers unconditionally called linkPolymorphicCall using the now-possibly-freed *callLinkInfo — a use-after-free.
The fix has two parts: virtualForWithFunction now sets calleeAsFunctionCell = internalFunction for the InternalFunction path (dynamicDowncast<InternalFunction>), and both callers only call linkPolymorphicCall when calleeAsFunctionCell is non-null, with the comment that a null cell means virtualForWithFunction did a host call that ran JS and may have freed *callLinkInfo, so it must not link.
The restored invariant is that the polymorphic call is only linked when the CallLinkInfo is still valid — never after a host call that could have freed it. The regression test installs a Proxy whose apply handler deletes the getter (resetting the IC) during a polymorphic getter call.
Attack Path
- Build a polymorphic call site Run JS so a call/getter site becomes polymorphic and uses operationPolymorphicCall / llint_polymorphic_call with a CallLinkInfo.
- Route to a host callee Make the callee a host InternalFunction (e.g. a Proxy apply) so virtualForWithFunction performs a host call that runs JS.
- Free the CallLinkInfo From that JS (the Proxy apply handler), delete the property/reset the inline cache so *callLinkInfo is freed.
- Use-after-free The caller links the polymorphic call using the freed CallLinkInfo, corrupting/crashing the WebContent process.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
operationPolymorphicCallSource/JavaScriptCore/jit/JITOperations.cpp |
modified | Only calls linkPolymorphicCall when calleeAsFunctionCell is non-null, since a null cell means virtualForWithFunction ran a host call that may have freed *callLinkInfo. |
llint_polymorphic_callSource/JavaScriptCore/llint/LLIntSlowPaths.cpp |
modified | Same guard: skip linkPolymorphicCall on a null callee cell to avoid using a freed CallLinkInfo. |
virtualForWithFunctionSource/JavaScriptCore/bytecode/RepatchInlines.h |
modified | Sets calleeAsFunctionCell to the InternalFunction (dynamicDowncast) so the callers can distinguish the host-call path that may have freed the CallLinkInfo. |
Files Changed
JSTests/stress/operation-polymorphic-call-host-call-ic-reset.jsSource/JavaScriptCore/bytecode/RepatchInlines.hSource/JavaScriptCore/jit/JITOperations.cppSource/JavaScriptCore/llint/LLIntSlowPaths.cpp
Audit Directions
- Post-host-call IC useAudit operationPolymorphicCall/virtual call slow paths for uses of *callLinkInfo (or other IC state) after a step that can run JS and reset the cache.
- Null-cell contractGrep virtualForWithFunction callers to confirm they treat a null calleeAsFunctionCell as ‘do not link’, not as a benign case.