From 729d602e290a3d986c8fc3d178cf1e56f93a88a4 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Sep 2025 14:39:52 -0400 Subject: [PATCH 1/5] Assert that RARRAY_AREF is within bounds We should assert that i is within bounds to prevent buffer overflows. --- internal/array.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/internal/array.h b/internal/array.h index 398676df4aa80d..3a689646fbb0f4 100644 --- a/internal/array.h +++ b/internal/array.h @@ -140,6 +140,8 @@ RARRAY_AREF(VALUE ary, long i) VALUE val; RBIMPL_ASSERT_TYPE(ary, RUBY_T_ARRAY); + RUBY_ASSERT(i < RARRAY_LEN(ary)); + RBIMPL_WARNING_PUSH(); #if defined(__GNUC__) && !defined(__clang__) && __GNUC__ == 13 RBIMPL_WARNING_IGNORED(-Warray-bounds); From 5a946a5be29a099ddaff55b52b5c2ab0cbe61ca1 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Sep 2025 14:42:02 -0400 Subject: [PATCH 2/5] Change order of RARRAY_AREF and ARY_SET_LEN in rb_ary_pop We should access the last element first before we shrink the length of the array. --- array.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/array.c b/array.c index 9f13b1bf5142b3..d9dff28ad95ebd 100644 --- a/array.c +++ b/array.c @@ -1439,10 +1439,12 @@ rb_ary_pop(VALUE ary) { ary_resize_capa(ary, n * 2); } - --n; - ARY_SET_LEN(ary, n); + + VALUE obj = RARRAY_AREF(ary, n - 1); + + ARY_SET_LEN(ary, n - 1); ary_verify(ary); - return RARRAY_AREF(ary, n); + return obj; } /* From 21d76b423cf4967762dd783dd1114d980e33dac9 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Sep 2025 14:47:06 -0400 Subject: [PATCH 3/5] Fix out-of-bounds read in Method#inspect for unnamed parameters For parameters without names, accessing the name in array index 1 would be an out-of-bounds read. --- proc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/proc.c b/proc.c index ae1068e24fe9cf..8f0eb0a898b711 100644 --- a/proc.c +++ b/proc.c @@ -3332,9 +3332,10 @@ method_inspect(VALUE method) for (int i = 0; i < RARRAY_LEN(params); i++) { pair = RARRAY_AREF(params, i); kind = RARRAY_AREF(pair, 0); - name = RARRAY_AREF(pair, 1); - // FIXME: in tests it turns out that kind, name = [:req] produces name to be false. Why?.. - if (NIL_P(name) || name == Qfalse) { + if (RARRAY_LEN(pair) > 1) { + name = RARRAY_AREF(pair, 1); + } + else { // FIXME: can it be reduced to switch/case? if (kind == req || kind == opt) { name = rb_str_new2("_"); @@ -3348,6 +3349,9 @@ method_inspect(VALUE method) else if (kind == nokey) { name = rb_str_new2("nil"); } + else { + name = Qnil; + } } if (kind == req) { From f164e1c03ac8b509563c7305ff2249c21d7b78b3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Sep 2025 14:55:55 -0400 Subject: [PATCH 4/5] Fix out-of-bounds read in require when $LOADED_FEATURES is modified The following script causes an out-of-bounds read on the $LOADED_FEATURES array when it is modified by another thread: require "tempfile" PATH = Tempfile.create(["test", ".rb"]).path 2.times.map do Thread.new do 20.times do require PATH $LOADED_FEATURES.delete_if { |p| p == PATH } end end end.each(&:join) Crashes with: internal/array.h:143: Assertion Failed: RARRAY_AREF:i < RARRAY_LEN(ary) ruby 3.5.0dev (2025-09-10T18:47:06Z array-aref-assert-.. 765a3fd01c) +PRISM [arm64-darwin24] -- Crash Report log information -------------------------------------------- See Crash Report log file in one of the following locations: * ~/Library/Logs/DiagnosticReports * /Library/Logs/DiagnosticReports for more details. Don't forget to include the above Crash Report log file in bug reports. -- Control frame information ----------------------------------------------- c:0005 p:---- s:0019 e:000018 CFUNC :require c:0004 p:0005 s:0014 e:000013 BLOCK test.rb:19 c:0003 p:0024 s:0011 e:000010 METHOD :257 c:0002 p:0005 s:0006 e:000005 BLOCK test.rb:18 [FINISH] c:0001 p:---- s:0003 e:000002 DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- test.rb:18:in 'block (2 levels) in
' :257:in 'times' test.rb:19:in 'block (3 levels) in
' test.rb:19:in 'require' -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 2 -- C level backtrace information ------------------------------------------- miniruby(rb_vm_bugreport+0xb88) [0x100f3f1d4] vm_dump.c:1175 miniruby(rb_vm_bugreport) (null):0 miniruby(rb_assert_failure_detail+0xd4) [0x10108d920] error.c:1215 miniruby(rb_assert_failure_detail+0x0) [0x10108d84c] error.c:1191 miniruby(rb_assert_failure) (null):0 miniruby(rb_ary_pop.cold.9+0x0) [0x101087198] internal/array.h:143 miniruby(RARRAY_AREF) (null):0 miniruby(rb_ary_pop.cold.7) array.c:1443 miniruby(rb_feature_p+0x720) [0x100dbe28c] internal/array.h:143 miniruby(search_required+0x2cc) [0x100dbcb78] load.c:1203 miniruby(require_internal+0x144) [0x100dbd108] load.c:1434 miniruby(rb_require_string_internal+0x78) [0x100dbc6bc] load.c:1581 miniruby(rb_require_string+0x20) [0x100dbc56c] load.c:1567 miniruby(rb_f_require) load.c:1160 miniruby(vm_call_cfunc_with_frame_+0xe8) [0x100f2e998] vm_insnhelper.c:3873 miniruby(vm_sendish+0x718) [0x100f08b20] miniruby(vm_exec_core+0x6044) [0x100f10a94] miniruby(rb_vm_exec+0x170) [0x100f08e3c] vm.c:2639 miniruby(vm_invoke_proc+0x200) [0x100f1f564] vm.c:1669 miniruby(thread_do_start_proc+0x2f4) [0x100ed8420] thread.c:605 miniruby(thread_start_func_2+0x37c) [0x100ed7714] thread.c:622 miniruby(call_thread_start_func_2+0x18) [0x100eda144] thread_pthread.c:2234 miniruby(nt_start) thread_pthread.c:2279 /usr/lib/system/libsystem_pthread.dylib(_pthread_start+0x88) [0x19c0e7c0c] --- load.c | 1 + 1 file changed, 1 insertion(+) diff --git a/load.c b/load.c index 1c1fe1afa15109..217d7bad847fa5 100644 --- a/load.c +++ b/load.c @@ -630,6 +630,7 @@ rb_feature_p(vm_ns_t *vm_ns, const char *feature, const char *ext, int rb, int e index = rb_darray_get(feature_indexes, i); } + if (index >= RARRAY_LEN(features)) continue; v = RARRAY_AREF(features, index); f = StringValuePtr(v); if ((n = RSTRING_LEN(v)) < len) continue; From b62753246eba4940f82a81736fc09b6517fa3965 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Wed, 10 Sep 2025 19:53:13 -0400 Subject: [PATCH 5/5] Fix out-of-bounds read in rb_location_ary_to_backtrace rb_location_ary_to_backtrace was not checking the length of the array before reading the first element. It can be reproduced by the following script: begin raise rescue $@ = [] end With assertions enabled, it crashes with: internal/array.h:143: Assertion Failed: RARRAY_AREF:i < RARRAY_LEN(ary) ruby 3.5.0dev (2025-09-10T19:01:16Z array-aref-assert-.. c431de0c64) +PRISM [arm64-darwin24] -- Crash Report log information -------------------------------------------- See Crash Report log file in one of the following locations: * ~/Library/Logs/DiagnosticReports * /Library/Logs/DiagnosticReports for more details. Don't forget to include the above Crash Report log file in bug reports. -- Control frame information ----------------------------------------------- c:0004 p:---- s:0015 e:000014 CFUNC :set_backtrace c:0003 p:0013 s:0012 e:000009 RESCUE test.rb:4 c:0002 p:0004 s:0006 e:000005 EVAL test.rb:1 [FINISH] c:0001 p:0000 s:0003 E:001bb0 DUMMY [FINISH] -- Ruby level backtrace information ---------------------------------------- test.rb:1:in '
' test.rb:4:in 'rescue in
' test.rb:4:in 'set_backtrace' -- Threading information --------------------------------------------------- Total ractor count: 1 Ruby thread count for this ractor: 1 -- C level backtrace information ------------------------------------------- miniruby(rb_vm_bugreport+0xb88) [0x1002adb88] vm_dump.c:1175 miniruby(rb_vm_bugreport) (null):0 miniruby(rb_assert_failure_detail+0xd4) [0x1003fbf90] error.c:1215 miniruby(rb_assert_failure_detail+0x0) [0x1003fbebc] error.c:1191 miniruby(rb_assert_failure) (null):0 miniruby(RARRAY_AREF+0x20) [0x1003f82c8] internal/array.h:143 miniruby(rb_keyword_error_new.cold.2) class.c:2867 miniruby(rb_keyword_error_new.cold.4) (null):0 miniruby(rb_location_ary_to_backtrace+0x244) [0x1002a8a60] internal/array.h:143 miniruby(RB_TEST+0x0) [0x1000ba648] error.c:2111 miniruby(exc_set_backtrace) error.c:2112 miniruby(vm_call0_body+0x7d0) [0x1002a414c] vm_eval.c:164 miniruby(rb_vm_call0+0x100) [0x100286ee4] vm_eval.c:101 miniruby(set_backtrace+0xfc) [0x1000c88a4] eval_error.c:75 miniruby(rb_gvar_set_entry+0x10) [0x100269230] variable.c:990 miniruby(rb_gvar_set) variable.c:1021 miniruby(vm_exec_core+0x1258) [0x10027a744] insns.def:319 miniruby(rb_vm_exec+0x324) [0x100277a8c] vm.c:2666 miniruby(rb_ec_exec_node+0x74) [0x1000c4a38] eval.c:282 miniruby(ruby_run_node+0x64) [0x1000c4968] eval.c:320 miniruby(rb_main+0x1c) [0x100000980] main.c:42 miniruby(main) main.c:62 --- vm_backtrace.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm_backtrace.c b/vm_backtrace.c index cc8607b2d724c7..aaa0b5051cc637 100644 --- a/vm_backtrace.c +++ b/vm_backtrace.c @@ -858,7 +858,7 @@ rb_backtrace_to_location_ary(VALUE self) VALUE rb_location_ary_to_backtrace(VALUE ary) { - if (!RB_TYPE_P(ary, T_ARRAY) || !rb_frame_info_p(RARRAY_AREF(ary, 0))) { + if (!RB_TYPE_P(ary, T_ARRAY) || RARRAY_LEN(ary) == 0 || !rb_frame_info_p(RARRAY_AREF(ary, 0))) { return Qfalse; }