Bug #21305
closedheap-use-after-free of set#merge via mutating hash method
Description
Hi, we found a heap-use-after-free of set#merge via mutating hash method. Here
is the PoC.
class C
def hash
$a.clear
return 0
end
end
$a = (1..100).to_a
$a.insert(0, C.new)
$b = Set.new([])
$b.merge($a)
Calling Set#merge on a set with an object whose hash method mutates the array
causes memory corruption. In this case, C#hash clears $a while Set#merge is
iterating over it, breaking internal state and potentially leading to a crash.
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
`RubyGems' were not loaded.
`error_highlight' was not loaded.
`did_you_mean' was not loaded.
`syntax_suggest' was not loaded.
=================================================================
==106456==ERROR: AddressSanitizer: heap-use-after-free on address 0x51900000dc88 at pc 0x5c83c02ed8b8 bp 0x7fff8201f1b0 sp 0x7fff8201f1a8
READ of size 8 at 0x51900000dc88 thread T0
#0 0x5c83c02ed8b7 in set_merge_enum_into /media/test/ruby/build/../set.c:1126:13
#1 0x5c83c02eb51f in set_i_merge /media/test/ruby/build/../set.c:1156:9
#2 0x5c83c041eb3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#3 0x5c83c04074e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#4 0x5c83c0406fd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#5 0x5c83c03cd1d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#6 0x5c83c03cd1d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
#7 0x5c83c03c5a47 in rb_vm_exec /media/test/ruby/build/../vm.c
#8 0x5c83c0097ce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
#9 0x5c83c0097ce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
#10 0x5c83c00933a0 in rb_main /media/test/ruby/build/../main.c:42:12
#11 0x5c83c00933a0 in main /media/test/ruby/build/../main.c:62:12
#12 0x72c910229d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
#13 0x72c910229e3f in __libc_start_main csu/../csu/libc-start.c:392:3
#14 0x5c83bffbbd54 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 0x5c83c005637c in realloc (/media/test/ruby/build/ruby+0x1e337c) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
#1 0x5c83c00f8acb in rb_gc_impl_realloc /media/test/ruby/build/../gc/default/default.c:8330:5
#2 0x5c83c00d5fd9 in ruby_sized_xrealloc2_body /media/test/ruby/build/../gc.c:4772:12
#3 0x5c83c00d5fd9 in ruby_sized_xrealloc2 /media/test/ruby/build/../gc.c:4765:34
#4 0x5c83c00d5fd9 in ruby_xrealloc2 /media/test/ruby/build/../gc.c:4778:12
#5 0x5c83c0485d64 in ary_heap_realloc /media/test/ruby/build/../array.c:370:5
#6 0x5c83c0485d64 in ary_resize_capa /media/test/ruby/build/../array.c:412:24
#7 0x5c83c048d663 in rb_ary_clear /media/test/ruby/build/../array.c:4750:13
#8 0x5c83c041eb3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#9 0x5c83c04074e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#10 0x5c83c0406fd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#11 0x5c83c03cd1d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#12 0x5c83c03cd1d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
#13 0x5c83c03c5a47 in rb_vm_exec /media/test/ruby/build/../vm.c
#14 0x5c83c04308ed in vm_call0_body /media/test/ruby/build/../vm_eval.c:225:20
#15 0x5c83c03eb3bb in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#16 0x5c83c03eb3bb in rb_funcallv_scope /media/test/ruby/build/../vm_eval.c:1047:16
#17 0x5c83c03f3471 in vm_catch_protect /media/test/ruby/build/../vm_eval.c:2612:15
#18 0x5c83c03548b5 in exec_recursive /media/test/ruby/build/../thread.c:5300:22
#19 0x5c83c0100fd3 in obj_any_hash /media/test/ruby/build/../hash.c:238:16
#20 0x5c83c0100cf2 in any_hash /media/test/ruby/build/../hash.c:207:16
#21 0x5c83c02e3c42 in set_do_hash /media/test/ruby/build/../st.c:2349:33
#22 0x5c83c02e3c42 in rb_set_insert /media/test/ruby/build/../st.c:2892:18
#23 0x5c83c02ed75b in set_table_insert_wb /media/test/ruby/build/../set.c:378:15
#24 0x5c83c02ed75b in set_merge_enum_into /media/test/ruby/build/../set.c:1126:13
#25 0x5c83c02eb51f in set_i_merge /media/test/ruby/build/../set.c:1156:9
#26 0x5c83c041eb3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#27 0x5c83c04074e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#28 0x5c83c0406fd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#29 0x5c83c03cd1d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#30 0x5c83c03cd1d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
#31 0x5c83c03c5a47 in rb_vm_exec /media/test/ruby/build/../vm.c
#32 0x5c83c0097ce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
#33 0x5c83c0097ce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
#34 0x5c83c00933a0 in rb_main /media/test/ruby/build/../main.c:42:12
#35 0x5c83c00933a0 in main /media/test/ruby/build/../main.c:62:12
#36 0x72c910229d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
previously allocated by thread T0 here:
#0 0x5c83c005637c in realloc (/media/test/ruby/build/ruby+0x1e337c) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
#1 0x5c83c00f8acb in rb_gc_impl_realloc /media/test/ruby/build/../gc/default/default.c:8330:5
#2 0x5c83c00d5fd9 in ruby_sized_xrealloc2_body /media/test/ruby/build/../gc.c:4772:12
#3 0x5c83c00d5fd9 in ruby_sized_xrealloc2 /media/test/ruby/build/../gc.c:4765:34
#4 0x5c83c00d5fd9 in ruby_xrealloc2 /media/test/ruby/build/../gc.c:4778:12
#5 0x5c83c0485d64 in ary_heap_realloc /media/test/ruby/build/../array.c:370:5
#6 0x5c83c0485d64 in ary_resize_capa /media/test/ruby/build/../array.c:412:24
#7 0x5c83c048551b in ary_double_capa /media/test/ruby/build/../array.c:461:5
#8 0x5c83c048551b in ary_ensure_room_for_push /media/test/ruby/build/../array.c:620:9
#9 0x5c83c04850c4 in rb_ary_push /media/test/ruby/build/../array.c:1386:24
#10 0x5c83c0684eb9 in collect_all /media/test/ruby/build/../enum.c:636:5
#11 0x5c83c03c1803 in vm_yield_with_cfunc /media/test/ruby/build/../vm_insnhelper.c:5146:11
#12 0x5c83c04381f8 in invoke_block_from_c_bh /media/test/ruby/build/../vm.c:1667:16
#13 0x5c83c03ed02a in vm_yield_with_cref /media/test/ruby/build/../vm.c:1699:12
#14 0x5c83c03ed02a in vm_yield /media/test/ruby/build/../vm.c:1707:12
#15 0x5c83c03ed02a in rb_yield_0 /media/test/ruby/build/../vm_eval.c:1344:12
#16 0x5c83c03ed02a in rb_yield /media/test/ruby/build/../vm_eval.c
#17 0x5c83c021cd87 in range_each_fixnum_loop /media/test/ruby/build/../range.c:1059:9
#18 0x5c83c021cd87 in range_each /media/test/ruby/build/../range.c:1096:16
#19 0x5c83c0430bb1 in vm_call0_cfunc_with_frame /media/test/ruby/build/../vm_eval.c:164:15
#20 0x5c83c0430bb1 in vm_call0_cfunc /media/test/ruby/build/../vm_eval.c:178:12
#21 0x5c83c0430bb1 in vm_call0_body /media/test/ruby/build/../vm_eval.c:229:15
#22 0x5c83c0434443 in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#23 0x5c83c0434443 in rb_call0 /media/test/ruby/build/../vm_eval.c:554:12
#24 0x5c83c03ee955 in rb_call /media/test/ruby/build/../vm_eval.c:873:12
#25 0x5c83c03ee955 in iterate_method /media/test/ruby/build/../vm_eval.c:1528:12
#26 0x5c83c03eef55 in rb_iterate0 /media/test/ruby/build/../vm_eval.c:1470:18
#27 0x5c83c03ee7c9 in rb_iterate_internal /media/test/ruby/build/../vm_eval.c:1502:12
#28 0x5c83c03ee7c9 in rb_block_call_kw /media/test/ruby/build/../vm_eval.c:1551:12
#29 0x5c83c067fb7b in enum_to_a /media/test/ruby/build/../enum.c:735:5
#30 0x5c83c0430bb1 in vm_call0_cfunc_with_frame /media/test/ruby/build/../vm_eval.c:164:15
#31 0x5c83c0430bb1 in vm_call0_cfunc /media/test/ruby/build/../vm_eval.c:178:12
#32 0x5c83c0430bb1 in vm_call0_body /media/test/ruby/build/../vm_eval.c:229:15
#33 0x5c83c03e9076 in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
#34 0x5c83c03e9076 in rb_vm_call0 /media/test/ruby/build/../vm_eval.c:61:12
#35 0x5c83c03e9076 in rb_vm_call_kw /media/test/ruby/build/../vm_eval.c:326:12
#36 0x5c83c03e9076 in vm_call_super /media/test/ruby/build/../vm_eval.c:350:12
#37 0x5c83c03e9076 in rb_call_super_kw /media/test/ruby/build/../vm_eval.c:358:12
#38 0x5c83c041eb3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
#39 0x5c83c04074e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
#40 0x5c83c0406fd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
#41 0x5c83c03cd1d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
#42 0x5c83c03cd1d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
#43 0x5c83c03c5a47 in rb_vm_exec /media/test/ruby/build/../vm.c
#44 0x5c83c0097ce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
#45 0x5c83c0097ce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
#46 0x5c83c00933a0 in rb_main /media/test/ruby/build/../main.c:42:12
#47 0x5c83c00933a0 in main /media/test/ruby/build/../main.c:62:12
#48 0x72c910229d8f 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:1126:13 in set_merge_enum_into
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
==106456==ABORTING
../triaged/set_merge.rb:11: [BUG] ASAN error
ruby 3.5.0dev (2025-05-02T21:28:25Z master 36c64b3be8) +PRISM [x86_64-linux]
-- Control frame information -----------------------------------------------
c:0003 p:---- s:0011 e:000010 CFUNC :merge
c:0002 p:0060 s:0006 e:000005 EVAL ../triaged/set_merge.rb:11 [FINISH]
c:0001 p:0000 s:0003 E:000540 DUMMY [FINISH]
-- Ruby level backtrace information ----------------------------------------
../triaged/set_merge.rb:11:in '<main>'
../triaged/set_merge.rb:11:in 'merge'
-- Threading information ---------------------------------------------------
Total ractor count: 1
Ruby thread count for this ractor: 1
-- C level backtrace information -------------------------------------------
./ruby(___interceptor_backtrace) [0x5c83c0000006]
/media/test/ruby/build/ruby(rb_print_backtrace+0x14) [0x5c83c074e337] /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) [0x5c83c06a276c] /media/test/ruby/build/../error.c:1097
/media/test/ruby/build/ruby(rb_bug_without_die+0x127) [0x5c83c06a2487] /media/test/ruby/build/../error.c:1106
./ruby(0x5c83c0079bc6) [0x5c83c0079bc6]
./ruby(0x5c83c005ac9f) [0x5c83c005ac9f]
./ruby(0x5c83c005dce5) [0x5c83c005dce5]
./ruby(__asan_report_load8) [0x5c83c005e988]
/media/test/ruby/build/ruby(set_merge_enum_into+0x428) [0x5c83c02ed8b8] /media/test/ruby/build/../set.c:1126
/media/test/ruby/build/ruby(set_i_merge+0xa0) [0x5c83c02eb520] /media/test/ruby/build/../set.c:1156
/media/test/ruby/build/ruby(vm_cfp_consistent_p+0x0) [0x5c83c041eb3c] ../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) [0x5c83c04074e4] ../vm_insnhelper.c:4775
./ruby(vm_call_method+0x2d4) [0x5c83c0406fd4]
/media/test/ruby/build/ruby(vm_sendish+0x1c8) [0x5c83c03cd1d9] ../vm_insnhelper.c:5972
/media/test/ruby/build/ruby(vm_exec_core) ../insns.def:899
./ruby(vm_exec_loop+0x0) [0x5c83c03c5a48]
/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) [0x5c83c0097ce1] /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) [0x5c83c00933a1] /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) [0x72c910229d90] ../sysdeps/nptl/libc_start_call_main.h:58
/lib/x86_64-linux-gnu/libc.so.6(call_init+0x0) [0x72c910229e40] ../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) [0x5c83bffbbd55]
-- Other runtime information -----------------------------------------------
* Loaded script: ../triaged/set_merge.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|be665cf855d7b35ce166ea1137d4f8d0cac1010b.
Handle mutation of array being merged into set
Check length of array during every iteration, as a #hash method
could truncate the array, resulting in heap-use-after-free.
Fixes [Bug #21305]