diff options
author | Samuel Williams <[email protected]> | 2025-06-05 12:49:02 +0900 |
---|---|---|
committer | Samuel Williams <[email protected]> | 2025-06-06 13:13:16 +0900 |
commit | ead14b19aa5acbdfb2f1ccc53cc7b8b34517b6e9 () | |
tree | 5070560cb4326d1d209d81e2aed6dfcd29f28bf6 | |
parent | 81a23c5793fecaff5f75cefe6a6e03dab99df16b (diff) |
Fix `blocking_operation_wait` use-after-free bug.
Notes: Merged: https://.com/ruby/ruby/pull/13437
-rw-r--r-- | include/ruby/fiber/scheduler.h | 43 | ||||
-rw-r--r-- | inits.c | 2 | ||||
-rw-r--r-- | scheduler.c | 345 | ||||
-rw-r--r-- | test/fiber/scheduler.rb | 2 | ||||
-rw-r--r-- | test/fiber/test_io_close.rb | 7 | ||||
-rw-r--r-- | thread.c | 2 |
6 files changed, 350 insertions, 51 deletions
@@ -394,11 +394,54 @@ VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io); */ VALUE rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname); struct rb_fiber_scheduler_blocking_operation_state { void *result; int saved_errno; }; /** * Defer the execution of the passed function to the scheduler. * @@ -63,9 +63,9 @@ rb_call_inits(void) CALL(ISeq); CALL(Thread); CALL(signal); CALL(Fiber_Scheduler); CALL(process); - CALL(Cont); CALL(Rational); CALL(Complex); CALL(MemoryView); @@ -15,9 +15,12 @@ #include "ruby/thread.h" -// For `ruby_thread_has_gvl_p`. #include "internal/thread.h" static ID id_close; static ID id_scheduler_close; @@ -41,7 +44,219 @@ static ID id_fiber_interrupt; static ID id_fiber_schedule; /* * Document-class: Fiber::Scheduler * * This is not an existing class, but documentation of the interface that Scheduler @@ -121,6 +336,15 @@ Init_Fiber_Scheduler(void) id_fiber_schedule = rb_intern_const("fiber"); #if 0 /* for RDoc */ rb_cFiberScheduler = rb_define_class_under(rb_cFiber, "Scheduler", rb_cObject); rb_define_method(rb_cFiberScheduler, "close", rb_fiber_scheduler_close, 0); @@ -136,7 +360,7 @@ Init_Fiber_Scheduler(void) rb_define_method(rb_cFiberScheduler, "timeout_after", rb_fiber_scheduler_timeout_after, 3); rb_define_method(rb_cFiberScheduler, "block", rb_fiber_scheduler_block, 2); rb_define_method(rb_cFiberScheduler, "unblock", rb_fiber_scheduler_unblock, 2); - rb_define_method(rb_cFiberScheduler, "fiber", rb_fiber_scheduler, -2); rb_define_method(rb_cFiberScheduler, "blocking_operation_wait", rb_fiber_scheduler_blocking_operation_wait, -2); #endif } @@ -798,60 +1022,52 @@ rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname) return rb_check_funcall(scheduler, id_address_resolve, 1, arguments); } -struct rb_blocking_operation_wait_arguments { - void *(*function)(void *); - void *data; - rb_unblock_function_t *unblock_function; - void *data2; - int flags; - - struct rb_fiber_scheduler_blocking_operation_state *state; -}; - -static VALUE -rb_fiber_scheduler_blocking_operation_wait_proc(RB_BLOCK_CALL_FUNC_ARGLIST(value, _arguments)) -{ - struct rb_blocking_operation_wait_arguments *arguments = (struct rb_blocking_operation_wait_arguments*)_arguments; - - if (arguments->state == NULL) { - rb_raise(rb_eRuntimeError, "Blocking function was already invoked!"); - } - - arguments->state->result = rb_nogvl(arguments->function, arguments->data, arguments->unblock_function, arguments->data2, arguments->flags); - arguments->state->saved_errno = rb_errno(); - - // Make sure it's only invoked once. - arguments->state = NULL; - - return Qnil; -} - /* * Document-method: Fiber::Scheduler#blocking_operation_wait - * call-seq: blocking_operation_wait(work) * * Invoked by Ruby's core methods to run a blocking operation in a non-blocking way. * * Minimal suggested implementation is: * - * def blocking_operation_wait(work) - * Thread.new(&work).join * end */ VALUE rb_fiber_scheduler_blocking_operation_wait(VALUE scheduler, void* (*function)(void *), void *data, rb_unblock_function_t *unblock_function, void *data2, int flags, struct rb_fiber_scheduler_blocking_operation_state *state) { - struct rb_blocking_operation_wait_arguments arguments = { - .function = function, - .data = data, - .unblock_function = unblock_function, - .data2 = data2, - .flags = flags, - .state = state - }; - VALUE proc = rb_proc_new(rb_fiber_scheduler_blocking_operation_wait_proc, (VALUE)&arguments); - return rb_check_funcall(scheduler, id_blocking_operation_wait, 1, &proc); } VALUE rb_fiber_scheduler_fiber_interrupt(VALUE scheduler, VALUE fiber, VALUE exception) @@ -890,3 +1106,46 @@ rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat) { return rb_funcall_passing_block_kw(scheduler, id_fiber_schedule, argc, argv, kw_splat); } @@ -341,7 +341,7 @@ class Scheduler end def blocking_operation_wait(work) - thread = Thread.new(&work) thread.join @@ -16,9 +16,8 @@ class TestFiberIOClose < Test::Unit::TestCase end end - # Problematic on Windows. def test_io_close_across_fibers - omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/ with_socket_pair do |i, o| error = nil @@ -45,7 +44,6 @@ class TestFiberIOClose < Test::Unit::TestCase end end - # Okay on all platforms. def test_io_close_blocking_thread omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/ @@ -77,9 +75,8 @@ class TestFiberIOClose < Test::Unit::TestCase end end - # Problematic on Windows. def test_io_close_blocking_fiber - omit "Interrupting a io_wait read is not supported!" if RUBY_PLATFORM =~ /mswin|mingw/ with_socket_pair do |i, o| error = nil @@ -1552,7 +1552,7 @@ rb_nogvl(void *(*func)(void *), void *data1, if (flags & RB_NOGVL_OFFLOAD_SAFE) { VALUE scheduler = rb_fiber_scheduler_current(); if (scheduler != Qnil) { - struct rb_fiber_scheduler_blocking_operation_state state; VALUE result = rb_fiber_scheduler_blocking_operation_wait(scheduler, func, data1, ubf, data2, flags, &state); |