summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSamuel Williams <[email protected]>2025-06-05 12:49:02 +0900
committerSamuel Williams <[email protected]>2025-06-06 13:13:16 +0900
commitead14b19aa5acbdfb2f1ccc53cc7b8b34517b6e9 ()
tree5070560cb4326d1d209d81e2aed6dfcd29f28bf6
parent81a23c5793fecaff5f75cefe6a6e03dab99df16b (diff)
Fix `blocking_operation_wait` use-after-free bug.
Notes: Merged: https://.com/ruby/ruby/pull/13437
-rw-r--r--include/ruby/fiber/scheduler.h43
-rw-r--r--inits.c2
-rw-r--r--scheduler.c345
-rw-r--r--test/fiber/scheduler.rb2
-rw-r--r--test/fiber/test_io_close.rb7
-rw-r--r--thread.c2
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);