coroutine_transfer() restores target->from after the switch, that is,
after this coroutine is resumed -- by then it may have been resumed by
someone else and target may be freed.
With M:N threads it usually is: a Ruby thread parks by transferring to
its shared native thread's context (thread_sched_wait_running_turn), and
that thread, finding the ready queue empty, retires and frees nt and
nt->nt_context. Over 40 retires in the reproducer below, the number of
threads still parked on the retiring context was a median of 19 (max 61,
zero in four cases); each writes to it when it is next resumed.
sizeof(ucontext_t) is 968, so the write lands in a freed 1008-byte
region, and once that is reused it corrupts the new owner:
free(): invalid next size (fast)
ruby_xfree_sized ... st_free_entries -> rb_st_free_table
ractor_free_all_ports -> ractor_notify_exit -> ractor_atexit
thread_start_func_2 -> co_start -> coroutine_trampoline
AddressSanitizer: heap-use-after-free
WRITE of size 8 in coroutine_transfer (Context.h:97), thread T3
968 bytes inside a 1008-byte region freed in nt_start ->
native_thread_destroy_self, allocated in native_thread_alloc
from is read only by the trampoline, when target starts, so the restore
is dead by the time the switch returns. Skip it under
COROUTINE_TARGET_MAY_BE_FREED, which thread_pthread.c defines; cont.c
and other embedders keep the original behaviour. Only ucontext runs
M:N threads today, but the lifetime rule is not specific to them, so
guard every backend that has the restore.
Fibers never hit this: a suspended fiber still references its target, so
it cannot be collected. A native thread's context is not a Ruby object
and is freed by hand.
Reproducer: clang-20 -O1 --enable-shared --with-coroutine=ucontext,
bootstraptest/test_ractor.rb (the case at line 2603 spawns 2000 Ractors)
four times in parallel on four CPUs: 21 of 48 runs died before, 0 of 48
after. Under ASAN, the isolated case eight at a time: 120 of 120
reported before, 0 of 120 after.
CI: https://github.com/ruby/ruby/actions/runs/31942932752/job/95154613098
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>