4 Commits
Author SHA1 Message Date
Samuel Williams 84a8652f01 coroutine: consistently return the context that resumed transfer. (#18814) 2026-09-14 03:04:04 +00:00
Koichi SasadaandClaude Opus 5 8c7c80f4da coroutine: do not write to the target after switching away
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>
2026-08-17 04:57:21 +09:00
Yuta Saito 0d4de0f4b1 wasm: align fiber stack pointer to 16 bytes
In WebAssembly C ABI, the linear stack pointer must be always aligned
to 16 bytes like other archs.
The misaligned stack pointer causes some weird memory corruption since
compiler assumes the aligned stack pointer.
2024-01-29 23:45:36 +09:00
Yuta Saito a4b73f1ba8 [wasm] add coroutine/asyncify implementation
set the default coroutine_type as asyncify when wasi
2022-01-19 11:19:06 +09:00