Project

General

Profile

Actions

Bug #21304

closed

heap-use-after-free of Array#hash via mutating hash method

Added by cyruscyliu (Qiang Liu) 3 days ago. Updated 1 day ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:121807]

Description

Hi, we found a heap-use-after-free of Array#hash via mutating hash method. Here
is the PoC.

class C
    def hash()
        puts $a
        $a.push(*1..100000)
        return 0
    end
end

c = C.new
$a = [c]
$a.push(*1..10)
$a.hash
puts "!"
puts $a

Calling Array#hash on an array containing an object whose hash method mutates
the array causes memory corruption. In this example, C#hash appends a large
number of elements to $a while Array#hash is iterating over it, leading to
inconsistent internal state and potentially 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

$ ./ruby ../triaged/array_hash.rb
`RubyGems' were not loaded.
`error_highlight' was not loaded.
`did_you_mean' was not loaded.
`syntax_suggest' was not loaded.
#<C:0x000071c87a142f30>
1
2
3
4
5
6
7
8
9
10
=================================================================
==105169==ERROR: AddressSanitizer: heap-use-after-free on address 0x511000009c88 at pc 0x601bf620302b bp 0x7ffd48775e70 sp 0x7ffd48775e68
READ of size 8 at 0x511000009c88 thread T0
    #0 0x601bf620302a in rb_ary_hash_values /media/test/ruby/build/../array.c:5334:21
    #1 0x601bf6193b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
    #2 0x601bf617c4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
    #3 0x601bf617bfd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
    #4 0x601bf61421d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
    #5 0x601bf61421d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
    #6 0x601bf613aa47 in rb_vm_exec /media/test/ruby/build/../vm.c
    #7 0x601bf5e0cce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
    #8 0x601bf5e0cce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
    #9 0x601bf5e083a0 in rb_main /media/test/ruby/build/../main.c:42:12
    #10 0x601bf5e083a0 in main /media/test/ruby/build/../main.c:62:12
    #11 0x71c87a629d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16
    #12 0x71c87a629e3f in __libc_start_main csu/../csu/libc-start.c:392:3
    #13 0x601bf5d30d54 in _start (/media/test/ruby/build/ruby+0x148d54) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)

0x511000009c88 is located 8 bytes inside of 216-byte region [0x511000009c80,0x511000009d58)
freed by thread T0 here:
    #0 0x601bf5dcb37c in realloc (/media/test/ruby/build/ruby+0x1e337c) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
    #1 0x601bf5e6dacb in rb_gc_impl_realloc /media/test/ruby/build/../gc/default/default.c:8330:5
    #2 0x601bf5e4afd9 in ruby_sized_xrealloc2_body /media/test/ruby/build/../gc.c:4772:12
    #3 0x601bf5e4afd9 in ruby_sized_xrealloc2 /media/test/ruby/build/../gc.c:4765:34
    #4 0x601bf5e4afd9 in ruby_xrealloc2 /media/test/ruby/build/../gc.c:4778:12
    #5 0x601bf61fad64 in ary_heap_realloc /media/test/ruby/build/../array.c:370:5
    #6 0x601bf61fad64 in ary_resize_capa /media/test/ruby/build/../array.c:412:24
    #7 0x601bf61fa51b in ary_double_capa /media/test/ruby/build/../array.c:461:5
    #8 0x601bf61fa51b in ary_ensure_room_for_push /media/test/ruby/build/../array.c:620:9
    #9 0x601bf6208ba7 in rb_ary_cat /media/test/ruby/build/../array.c:1399:24
    #10 0x601bf6208ba7 in rb_ary_push_m /media/test/ruby/build/../array.c:1426:12
    #11 0x601bf6193b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
    #12 0x601bf61421d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
    #13 0x601bf61421d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
    #14 0x601bf613aa47 in rb_vm_exec /media/test/ruby/build/../vm.c
    #15 0x601bf61a58ed in vm_call0_body /media/test/ruby/build/../vm_eval.c:225:20
    #16 0x601bf61603bb in vm_call0_cc /media/test/ruby/build/../vm_eval.c:101:12
    #17 0x601bf61603bb in rb_funcallv_scope /media/test/ruby/build/../vm_eval.c:1047:16
    #18 0x601bf6168471 in vm_catch_protect /media/test/ruby/build/../vm_eval.c:2612:15
    #19 0x601bf60c98b5 in exec_recursive /media/test/ruby/build/../thread.c:5300:22
    #20 0x601bf5e75fd3 in obj_any_hash /media/test/ruby/build/../hash.c:238:16
    #21 0x601bf5e75cf2 in any_hash /media/test/ruby/build/../hash.c:207:16
    #22 0x601bf5e7621f in rb_hash /media/test/ruby/build/../hash.c:269:21
    #23 0x601bf6202fda in rb_ary_hash_values /media/test/ruby/build/../array.c:5334:13
    #24 0x601bf6193b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
    #25 0x601bf617c4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
    #26 0x601bf617bfd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
    #27 0x601bf61421d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
    #28 0x601bf61421d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
    #29 0x601bf613aa47 in rb_vm_exec /media/test/ruby/build/../vm.c
    #30 0x601bf5e0cce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
    #31 0x601bf5e0cce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
    #32 0x601bf5e083a0 in rb_main /media/test/ruby/build/../main.c:42:12
    #33 0x601bf5e083a0 in main /media/test/ruby/build/../main.c:62:12
    #34 0x71c87a629d8f in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

previously allocated by thread T0 here:
    #0 0x601bf5dcaf8f in malloc (/media/test/ruby/build/ruby+0x1e2f8f) (BuildId: 58c97094e0527fad484552e230da980d80ffa516)
    #1 0x601bf5e6cde5 in rb_gc_impl_malloc /media/test/ruby/build/../gc/default/default.c:8240:5
    #2 0x601bf5e4ac51 in ruby_xmalloc2_body /media/test/ruby/build/../gc.c:4713:12
    #3 0x601bf5e4ac51 in ruby_xmalloc2 /media/test/ruby/build/../gc.c:4707:34
    #4 0x601bf61fae7f in ary_heap_alloc_buffer /media/test/ruby/build/../array.c:351:12
    #5 0x601bf61fae7f in ary_resize_capa /media/test/ruby/build/../array.c:404:26
    #6 0x601bf61fa51b in ary_double_capa /media/test/ruby/build/../array.c:461:5
    #7 0x601bf61fa51b in ary_ensure_room_for_push /media/test/ruby/build/../array.c:620:9
    #8 0x601bf6208ba7 in rb_ary_cat /media/test/ruby/build/../array.c:1399:24
    #9 0x601bf6208ba7 in rb_ary_push_m /media/test/ruby/build/../array.c:1426:12
    #10 0x601bf6193b3b in vm_call_cfunc_with_frame_ /media/test/ruby/build/../vm_insnhelper.c:3797:11
    #11 0x601bf617c4e3 in vm_call_method_each_type /media/test/ruby/build/../vm_insnhelper.c:4775:16
    #12 0x601bf617bfd3 in vm_call_method /media/test/ruby/build/../vm_insnhelper.c
    #13 0x601bf61421d8 in vm_sendish /media/test/ruby/build/../vm_insnhelper.c:5972:15
    #14 0x601bf61421d8 in vm_exec_core /media/test/ruby/build/../insns.def:899:11
    #15 0x601bf613aa47 in rb_vm_exec /media/test/ruby/build/../vm.c
    #16 0x601bf5e0cce0 in rb_ec_exec_node /media/test/ruby/build/../eval.c:281:9
    #17 0x601bf5e0cce0 in ruby_run_node /media/test/ruby/build/../eval.c:319:30
    #18 0x601bf5e083a0 in rb_main /media/test/ruby/build/../main.c:42:12
    #19 0x601bf5e083a0 in main /media/test/ruby/build/../main.c:62:12
    #20 0x71c87a629d8f 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/../array.c:5334:21 in rb_ary_hash_values
Shadow bytes around the buggy address:
  0x511000009a00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x511000009a80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x511000009b00: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x511000009b80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x511000009c00: 00 00 00 00 00 00 fa fa fa fa fa fa fa fa fa fa
=>0x511000009c80: fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x511000009d00: fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa fa
  0x511000009d80: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00
  0x511000009e00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x511000009e80: 00 00 00 00 00 00 00 00 fa fa fa fa fa fa fa fa
  0x511000009f00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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
==105169==ABORTING
../triaged/array_hash.rb:12: [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  :hash
c:0002 p:0044 s:0007 E:000e48 EVAL   ../triaged/array_hash.rb:12 [FINISH]
c:0001 p:0000 s:0003 E:000540 DUMMY  [FINISH]

-- Ruby level backtrace information ----------------------------------------
../triaged/array_hash.rb:12:in '<main>'
../triaged/array_hash.rb:12:in 'hash'

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

-- C level backtrace information -------------------------------------------
./ruby(___interceptor_backtrace) [0x601bf5d75006]
/media/test/ruby/build/ruby(rb_print_backtrace+0x14) [0x601bf64c3337] /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) [0x601bf641776c] /media/test/ruby/build/../error.c:1097
/media/test/ruby/build/ruby(rb_bug_without_die+0x127) [0x601bf6417487] /media/test/ruby/build/../error.c:1106
./ruby(0x601bf5deebc6) [0x601bf5deebc6]
./ruby(0x601bf5dcfc9f) [0x601bf5dcfc9f]
./ruby(0x601bf5dd2ce5) [0x601bf5dd2ce5]
./ruby(__asan_report_load8) [0x601bf5dd3988]
/media/test/ruby/build/ruby(rb_ary_hash_values+0xcb) [0x601bf620302b] /media/test/ruby/build/../array.c:5334
/media/test/ruby/build/ruby(vm_cfp_consistent_p+0x0) [0x601bf6193b3c] ../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) [0x601bf617c4e4] ../vm_insnhelper.c:4775
./ruby(vm_call_method+0x2d4) [0x601bf617bfd4]
/media/test/ruby/build/ruby(vm_sendish+0x1c8) [0x601bf61421d9] ../vm_insnhelper.c:5972
/media/test/ruby/build/ruby(vm_exec_core) ../insns.def:899
./ruby(vm_exec_loop+0x0) [0x601bf613aa48]
/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) [0x601bf5e0cce1] /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) [0x601bf5e083a1] /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) [0x71c87a629d90] ../sysdeps/nptl/libc_start_call_main.h:58
/lib/x86_64-linux-gnu/libc.so.6(call_init+0x0) [0x71c87a629e40] ../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) [0x601bf5d30d55]

-- Other runtime information -----------------------------------------------

* Loaded script: ../triaged/array_hash.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) 3 days ago

Thanks for the clear bug report! https://github.com/ruby/ruby/pull/13250

Actions #2

Updated by nobu (Nobuyoshi Nakada) 1 day ago

  • Status changed from Open to Closed

Applied in changeset commit:git|ce8f7da49e2fea995993b49aa7a26f7640c2e258.


[Bug #21304] Reload length and pointer after #hash method

The receiver can be modified during the method calls.

Actions

Also available in: Atom PDF

Like0
Like0Like0