diff options
| author | Alan Wu <XrXr@users.noreply.github.com> | 2025-10-01 23:53:48 -0400 |
|---|---|---|
| committer | Alan Wu <XrXr@users.noreply.github.com> | 2025-10-02 12:09:40 -0400 |
| commit | 20fc91df395b834ecaee0bdb29df8da224fdf89a (patch) | |
| tree | 9f5c9ae7498d89afbb02464dbc01c87092cb9309 | |
| parent | 2ed5a02fcca4da4acf4c8c3d7ee4c392fc18d948 (diff) | |
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.
| -rw-r--r-- | yjit/src/core.rs | 22 |
1 files changed, 15 insertions, 7 deletions
diff --git a/yjit/src/core.rs b/yjit/src/core.rs index cfe55b8c76..2999f151bf 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -3591,6 +3591,13 @@ fn branch_stub_hit_body(branch_ptr: *const c_void, target_idx: u32, ec: EcPtr) - return CodegenGlobals::get_stub_exit_code().raw_ptr(cb); } + // Bail if this branch is housed in an invalidated (dead) block. + // This only happens in rare invalidation scenarios and we need + // to avoid linking a dead block to a live block with a branch. + if branch.block.get().as_ref().iseq.get().is_null() { + return CodegenGlobals::get_stub_exit_code().raw_ptr(cb); + } + (cfp, original_interp_sp) }; @@ -4297,11 +4304,9 @@ pub fn invalidate_block_version(blockref: &BlockRef) { incr_counter!(invalidation_count); } -// We cannot deallocate blocks immediately after invalidation since there -// could be stubs waiting to access branch pointers. Return stubs can do -// this since patching the code for setting up return addresses does not -// affect old return addresses that are already set up to use potentially -// invalidated branch pointers. Example: +// We cannot deallocate blocks immediately after invalidation since patching the code for setting +// up return addresses does not affect outstanding return addresses that are on stack and will use +// invalidated branch pointers when hit. Example: // def foo(n) // if n == 2 // # 1.times.each to create a cfunc frame to preserve the JIT frame @@ -4309,13 +4314,16 @@ pub fn invalidate_block_version(blockref: &BlockRef) { // return 1.times.each { Object.define_method(:foo) {} } // end // -// foo(n + 1) +// foo(n + 1) # The block for this call houses the return branch stub // end // p foo(1) pub fn delayed_deallocation(blockref: BlockRef) { block_assumptions_free(blockref); - let payload = get_iseq_payload(unsafe { blockref.as_ref() }.iseq.get()).unwrap(); + let block = unsafe { blockref.as_ref() }; + // Set null ISEQ on the block to signal that it's dead. + let iseq = block.iseq.replace(ptr::null()); + let payload = get_iseq_payload(iseq).unwrap(); payload.dead_blocks.push(blockref); } |
