
Issue #21511 has been updated by tuonigou (tianyang sun). using ruby 3.5.0dev (2025-07-14T05:11:58Z master 8f54b5bb93) +PRISM [x86_64-linux] ``` shell # 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 ---------------------------------------- Bug #21511: Use-after-free of the execution context after the fiber object carrying it is freed in GC https://bugs.ruby-lang.org/issues/21511#change-114020 * Author: tuonigou (tianyang sun) * Status: Open * ruby -v: ruby 3.4.1 (2025-06-13 revision de8de51182) +PRISM [x86_64-linux] * Backport: 3.2: UNKNOWN, 3.3: UNKNOWN, 3.4: UNKNOWN ---------------------------------------- In bootstraptest/test_thread.rb, ``` ruby 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 } ``` ``` shell # 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. -- https://bugs.ruby-lang.org/