Skip to content

[pull] master from ruby:master - #1318

Merged
pull[bot] merged 2 commits into
turkdevops:masterfrom
ruby:master
Aug 17, 2026
Merged

[pull] master from ruby:master#1318
pull[bot] merged 2 commits into
turkdevops:masterfrom
ruby:master

Conversation

@pull

@pull pull Bot commented Aug 17, 2026

Copy link
Copy Markdown

See Commits and Changes for more details.


Created by pull[bot] (v2.0.0-alpha.4)

Can you help keep this open source service alive? 💖 Please sponsor : )

ko1 and others added 2 commits August 17, 2026 04:57
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>
@pull pull Bot locked and limited conversation to collaborators Aug 17, 2026
@pull pull Bot added the ⤵️ pull label Aug 17, 2026
@pull
pull Bot merged commit 83131db into turkdevops:master Aug 17, 2026
1 check was pending
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants