From dc671f007abf4b931eeec869d0f16cddd16d9df9 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 28 May 2026 13:19:10 +0900 Subject: [PATCH] vm.c: restore mrb->jmp on early return for task switch mrb_vm_exec sets mrb->jmp to its own stack-local c_jmp on entry and restores the caller's prev_jmp on every normal return path. The early return added for task switching (RETURN_IF_TASK_STOPPED) skipped that restore, so after a task was preempted via Task.pass the dangling mrb->jmp pointed into mrb_vm_exec's freed stack frame. A subsequent raise then longjmp'd into that freed frame and crashed. Restore prev_jmp in the early-return path, matching the normal returns. Task.new { Task.pass } Task.pass raise "boom" # SIGSEGV before this change Fixes #6863. Refs #6864. Co-authored-by: Claude --- src/vm.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/vm.c b/src/vm.c index fae3b929c..10efd5b70 100644 --- a/src/vm.c +++ b/src/vm.c @@ -1594,11 +1594,21 @@ prepare_tagged_break(mrb_state *mrb, uint32_t tag, const mrb_callinfo *return_ci iteration is still active drifts the call-info stack and eventually crashes (issue #6862). Switches resume at the next OP boundary after the walk releases gc.iterating. A pending MRB_TASK_STOPPED is not - deferred -- the task is going away. */ + deferred, since the task is going away. + + mrb->jmp is restored to prev_jmp before returning, exactly as the + normal return paths below do. mrb_vm_exec set mrb->jmp to its own + stack-local c_jmp on entry; leaving it dangling after this early return + means a later raise longjmps into a freed frame (issue #6863). + + This macro must only be expanded where prev_jmp is in scope, i.e. + inside mrb_vm_exec (via NEXT / END_DISPATCH). */ #define RETURN_IF_TASK_STOPPED(mrb) do { \ if (((mrb)->task.switching && !(mrb)->gc.iterating) || \ - (mrb)->c->status == MRB_TASK_STOPPED) \ + (mrb)->c->status == MRB_TASK_STOPPED) { \ + (mrb)->jmp = prev_jmp; \ return mrb_nil_value(); \ + } \ } while (0) #define TASK_STOP(mrb) do { \ if (mrb->c->status != MRB_TASK_STOPPED) \