Bug #21306
closedheap-use-after-free in set initialization via clearing the array while it’s being iterated
Description
Hi, we found a heap-use-after-free in set initialization
via clearing the array while it’s being iterated. Here is the PoC.
$a = (1..100).to_a
s = Set.new($a) { |x|
$a.clear
}
Initializing a Set with an array that has a block modifying the array causes
memory corruption. In this case, the block clears $a while Set is being
initialized, which can lead to inconsistent internal state and potential
crashes.
To reproduce, compile the recent Ruby with ASAN, and run the PoC.
$ git log | head -n3
commit 36c64b3be83f17992137d63ffd0b94f90e24424a
Author: John Hawthorn <john@hawthorn.email>
Date: Fri Apr 11 16:02:23 2025 -070
./ruby set_initialize.rb
`RubyGems' were not loaded.
`error_highlight' was not loaded.
`did_you_mean' was not loaded.
`syntax_suggest' was not loaded.
=================================================================
==107246==ERROR: AddressSanitizer: heap-use-after-free on address 0x51900000dc88 at pc 0x5de0c6510d40 bp 0x7ffc82400690 sp 0x7ffc82400688
READ of size 8 at 0x51900000dc88 thread T0
#0 0x5de0c6510d3f in set_i_initialize /media/test/ruby/build/../set.c:502:17
#1 0x5de0c6658bb1 in vm_call0_cfunc_with_frame /media/test/ruby/build/../vm_eval.c:164:15
#2 0x5de0c6658bb1 in vm_call0_cfunc /media/test/ruby/build/../vm_eval.c:178:12
#3 0x5de0c6658bb1 in vm_call0_body /media/test/ruby/build/../vm_eval.c:229:15
#4 0x5de0c665c443 in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#5 0x5de0c665c443 in rb_call0 /media/test/ruby/build/../vm_eval.c:554:12
#6 0x5de0c6613825 in rb_call /media/test/ruby/build/../vm_eval.c:873:12
#7 0x5de0c6613825 in rb_funcallv_kw /media/test/ruby/build/../vm_eval.c:1070:12
#8 0x5de0c63edc54 in rb_class_new_instance_pass_kw /media/test/ruby/build/../object.c:2203:5
#9 0x5de0c6646b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#10 0x5de0c662f4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#11 0x5de0c662efd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#12 0x5de0c65f7f98 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#13 0x5de0c65f7f98 in vm_exec_core /media/test/ruby/build/../insns.def:851:11
#14 0x5de0c65eda47 in rb_vm_exec /media/test/ruby/build/../vm.c
#15 0x5de0c62bfce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
#16 0x5de0c62bfce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
#17 0x5de0c62bb3a0 in rb_main /media/test/ruby/build/../main.c:42:12
#18 0x5de0c62bb3a0 in main /media/test/ruby/build/../main.c:62:12
#19 0x77f098629d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#20 0x77f098629e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#21 0x5de0c61e3d54 in _start (/media/test/ruby/build/ruby+0x148d54) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
0x51900000dc88 is located 8 bytes inside of 1024-byte region [0x51900000dc80,0x51900000e080)
freed by thread T0 here:
#0 0x5de0c627e37c in realloc (/media/test/ruby/build/ruby+0x1e337c) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
#1 0x5de0c6320acb in rb_gc_impl_realloc /media/test/ruby/build/../gc/default/default.c:8330:5
#2 0x5de0c62fdfd9 in ruby_sized_xrealloc2_body /media/test/ruby/build/../gc.c:4772:12
#3 0x5de0c62fdfd9 in ruby_sized_xrealloc2 /media/test/ruby/build/../gc.c:4765:34
#4 0x5de0c62fdfd9 in ruby_xrealloc2 /media/test/ruby/build/../gc.c:4778:12
#5 0x5de0c66add64 in ary_heap_realloc /media/test/ruby/build/../array.c:370:5
#6 0x5de0c66add64 in ary_resize_capa /media/test/ruby/build/../array.c:412:24
#7 0x5de0c66b5663 in rb_ary_clear /media/test/ruby/build/../array.c:4750:13
#8 0x5de0c6646b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#9 0x5de0c662f4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#10 0x5de0c662efd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#11 0x5de0c65f51d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#12 0x5de0c65f51d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
#13 0x5de0c65eda47 in rb_vm_exec /media/test/ruby/build/../vm.c
#14 0x5de0c66607b6 in invoke_iseq_block_from_c /media/test/ruby/build/../vm.c:1648:12
#15 0x5de0c66607b6 in invoke_block_from_c_bh /media/test/ruby/build/../vm.c:1662:20
#16 0x5de0c661502a in vm_yield_with_cref /media/test/ruby/build/../vm.c:1699:12
#17 0x5de0c661502a in vm_yield /media/test/ruby/build/../vm.c:1707:12
#18 0x5de0c661502a in rb_yield_0 /media/test/ruby/build/../vm_eval.c:1344:12
#19 0x5de0c661502a in rb_yield /media/test/ruby/build/../vm_eval.c
#20 0x5de0c6510aa4 in set_i_initialize /media/test/ruby/build/../set.c:502:17
#21 0x5de0c6658bb1 in vm_call0_cfunc_with_frame /media/test/ruby/build/../vm_eval.c:164:15
#22 0x5de0c6658bb1 in vm_call0_cfunc /media/test/ruby/build/../vm_eval.c:178:12
#23 0x5de0c6658bb1 in vm_call0_body /media/test/ruby/build/../vm_eval.c:229:15
#24 0x5de0c665c443 in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#25 0x5de0c665c443 in rb_call0 /media/test/ruby/build/../vm_eval.c:554:12
#26 0x5de0c6613825 in rb_call /media/test/ruby/build/../vm_eval.c:873:12
#27 0x5de0c6613825 in rb_funcallv_kw /media/test/ruby/build/../vm_eval.c:1070:12
#28 0x5de0c63edc54 in rb_class_new_instance_pass_kw /media/test/ruby/build/../object.c:2203:5
#29 0x5de0c6646b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#30 0x5de0c662f4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#31 0x5de0c662efd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#32 0x5de0c65f7f98 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#33 0x5de0c65f7f98 in vm_exec_core /media/test/ruby/build/../insns.def:851:11
#34 0x5de0c65eda47 in rb_vm_exec /media/test/ruby/build/../vm.c
#35 0x5de0c62bfce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
#36 0x5de0c62bfce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
#37 0x5de0c62bb3a0 in rb_main /media/test/ruby/build/../main.c:42:12
#38 0x5de0c62bb3a0 in main /media/test/ruby/build/../main.c:62:12
#39 0x77f098629d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
#0 0x5de0c627e37c in realloc (/media/test/ruby/build/ruby+0x1e337c) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
#1 0x5de0c6320acb in rb_gc_impl_realloc /media/test/ruby/build/../gc/default/default.c:8330:5
#2 0x5de0c62fdfd9 in ruby_sized_xrealloc2_body /media/test/ruby/build/../gc.c:4772:12
#3 0x5de0c62fdfd9 in ruby_sized_xrealloc2 /media/test/ruby/build/../gc.c:4765:34
#4 0x5de0c62fdfd9 in ruby_xrealloc2 /media/test/ruby/build/../gc.c:4778:12
#5 0x5de0c66add64 in ary_heap_realloc /media/test/ruby/build/../array.c:370:5
#6 0x5de0c66add64 in ary_resize_capa /media/test/ruby/build/../array.c:412:24
#7 0x5de0c66ad51b in ary_double_capa /media/test/ruby/build/../array.c:461:5
#8 0x5de0c66ad51b in ary_ensure_room_for_push /media/test/ruby/build/../array.c:620:9
#9 0x5de0c66ad0c4 in rb_ary_push /media/test/ruby/build/../array.c:1386:24
#10 0x5de0c68aceb9 in collect_all /media/test/ruby/build/../enum.c:636:5
#11 0x5de0c65e9803 in vm_yield_with_cfunc /media/test/ruby/build/../vm_insnhelper.c:5146:11
#12 0x5de0c66601f8 in invoke_block_from_c_bh /media/test/ruby/build/../vm.c:1667:16
#13 0x5de0c661502a in vm_yield_with_cref /media/test/ruby/build/../vm.c:1699:12
#14 0x5de0c661502a in vm_yield /media/test/ruby/build/../vm.c:1707:12
#15 0x5de0c661502a in rb_yield_0 /media/test/ruby/build/../vm_eval.c:1344:12
#16 0x5de0c661502a in rb_yield /media/test/ruby/build/../vm_eval.c
#17 0x5de0c6444d87 in range_each_fixnum_loop /media/test/ruby/build/../range.c:1059:9
#18 0x5de0c6444d87 in range_each /media/test/ruby/build/../range.c:1096:16
#19 0x5de0c6658bb1 in vm_call0_cfunc_with_frame /media/test/ruby/build/../vm_eval.c:164:15
#20 0x5de0c6658bb1 in vm_call0_cfunc /media/test/ruby/build/../vm_eval.c:178:12
#21 0x5de0c6658bb1 in vm_call0_body /media/test/ruby/build/../vm_eval.c:229:15
#22 0x5de0c665c443 in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#23 0x5de0c665c443 in rb_call0 /media/test/ruby/build/../vm_eval.c:554:12
#24 0x5de0c6616955 in rb_call /media/test/ruby/build/../vm_eval.c:873:12
#25 0x5de0c6616955 in iterate_method /media/test/ruby/build/../vm_eval.c:1528:12
#26 0x5de0c6616f55 in rb_iterate0 /media/test/ruby/build/../vm_eval.c:1470:18
#27 0x5de0c66167c9 in rb_iterate_internal /media/test/ruby/build/../vm_eval.c:1502:12
#28 0x5de0c66167c9 in rb_block_call_kw /media/test/ruby/build/../vm_eval.c:1551:12
#29 0x5de0c68a7b7b in enum_to_a /media/test/ruby/build/../enum.c:735:5
#30 0x5de0c6658bb1 in vm_call0_cfunc_with_frame /media/test/ruby/build/../vm_eval.c:164:15
#31 0x5de0c6658bb1 in vm_call0_cfunc /media/test/ruby/build/../vm_eval.c:178:12
#32 0x5de0c6658bb1 in vm_call0_body /media/test/ruby/build/../vm_eval.c:229:15
#33 0x5de0c6611076 in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#34 0x5de0c6611076 in rb_vm_call0 /media/test/ruby/build/../vm_eval.c:61:12
#35 0x5de0c6611076 in rb_vm_call_kw /media/test/ruby/build/../vm_eval.c:326:12
#36 0x5de0c6611076 in vm_call_super /media/test/ruby/build/../vm_eval.c:350:12
#37 0x5de0c6611076 in rb_call_super_kw /media/test/ruby/build/../vm_eval.c:358:12
#38 0x5de0c6646b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#39 0x5de0c662f4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#40 0x5de0c662efd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#41 0x5de0c65f51d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#42 0x5de0c65f51d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
#43 0x5de0c65eda47 in rb_vm_exec /media/test/ruby/build/../vm.c
#44 0x5de0c62bfce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
#45 0x5de0c62bfce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
#46 0x5de0c62bb3a0 in rb_main /media/test/ruby/build/../main.c:42:12
#47 0x5de0c62bb3a0 in main /media/test/ruby/build/../main.c:62:12
#48 0x77f098629d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
SUMMARY: AddressSanitizer: heap-use-after-free /media/test/ruby/build/../set.c:502:17 in set_i_initialize
Shadow bytes around the buggy address:
0x51900000da00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51900000da80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x51900000db00: 00 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa
0x51900000db80: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x51900000dc00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x51900000dc80: fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900000dd00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900000dd80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900000de00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900000de80: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
0x51900000df00: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==107246==ABORTING
../triaged/set_initialize.rb:3: [BUG] ASAN error
ruby 3.5.0dev (2025-05-02T21:28:25Z master 36c64b3be8) +PRISM [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0004 p:---- s:0015 e:000014 CFUNC :initialize
c:0003 p:---- s:0012 e:000011 CFUNC :new
c:0002 p:0013 s:0007 E:000c48 EVAL ../triaged/set_initialize.rb:3 [FINISH]
c:0001 p:0000 s:0003 E:000540 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
../triaged/set_initialize.rb:3:in '<main>'
../triaged/set_initialize.rb:3:in 'new'
../triaged/set_initialize.rb:3:in 'initialize'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
./ruby(___interceptor_backtrace) [0x5de0c6228006]
/media/test/ruby/build/ruby(rb_print_backtrace+0x14) [0x5de0c6976337] /media/test/ruby/build/../vm_dump.c:839
/media/test/ruby/build/ruby(rb_vm_bugreport) /media/test/ruby/build/../vm_dump.c:1171
/media/test/ruby/build/ruby(rb_bug_without_die_internal+0x23c) [0x5de0c68ca76c] /media/test/ruby/build/../error.c:1097
/media/test/ruby/build/ruby(rb_bug_without_die+0x127) [0x5de0c68ca487] /media/test/ruby/build/../error.c:1106
./ruby(0x5de0c62a1bc6) [0x5de0c62a1bc6]
./ruby(0x5de0c6282c9f) [0x5de0c6282c9f]
./ruby(0x5de0c6285ce5) [0x5de0c6285ce5]
./ruby(__asan_report_load8) [0x5de0c6286988]
/media/test/ruby/build/ruby(set_i_initialize+0x4c0) [0x5de0c6510d40] /media/test/ruby/build/../set.c:502
/media/test/ruby/build/ruby(vm_call0_cfunc_with_frame+0x280) [0x5de0c6658bb2] ../vm_eval.c:164
/media/test/ruby/build/ruby(vm_call0_cfunc) ../vm_eval.c:178
/media/test/ruby/build/ruby(vm_call0_body) ../vm_eval.c:229
/media/test/ruby/build/ruby(vm_call0_cc+0x141) [0x5de0c665c444] ../vm_eval.c:101
/media/test/ruby/build/ruby(rb_call0) ../vm_eval.c:554
/media/test/ruby/build/ruby(rb_funcallv_kw+0x86) [0x5de0c6613826] ../vm_eval.c:873
/media/test/ruby/build/ruby(rb_class_new_instance_pass_kw+0x35) [0x5de0c63edc55] /media/test/ruby/build/../object.c:2203
/media/test/ruby/build/ruby(vm_cfp_consistent_p+0x0) [0x5de0c6646b3c] ../vm_insnhelper.c:3797
/media/test/ruby/build/ruby(vm_call_cfunc_with_frame_) ../vm_insnhelper.c:3799
/media/test/ruby/build/ruby(vm_call_method_each_type+0x264) [0x5de0c662f4e4] ../vm_insnhelper.c:4775
./ruby(vm_call_method+0x2d4) [0x5de0c662efd4]
/media/test/ruby/build/ruby(vm_sendish+0x10e) [0x5de0c65f7f99] ../vm_insnhelper.c:5972
/media/test/ruby/build/ruby(vm_exec_core) ../insns.def:851
./ruby(vm_exec_loop+0x0) [0x5de0c65eda48]
/media/test/ruby/build/ruby(rb_vm_exec) /media/test/ruby/build/../vm.c:2621
/media/test/ruby/build/ruby(rb_ec_exec_node+0x53) [0x5de0c62bfce1] /media/test/ruby/build/../eval.c:281
/media/test/ruby/build/ruby(ruby_run_node) /media/test/ruby/build/../eval.c:319
/media/test/ruby/build/ruby(rb_main+0x29) [0x5de0c62bb3a1] /media/test/ruby/build/../main.c:42
/media/test/ruby/build/ruby(main) /media/test/ruby/build/../main.c:62
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_call_main+0x80) [0x77f098629d90] ../sysdeps/nptl/libc_start_call_main.h:58
/lib/x86_64-linux-gnu/libc.so.6(call_init+0x0) [0x77f098629e40] ../csu/libc-start.c:392
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main_impl) ../csu/libc-start.c:379
/lib/x86_64-linux-gnu/libc.so.6(__libc_start_main) (null):0
./ruby(_start) [0x5de0c61e3d55]
-- Other runtime information -----------------------------------------------
* Loaded script: ../triaged/set_initialize.rb
* Loaded features:
0 enumerator.so
1 thread.rb
2 fiber.so
3 rational.so
4 complex.so
5 ruby2_keywords.rb
6 set.rb
Updated by mame (Yusuke Endoh) 2 days ago
- Assignee set to jeremyevans0 (Jeremy Evans)
Updated by jeremyevans0 (Jeremy Evans) 2 days ago
Potential fix in https://github.com/ruby/ruby/pull/13253
I'm not sure ASAN is supported on OpenBSD, so I'm not sure I can test with ASAN. If you could test to confirm the fix, I would appreciate it.
Updated by jeremyevans (Jeremy Evans) 2 days ago
- Status changed from Open to Closed
Applied in changeset git|21035c826db5933cf836a4a12fb74b696a76b255.
Handle mutating of array passed to Set.new during iteration
This avoids a heap-use-after-free.
Fixes [Bug #21306]