Skip to content

Commit b42f7a7

Browse files
committed
YJIT: Prevent making a branch from a dead block to a live block
I'm seeing some memory corruption in the wild on blocks in `IseqPayload::dead_blocks`. While I unfortunately can't recreate the issue, (For all I know, it could be some external code corrupting YJIT's memory.) establishing a link between dead blocks and live blocks seems fishy enough that we ought to prevent it. When it did happen, it might've had bad interacts with Code GC and the optimization to immediately free empty blocks.
1 parent 75ae709 commit b42f7a7

1 file changed

Lines changed: 15 additions & 7 deletions

File tree

yjit/src/core.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3572,6 +3572,13 @@ fn branch_stub_hit_body(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) -
35723572
return CodegenGlobals::get_stub_exit_code().raw_ptr(cb);
35733573
}
35743574

3575+
// Bail if this branch is housed in an invalidated (dead) block.
3576+
// This only happens in rare invalidation scenarios and we need
3577+
// to avoid linking a dead block to a live block with a branch.
3578+
if branch.block.get().as_ref().iseq.get().is_null() {
3579+
return CodegenGlobals::get_stub_exit_code().raw_ptr(cb);
3580+
}
3581+
35753582
(cfp, original_interp_sp)
35763583
};
35773584

@@ -4278,25 +4285,26 @@ pub fn invalidate_block_version(blockref: &BlockRef) {
42784285
incr_counter!(invalidation_count);
42794286
}
42804287

4281-
// We cannot deallocate blocks immediately after invalidation since there
4282-
// could be stubs waiting to access branch pointers. Return stubs can do
4283-
// this since patching the code for setting up return addresses does not
4284-
// affect old return addresses that are already set up to use potentially
4285-
// invalidated branch pointers. Example:
4288+
// We cannot deallocate blocks immediately after invalidation since patching the code for setting
4289+
// up return addresses does not affect outstanding return addresses that are on stack and will use
4290+
// invalidated branch pointers when hit. Example:
42864291
// def foo(n)
42874292
// if n == 2
42884293
// # 1.times.each to create a cfunc frame to preserve the JIT frame
42894294
// # which will return to a stub housed in an invalidated block
42904295
// return 1.times.each { Object.define_method(:foo) {} }
42914296
// end
42924297
//
4293-
// foo(n + 1)
4298+
// foo(n + 1) # The block for this call houses the return branch stub
42944299
// end
42954300
// p foo(1)
42964301
pub fn delayed_deallocation(blockref: BlockRef) {
42974302
block_assumptions_free(blockref);
42984303

4299-
let payload = get_iseq_payload(unsafe { blockref.as_ref() }.iseq.get()).unwrap();
4304+
let block = unsafe { blockref.as_ref() };
4305+
// Set null ISEQ on the block to signal that it's dead.
4306+
let iseq = block.iseq.replace(ptr::null());
4307+
let payload = get_iseq_payload(iseq).unwrap();
43004308
payload.dead_blocks.push(blockref);
43014309
}
43024310

0 commit comments

Comments
 (0)