Bug #21511
openUse-after-free of the execution context after the fiber object carrying it is freed in GC
Description
In bootstraptest/test_thread.rb,
assert_equal 'ok', %{
File.write("zzz_t1.rb", <<-END)
begin
Thread.new { fork { GC.start } }.join
pid, status = Process.wait2
$result = status.success? ? :ok : :ng
rescue NotImplementedError
$result = :ok
end
END
require "./zzz_t1.rb"
$result
}
# in build/
make btest BTESTS="file_containing_above.rb"
# or
ruby --disable=gems "../bootstraptest/runner.rb" --ruby="./miniruby -I../lib -I. -I.ext/common -r./x86_64-linux-fake --disable-gems" file_containing_above.rb
Suppose thread 1 called the Thread.new
and created thread 2
The forked process by thread 2 that initiates GC with GC.start
would sweep the fiber object embedded in RTypedData
in the gc_sweep_rest()
stage of sweep in fiber_free()
. That fiber object contains the execution context of thread 1, rb_execution_context_t saved_ec
field of cont
.
Since the fiber object is freed, the allocated area pointed by it should be invalid, including the embedded struct for ec, but after thread 2 joins, thread 1 still uses the ec in rb_current_thread(), causing a use after free.
Updated by nobu (Nobuyoshi Nakada) about 16 hours ago
- Status changed from Open to Feedback
I can't reproduce it with ruby_3_4 (1e3d24a0f47) on aarch64-linux.
What version is commit:de8de51182?
Updated by tuonigou (tianyang sun) about 16 hours ago
nobu (Nobuyoshi Nakada) wrote in #note-1:
I can't reproduce it with ruby_3_4 (1e3d24a0f47) on aarch64-linux.
What version is commit:de8de51182?
Sorry I was using the 3.4.1 stable release from https://www.ruby-lang.org/en/downloads/, which is not on there anymore. The version number is just from adding version control, but I did not change anything to the codebase, sorry for the confusion.
Updated by nobu (Nobuyoshi Nakada) about 16 hours ago
- Status changed from Feedback to Open
3.4.1 is outdate.
Could you try with more recent version?
BTW, 3.4.1 tarball is not listed there, but still exists.
https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.1.tar.gz
And its RUBY_REVISION
in revision.h
is "48d4efcb85"
, and the date is 2024-12-25.
Updated by tuonigou (tianyang sun) about 16 hours ago
nobu (Nobuyoshi Nakada) wrote in #note-3:
3.4.1 is outdate.
Could you try with more recent version?BTW, 3.4.1 tarball is not listed there, but still exists.
https://cache.ruby-lang.org/pub/ruby/3.4/ruby-3.4.1.tar.gz
And itsRUBY_REVISION
inrevision.h
is"48d4efcb85"
, and the date is 2024-12-25.
Thank you I am trying to compile the current master branch and maybe the 3.4.4 release.
Updated by tuonigou (tianyang sun) about 14 hours ago
using ruby 3.5.0dev (2025-07-14T05:11:58Z master 8f54b5bb93) +PRISM [x86_64-linux]
# this is currently in the forked proc in GC (the `fork { GC.start }` part)
# thread 3.3 is the created thread by Thread.new
(gdb) i threads
Id Target Id Frame
1.1 Thread 0x7ffff7de4580 (LWP 2916144) "ruby" vfork ()
at ../sysdeps/unix/sysv/linux/x86_64/vfork.S:41
1.2 Thread 0x7fffdddff640 (LWP 2916147) "ruby" 0x00007ffff7b25e2e in epoll_wait (
epfd=4, events=0x555555b219fc <timer_th+28>, maxevents=16, timeout=-1)
at ../sysdeps/unix/sysv/linux/epoll_wait.c:30
1.3 Thread 0x7fffdc0bd640 (LWP 2916150) "runner.rb:546" 0x00007ffff7b18bcf in __GI___poll (
fds=0x7fffdc0bb1f0, nfds=1, timeout=-1) at ../sysdeps/unix/sysv/linux/poll.c:29
3.1 Thread 0x7ffff7de4580 (LWP 2916151) "miniruby" __futex_abstimed_wait_common64 (
private=0, cancel=true, abstime=0x0, op=393, expected=0, futex_word=0x555555b34350)
at ./nptl/futex-internal.c:57
3.3 Thread 0x7fffdc1be640 (LWP 2916153) "zzz_t1.rb:2" arch_fork (ctid=0x7fffdc1be910)
at ../sysdeps/unix/sysv/linux/arch-fork.h:52
* 4.1 Thread 0x7fffdc1be640 (LWP 2916154) "zzz_t1.rb:2" cont_free (ptr=0x555555ba84f0)
at ../cont.c:1094
# showing the ec's address in those two threads which will be used after freeing in thread 4.1's GC
(gdb) t 3.1
[Switching to thread 3.1 (Thread 0x7ffff7de4580 (LWP 2916151))]
#0 __futex_abstimed_wait_common64 (private=0, cancel=true, abstime=0x0, op=393, expected=0,
futex_word=0x555555b34350) at ./nptl/futex-internal.c:57
57 ./nptl/futex-internal.c: No such file or directory.
(gdb) p ruby_current_ec
$6 = (struct rb_execution_context_struct *) 0x555555b3c230
(gdb) t 3.3
[Switching to thread 3.3 (Thread 0x7fffdc1be640 (LWP 2916153))]
#0 arch_fork (ctid=0x7fffdc1be910) at ../sysdeps/unix/sysv/linux/arch-fork.h:52
52 ../sysdeps/unix/sysv/linux/arch-fork.h: No such file or directory.
(gdb) p ruby_current_ec
$7 = (struct rb_execution_context_struct *) 0x555555ba8540
# the fiber_free in GC frees fiber that contains above ec's
Thread 4.1 "zzz_t1.rb:2" hit Breakpoint 3, fiber_free (ptr=0x555555b3c1e0) at ../cont.c:1170
1170 rb_fiber_t *fiber = ptr;
(gdb) p fiber
$1 = (rb_fiber_t *) 0x555555b3c1e0
ruby_xfree (x=0x555555b3c1e0) at ../gc.c:5301
5301 ruby_sized_xfree(x, 0);
(gdb) p x
$2 = (void *) 0x555555b3c1e0
Thread 4.1 "zzz_t1.rb:2" hit Breakpoint 3, fiber_free (ptr=0x555555ba84f0) at ../cont.c:1170
1170 rb_fiber_t *fiber = ptr;
(gdb) p fiber
$4 = (rb_fiber_t *) 0x555555ba84f0
1094 ruby_xfree(ptr);
(gdb) p ptr
$5 = (void *) 0x555555ba84f0
# after 4.1 exits
[Inferior 4 (process 2916154) exited normally]
# they were used
(gdb) awatch *0x555555b3c230
Hardware access (read/write) watchpoint 4: *0x555555b3c230
(gdb) awatch *0x555555ba8540
Hardware access (read/write) watchpoint 5: *0x555555ba8540
Thread 3.3 "zzz_t1.rb:2" hit Hardware access (read/write) watchpoint 5: *0x555555ba8540
Old value = -141955056
New value = 0
rb_ec_set_vm_stack (ec=0x555555ba8540, stack=0x0, size=0) at ../vm.c:3648
3648 ec->vm_stack_size = size;
(gdb) p ec
$8 = (rb_execution_context_t *) 0x555555ba8540
Thread 3.1 "miniruby" hit Hardware access (read/write) watchpoint 4: *0x555555b3c230
Value = -137850864
0x00005555558a0bd1 in ruby_vm_destruct (vm=0x555555b35310) at ../vm.c:3144
3144 VALUE *stack = th->ec->vm_stack;
(gdb) p th->ec
$9 = (rb_execution_context_t *) 0x555555b3c230
Thread 3.1 "miniruby" hit Hardware access (read/write) watchpoint 4: *0x555555b3c230
Old value = -137850864
New value = 0
rb_ec_set_vm_stack (ec=0x555555b3c230, stack=0x0, size=0) at ../vm.c:3648
3648 ec->vm_stack_size = size;
(gdb) p ec
$10 = (rb_execution_context_t *) 0x555555b3c230
sizeof rb_fiber_t = 0x250, so
sizeof(struct rb_execution_context_struct) = 0x170
[0x555555b3c230-0x555555B3C3A0] is within [0x555555b3c1e0-0x555555B3C430] the range of rb_fiber_t which is freed
[0x555555ba8540-0x555555BA86B0] is within [0x555555ba84f0-0x555555BA8740] the range of rb_fiber_t which is freed