merge revision(s) 928fea3bfa86053c0bc6f7a5bf7559b115a676b5: [Backport #21567]

Fix crash when $LOADED_FEATURES is modified during require

	[Bug #21567]

	When we require an object that is not a string, it will attempt to convert
	it to a string by calling to_str on it. If we modify the $LOADED_FEATURES
	array while it calls to_str, Ruby can crash because it can end up inserting
	the string in the wrong index in the array.

	For example, the following script crashes:

	    require "tempfile"

	    class MyString
	      def initialize(path)
	        @path = path
	      end

	      def to_str
	        $LOADED_FEATURES.clear
	        @path
	      end

	      def to_path = @path
	    end

	    def create_ruby_file = Tempfile.create(["test", ".rb"]).path

	    require MyString.new(create_ruby_file)
	    $LOADED_FEATURES.unshift(create_ruby_file)
	    $LOADED_FEATURES << MyString.new(create_ruby_file)
	    require create_ruby_file

	Crash log:

	    test.rb:21: [BUG] Segmentation fault at 0x0000000000000004
	    ruby 3.5.0dev (2025-09-09T09:29:35Z master ce94add7fb) +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:0003 p:---- s:0011 e:000010 CFUNC  :require
	    c:0002 p:0076 s:0006 e:000005 EVAL   test.rb:21 [FINISH]
	    c:0001 p:0000 s:0003 E:0001b0 DUMMY  [FINISH]

	    -- Ruby level backtrace information ----------------------------------------
	    test.rb:21:in '<main>'
	    test.rb:21:in 'require'

	    -- Threading information ---------------------------------------------------
	    Total ractor count: 1
	    Ruby thread count for this ractor: 1

	    -- Machine register context ------------------------------------------------
	      x0: 0x0000000000000004  x1: 0x000000000000c800  x2: 0x0000000000000000
	      x3: 0x0000000000000000  x4: 0x0000000000000205  x5: 0x0000000000000000
	      x6: 0x0000000000000000  x7: 0x0000000000000001 x18: 0x0000000000000000
	    x19: 0x0000000209dfc0b0 x20: 0x0000000209dfc018 x21: 0x000000016ee8ab58
	    x22: 0x0fffffff0009d71d x23: 0x0000000209dfc018 x24: 0x0000000209dfc150
	    x25: 0x000000016ee8acc0 x26: 0x0000000000000000 x27: 0x0000000000000000
	    x28: 0x0000000000000000  lr: 0x0000000101244140  fp: 0x000000016ee887f0
	      sp: 0x000000016ee887d0

	    -- C level backtrace information -------------------------------------------
	    miniruby(rb_print_backtrace+0x24) [0x101317b08] vm_dump.c:843
	    miniruby(rb_print_backtrace) (null):0
	    miniruby(rb_vm_bugreport+0x26c) [0x101317d94] vm_dump.c:1175
	    miniruby(rb_bug_for_fatal_signal+0xa4) [0x10105ddac] error.c:1130
	    miniruby(sig_do_nothing+0x0) [0x1012278c0] signal.c:948
	    miniruby(sigsegv) (null):0
	    /usr/lib/system/libsystem_platform.dylib(_sigtramp+0x38) [0x19c1216a4]
	    miniruby(rb_str_new_frozen+0x1c) [0x101244140] string.c:1495
	    miniruby(rb_check_realpath_internal+0x68) [0x101077804] file.c:4679
	    miniruby(rb_check_realpath+0x2c) [0x101077aa4] file.c:4765
	    miniruby(get_loaded_features_index+0x37c) [0x1010f9c94] load.c:467
	    miniruby(rb_feature_p+0xd0) [0x1010f8174] load.c:582
	    miniruby(search_required+0xac) [0x1010f6ad4] load.c:1193
	    miniruby(require_internal+0x274) [0x1010f7518] load.c:1424
	    miniruby(rb_require_string_internal+0x94) [0x1010f6830] load.c:1571
	    miniruby(rb_require_string+0x58) [0x1010f66e8] load.c:1557
	    miniruby(rb_f_require+0x1c) [0x1010f6684] load.c:1150
	    miniruby(ractor_safe_call_cfunc_1+0x38) [0x101306c28] vm_insnhelper.c:3696
	    miniruby(vm_call_cfunc_with_frame_+0x250) [0x1012f857c] vm_insnhelper.c:3873
	    miniruby(vm_call_cfunc_with_frame+0x6c) [0x1012f8834] vm_insnhelper.c:3919
	    miniruby(vm_sendish+0x1a8) [0x1012c990c] vm_insnhelper.c:6087
	    miniruby(vm_exec_core+0x4050) [0x1012cfb48] insns.def:900
	    miniruby(vm_exec_loop+0x80) [0x1012e5448] vm.c:2666
	    miniruby(rb_vm_exec+0x134) [0x1012c9b40] vm.c:2645
	    miniruby(rb_iseq_eval_main+0x34) [0x1012e5628] vm.c:2919
	    miniruby(rb_ec_exec_node+0xe4) [0x10106d094] eval.c:282
	    miniruby(ruby_run_node+0x94) [0x10106cf64] eval.c:320
	    miniruby(rb_main+0x40) [0x100f7499c] main.c:42
	    miniruby(main+0x60) [0x100f74928] main.c:62
This commit is contained in:
Takashi Kokubun 2025-09-12 14:56:47 -07:00
parent cdd1de636b
commit 00fa53d0ba
3 changed files with 34 additions and 2 deletions

10
load.c
View File

@ -395,7 +395,11 @@ get_loaded_features_index(rb_vm_t *vm)
VALUE previous_realpath_map = rb_hash_dup(realpath_map);
rb_hash_clear(realpaths);
rb_hash_clear(realpath_map);
features = vm->loaded_features;
/* We have to make a copy of features here because the StringValue call
* below could call a Ruby method, which could modify $LOADED_FEATURES
* and cause it to be corrupt. */
features = rb_ary_resurrect(vm->loaded_features);
for (i = 0; i < RARRAY_LEN(features); i++) {
VALUE entry, as_str;
as_str = entry = rb_ary_entry(features, i);
@ -405,6 +409,10 @@ get_loaded_features_index(rb_vm_t *vm)
rb_ary_store(features, i, as_str);
features_index_add(vm, as_str, INT2FIX(i));
}
/* The user modified $LOADED_FEATURES, so we should restore the changes. */
if (!rb_ary_shared_with_p(features, CURRENT_NS_LOADED_FEATURES(vm_ns))) {
rb_ary_replace(CURRENT_NS_LOADED_FEATURES(vm_ns), features);
}
reset_loaded_features_snapshot(vm);
features = rb_ary_dup(vm->loaded_features_snapshot);

View File

@ -840,6 +840,30 @@ class TestRequire < Test::Unit::TestCase
p :ok
end;
}
# [Bug #21567]
assert_separately(%w[-rtempfile], "#{<<~"begin;"}\n#{<<~"end;"}")
begin;
class MyString
def initialize(path)
@path = path
end
def to_str
$LOADED_FEATURES.clear
@path
end
def to_path = @path
end
def create_ruby_file = Tempfile.create(["test", ".rb"]).path
require MyString.new(create_ruby_file)
$LOADED_FEATURES.unshift(create_ruby_file)
$LOADED_FEATURES << MyString.new(create_ruby_file)
require create_ruby_file
end;
end
def test_loading_fifo_threading_raise

View File

@ -11,7 +11,7 @@
# define RUBY_VERSION_MINOR RUBY_API_VERSION_MINOR
#define RUBY_VERSION_TEENY 5
#define RUBY_RELEASE_DATE RUBY_RELEASE_YEAR_STR"-"RUBY_RELEASE_MONTH_STR"-"RUBY_RELEASE_DAY_STR
#define RUBY_PATCHLEVEL 52
#define RUBY_PATCHLEVEL 53
#include "ruby/version.h"
#include "ruby/internal/abi.h"