Skip to content

Avoid the impact of object modifications caused by calls to mrb_vm_exec()#6756

Open
dearblue wants to merge 1 commit intomruby:masterfrom
dearblue:array-ext
Open

Avoid the impact of object modifications caused by calls to mrb_vm_exec()#6756
dearblue wants to merge 1 commit intomruby:masterfrom
dearblue:array-ext

Conversation

@dearblue
Copy link
Contributor

Several methods defined in mruby-array-ext are written in C and may call mrb_vm_exec().
If array objects are modified on the Ruby side, problems may arise in subsequent processing.

  • Using objects that have been removed from the array and garbage collected
  • Using pointers or array lengths that have become invalid due to changes to the array object
  • Modifying the contents of a shared array object directly

ref: #6662


This patch may be excessive, or it may not be comprehensive enough.

I’ve attached two examples of code that reproduce the issue, along with the results.

Use-after-free in objects caused by `Array#assoc`
% cat #6662x1.rb
$list = []
$list[0] = Array.new(8192) { [0, 0] }.last # 単独のオブジェクトヒープページに配置させる
GC.start

key = Object.new
class << key
  def ==(o)
    $list.replace([])
    GC.start
    true
  end
end
$list[0][0] = key

$list.assoc(0).class

% valgrind -- build/host/bin/mruby #6662x1.rb
==70394== Memcheck, a memory error detector
==70394== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==70394== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info
==70394== Command: build/host/bin/mruby #6662x1.rb
==70394==
==70394== Invalid read of size 8
==70394==    at 0x4362BE: mrb_class (class.h:30)
==70394==    by 0x4362BE: mrb_vm_exec (vm.c:2298)
==70394==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==70394==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==70394==    by 0x403CB8: main (mruby.c:354)
==70394==  Address 0x56a1988 is 11,832 bytes inside a block of size 40,992 free'd
==70394==    at 0x48501FC: free (vg_replace_malloc.c:994)
==70394==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==70394==    by 0x415080: mrb_free (gc.c:284)
==70394==    by 0x415080: incremental_sweep_phase (gc.c:1215)
==70394==    by 0x415080: incremental_gc (gc.c:1264)
==70394==    by 0x413501: incremental_gc_finish (gc.c:1280)
==70394==    by 0x413501: mrb_full_gc (gc.c:1377)
==70394==    by 0x414858: gc_start (gc.c:1461)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x43002C: mrb_run (vm.c:3679)
==70394==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==70394==    by 0x420F42: mrb_equal (object.c:101)
==70394==    by 0x48D2FE: ary_assoc (array.c:85)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==70394==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==70394==  Block was alloc'd at
==70394==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==70394==    by 0x413DEB: mrb_realloc_simple (gc.c:221)
==70394==    by 0x413DEB: mrb_realloc (gc.c:235)
==70394==    by 0x413DEB: mrb_malloc (gc.c:251)
==70394==    by 0x413DEB: mrb_calloc (gc.c:270)
==70394==    by 0x413DEB: add_heap (gc.c:358)
==70394==    by 0x41396B: mrb_obj_alloc (gc.c:595)
==70394==    by 0x403FEF: ary_new_capa (array.c:50)
==70394==    by 0x4040A1: ary_new_from_values (array.c:123)
==70394==    by 0x4040A1: mrb_ary_new_from_values (array.c:145)
==70394==    by 0x433009: mrb_vm_exec (vm.c:0)
==70394==    by 0x4318BF: mrb_yield (vm.c:1324)
==70394==    by 0x40896A: mrb_ary_init (array.c:482)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==70394==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==70394==    by 0x403CB8: main (mruby.c:354)
==70394==
==70394== Invalid read of size 8
==70394==    at 0x40E3B9: mrb_class (class.h:0)
==70394==    by 0x40E3B9: mrb_obj_class (class.c:3282)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==70394==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==70394==    by 0x403CB8: main (mruby.c:354)
==70394==  Address 0x56a1988 is 11,832 bytes inside a block of size 40,992 free'd
==70394==    at 0x48501FC: free (vg_replace_malloc.c:994)
==70394==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==70394==    by 0x415080: mrb_free (gc.c:284)
==70394==    by 0x415080: incremental_sweep_phase (gc.c:1215)
==70394==    by 0x415080: incremental_gc (gc.c:1264)
==70394==    by 0x413501: incremental_gc_finish (gc.c:1280)
==70394==    by 0x413501: mrb_full_gc (gc.c:1377)
==70394==    by 0x414858: gc_start (gc.c:1461)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x43002C: mrb_run (vm.c:3679)
==70394==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==70394==    by 0x420F42: mrb_equal (object.c:101)
==70394==    by 0x48D2FE: ary_assoc (array.c:85)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==70394==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==70394==  Block was alloc'd at
==70394==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==70394==    by 0x413DEB: mrb_realloc_simple (gc.c:221)
==70394==    by 0x413DEB: mrb_realloc (gc.c:235)
==70394==    by 0x413DEB: mrb_malloc (gc.c:251)
==70394==    by 0x413DEB: mrb_calloc (gc.c:270)
==70394==    by 0x413DEB: add_heap (gc.c:358)
==70394==    by 0x41396B: mrb_obj_alloc (gc.c:595)
==70394==    by 0x403FEF: ary_new_capa (array.c:50)
==70394==    by 0x4040A1: ary_new_from_values (array.c:123)
==70394==    by 0x4040A1: mrb_ary_new_from_values (array.c:145)
==70394==    by 0x433009: mrb_vm_exec (vm.c:0)
==70394==    by 0x4318BF: mrb_yield (vm.c:1324)
==70394==    by 0x40896A: mrb_ary_init (array.c:482)
==70394==    by 0x436566: mrb_vm_exec (vm.c:0)
==70394==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==70394==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==70394==    by 0x403CB8: main (mruby.c:354)
==70394==
==70394==
==70394== HEAP SUMMARY:
==70394==     in use at exit: 0 bytes in 0 blocks
==70394==   total heap usage: 849 allocs, 849 frees, 634,133 bytes allocated
==70394==
==70394== All heap blocks were freed -- no leaks are possible
==70394==
==70394== For lists of detected and suppressed errors, rerun with: -s
==70394== ERROR SUMMARY: 2 errors from 2 contexts (suppressed: 0 from 0)
Use-after-free in objects caused by `Array#|`
% cat #6662x2.rb
$a = Array.new(40) { Object.new }
$a.each do |e|
  class << e
    def hash = 0
    def eql?(o) = false
  end
end

$b = $a.slice!(20..)

$o = $b[0]
class << $o
  def eql?(o)
    ObjectSpace.each_object(Array) { |e|
      if e.__id__ != $b.__id__ && e[0] == $b[0] && e.size == $b.size
        $b.replace []
        e.replace []
      end
    }
    o = nil
    GC.start
    false
  end
end

$a | $b

% valgrind -- build/host/bin/mruby #6662x2.rb
==88305== Memcheck, a memory error detector
==88305== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al.
==88305== Using Valgrind-3.26.0 and LibVEX; rerun with -h for copyright info
==88305== Command: build/host/bin/mruby #6662x2.rb
==88305==
==88305== Invalid read of size 4
==88305==    at 0x40D9D9: mrb_vm_find_method (class.c:0)
==88305==    by 0x42FF27: mrb_funcall_with_block (vm.c:820)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  Address 0x5666b90 is 0 bytes inside a block of size 24 free'd
==88305==    at 0x48501FC: free (vg_replace_malloc.c:994)
==88305==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==88305==    by 0x40A6F1: mt_free (class.c:171)
==88305==    by 0x40A6F1: mrb_gc_free_mt (class.c:301)
==88305==    by 0x414B3A: obj_free (gc.c:866)
==88305==    by 0x415112: incremental_sweep_phase (gc.c:1188)
==88305==    by 0x415112: incremental_gc (gc.c:1264)
==88305==    by 0x413501: incremental_gc_finish (gc.c:1280)
==88305==    by 0x413501: mrb_full_gc (gc.c:1377)
==88305==    by 0x414858: gc_start (gc.c:1461)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x421BA5: mrb_eql (object.c:867)
==88305==    by 0x48FD44: ary_set_equal_func (array.c:24)
==88305==    by 0x48FD44: kh_get_ary_set (array.c:28)
==88305==    by 0x48FD44: ary_union_body (array.c:604)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==  Block was alloc'd at
==88305==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==88305==    by 0x413603: mrb_realloc_simple (gc.c:221)
==88305==    by 0x413603: mrb_realloc (gc.c:235)
==88305==    by 0x413603: mrb_malloc (gc.c:251)
==88305==    by 0x40B45B: mt_new (class.c:46)
==88305==    by 0x40B45B: mrb_define_method_raw (class.c:1008)
==88305==    by 0x432B44: mrb_vm_exec (vm.c:3529)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==
==88305== Invalid read of size 8
==88305==    at 0x40D9E1: mt_get (class.c:0)
==88305==    by 0x40D9E1: mrb_vm_find_method (class.c:2639)
==88305==    by 0x42FF27: mrb_funcall_with_block (vm.c:820)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  Address 0x5666b98 is 8 bytes inside a block of size 24 free'd
==88305==    at 0x48501FC: free (vg_replace_malloc.c:994)
==88305==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==88305==    by 0x40A6F1: mt_free (class.c:171)
==88305==    by 0x40A6F1: mrb_gc_free_mt (class.c:301)
==88305==    by 0x414B3A: obj_free (gc.c:866)
==88305==    by 0x415112: incremental_sweep_phase (gc.c:1188)
==88305==    by 0x415112: incremental_gc (gc.c:1264)
==88305==    by 0x413501: incremental_gc_finish (gc.c:1280)
==88305==    by 0x413501: mrb_full_gc (gc.c:1377)
==88305==    by 0x414858: gc_start (gc.c:1461)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x421BA5: mrb_eql (object.c:867)
==88305==    by 0x48FD44: ary_set_equal_func (array.c:24)
==88305==    by 0x48FD44: kh_get_ary_set (array.c:28)
==88305==    by 0x48FD44: ary_union_body (array.c:604)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==  Block was alloc'd at
==88305==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==88305==    by 0x413603: mrb_realloc_simple (gc.c:221)
==88305==    by 0x413603: mrb_realloc (gc.c:235)
==88305==    by 0x413603: mrb_malloc (gc.c:251)
==88305==    by 0x40B45B: mt_new (class.c:46)
==88305==    by 0x40B45B: mrb_define_method_raw (class.c:1008)
==88305==    by 0x432B44: mrb_vm_exec (vm.c:3529)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==
==88305== Invalid read of size 4
==88305==    at 0x40D9F0: mt_get (class.c:93)
==88305==    by 0x40D9F0: mrb_vm_find_method (class.c:2639)
==88305==    by 0x42FF27: mrb_funcall_with_block (vm.c:820)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  Address 0x5666bf8 is 8 bytes inside a block of size 128 free'd
==88305==    at 0x48501FC: free (vg_replace_malloc.c:994)
==88305==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==88305==    by 0x40A6E6: mt_free (class.c:170)
==88305==    by 0x40A6E6: mrb_gc_free_mt (class.c:301)
==88305==    by 0x414B3A: obj_free (gc.c:866)
==88305==    by 0x415112: incremental_sweep_phase (gc.c:1188)
==88305==    by 0x415112: incremental_gc (gc.c:1264)
==88305==    by 0x413501: incremental_gc_finish (gc.c:1280)
==88305==    by 0x413501: mrb_full_gc (gc.c:1377)
==88305==    by 0x414858: gc_start (gc.c:1461)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x421BA5: mrb_eql (object.c:867)
==88305==    by 0x48FD44: ary_set_equal_func (array.c:24)
==88305==    by 0x48FD44: kh_get_ary_set (array.c:28)
==88305==    by 0x48FD44: ary_union_body (array.c:604)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==  Block was alloc'd at
==88305==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==88305==    by 0x41357D: mrb_realloc_simple (gc.c:221)
==88305==    by 0x41357D: mrb_realloc (gc.c:235)
==88305==    by 0x40B575: mt_grow (class.c:35)
==88305==    by 0x40B575: mt_put (class.c:0)
==88305==    by 0x40B575: mrb_define_method_raw (class.c:1063)
==88305==    by 0x432B44: mrb_vm_exec (vm.c:3529)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==
==88305== Invalid read of size 4
==88305==    at 0x40DA10: mt_get (class.c:94)
==88305==    by 0x40DA10: mrb_vm_find_method (class.c:2639)
==88305==    by 0x42FF27: mrb_funcall_with_block (vm.c:820)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  Address 0x5666bfc is 12 bytes inside a block of size 128 free'd
==88305==    at 0x48501FC: free (vg_replace_malloc.c:994)
==88305==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==88305==    by 0x40A6E6: mt_free (class.c:170)
==88305==    by 0x40A6E6: mrb_gc_free_mt (class.c:301)
==88305==    by 0x414B3A: obj_free (gc.c:866)
==88305==    by 0x415112: incremental_sweep_phase (gc.c:1188)
==88305==    by 0x415112: incremental_gc (gc.c:1264)
==88305==    by 0x413501: incremental_gc_finish (gc.c:1280)
==88305==    by 0x413501: mrb_full_gc (gc.c:1377)
==88305==    by 0x414858: gc_start (gc.c:1461)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x421BA5: mrb_eql (object.c:867)
==88305==    by 0x48FD44: ary_set_equal_func (array.c:24)
==88305==    by 0x48FD44: kh_get_ary_set (array.c:28)
==88305==    by 0x48FD44: ary_union_body (array.c:604)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==  Block was alloc'd at
==88305==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==88305==    by 0x41357D: mrb_realloc_simple (gc.c:221)
==88305==    by 0x41357D: mrb_realloc (gc.c:235)
==88305==    by 0x40B575: mt_grow (class.c:35)
==88305==    by 0x40B575: mt_put (class.c:0)
==88305==    by 0x40B575: mrb_define_method_raw (class.c:1063)
==88305==    by 0x432B44: mrb_vm_exec (vm.c:3529)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==
==88305== Invalid read of size 8
==88305==    at 0x40DA2F: mt_get (class.c:95)
==88305==    by 0x40DA2F: mrb_vm_find_method (class.c:2639)
==88305==    by 0x42FF27: mrb_funcall_with_block (vm.c:820)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  Address 0x5666bf0 is 0 bytes inside a block of size 128 free'd
==88305==    at 0x48501FC: free (vg_replace_malloc.c:994)
==88305==    by 0x46C0B1: mrb_basic_alloc_func (allocf.c:30)
==88305==    by 0x40A6E6: mt_free (class.c:170)
==88305==    by 0x40A6E6: mrb_gc_free_mt (class.c:301)
==88305==    by 0x414B3A: obj_free (gc.c:866)
==88305==    by 0x415112: incremental_sweep_phase (gc.c:1188)
==88305==    by 0x415112: incremental_gc (gc.c:1264)
==88305==    by 0x413501: incremental_gc_finish (gc.c:1280)
==88305==    by 0x413501: mrb_full_gc (gc.c:1377)
==88305==    by 0x414858: gc_start (gc.c:1461)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x421BA5: mrb_eql (object.c:867)
==88305==    by 0x48FD44: ary_set_equal_func (array.c:24)
==88305==    by 0x48FD44: kh_get_ary_set (array.c:28)
==88305==    by 0x48FD44: ary_union_body (array.c:604)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==  Block was alloc'd at
==88305==    at 0x48532B1: realloc (vg_replace_malloc.c:1810)
==88305==    by 0x41357D: mrb_realloc_simple (gc.c:221)
==88305==    by 0x41357D: mrb_realloc (gc.c:235)
==88305==    by 0x40B575: mt_grow (class.c:35)
==88305==    by 0x40B575: mt_put (class.c:0)
==88305==    by 0x40B575: mrb_define_method_raw (class.c:1063)
==88305==    by 0x432B44: mrb_vm_exec (vm.c:3529)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==
==88305== Invalid read of size 1
==88305==    at 0x431C88: mrb_vm_exec (vm.c:1727)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  Address 0x900104 is not stack'd, malloc'd or (recently) free'd
==88305==
==88305==
==88305== Process terminating with default action of signal 11 (SIGSEGV): dumping core
==88305==  Access not within mapped region at address 0x900104
==88305==    at 0x431C88: mrb_vm_exec (vm.c:1727)
==88305==    by 0x43002C: mrb_run (vm.c:3679)
==88305==    by 0x43002C: mrb_funcall_with_block (vm.c:842)
==88305==    by 0x41666A: mrb_obj_hash_code (hash.c:360)
==88305==    by 0x48CF29: ary_set_hash_func (array.c:18)
==88305==    by 0x48CF29: kh__key_idx_ary_set (array.c:28)
==88305==    by 0x48CF29: kh_put_ary_set (array.c:28)
==88305==    by 0x48FDB2: ary_union_body (array.c:606)
==88305==    by 0x42F5D6: mrb_protect_error (vm.c:532)
==88305==    by 0x48F802: ary_union_internal (array.c:635)
==88305==    by 0x48DCE5: ary_union (array.c:676)
==88305==    by 0x436566: mrb_vm_exec (vm.c:0)
==88305==    by 0x4424D1: mrb_load_exec (parse.y:7797)
==88305==    by 0x4427EF: mrb_load_detect_file_cxt (parse.y:7840)
==88305==    by 0x403CB8: main (mruby.c:354)
==88305==  If you believe this happened as a result of a stack
==88305==  overflow in your program's main thread (unlikely but
==88305==  possible), you can try to increase the size of the
==88305==  main thread stack using the --main-stacksize= flag.
==88305==  The main thread stack size used in this run was 16777216.
==88305==
==88305== HEAP SUMMARY:
==88305==     in use at exit: 119,962 bytes in 902 blocks
==88305==   total heap usage: 1,159 allocs, 257 frees, 397,010 bytes allocated
==88305==
==88305== LEAK SUMMARY:
==88305==    definitely lost: 0 bytes in 0 blocks
==88305==    indirectly lost: 0 bytes in 0 blocks
==88305==      possibly lost: 0 bytes in 0 blocks
==88305==    still reachable: 119,962 bytes in 902 blocks
==88305==         suppressed: 0 bytes in 0 blocks
==88305== Rerun with --leak-check=full to see details of leaked memory
==88305==
==88305== For lists of detected and suppressed errors, rerun with: -s
==88305== ERROR SUMMARY: 6 errors from 6 contexts (suppressed: 0 from 0)

@dearblue dearblue requested a review from matz as a code owner March 22, 2026 13:23
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses critical memory safety vulnerabilities in mruby's array extension methods. By strategically applying garbage collection protection mechanisms and explicitly marking array modifications, it prevents potential use-after-free errors and other undefined behaviors that could occur when C-implemented array operations interact with Ruby-side object modifications or garbage collection cycles. The changes enhance the robustness and stability of array handling within the mruby runtime.

Highlights

  • Memory Safety Improvements: Implemented garbage collection protection using mrb_gc_arena_save and mrb_gc_protect in various array manipulation methods (ary_assoc, ary_rassoc, ary_populate_temp_set, ary_subtract_body, ary_union_body, ary_intersection_body, ary_intersect_p_body, ary_uniq_bang_body, ary_subtract_internal, ary_union_internal, ary_intersection_internal, ary_intersect_p, ary_uniq_bang). This prevents use-after-free and other memory corruption issues that could arise if Ruby-side object modifications or garbage collection occur during C method execution, especially when mrb_equal or hash table operations are involved.
  • Array Modification Signaling: Added mrb_ary_modify calls in ary_fill_exec and ary_uniq_bang to explicitly signal that an array's contents have been altered. This ensures that the mruby runtime is aware of direct C-level modifications to array objects, maintaining internal consistency.
  • Code Simplification: Removed redundant len variables in several array iteration loops, directly using RARRAY_LEN for array length, which streamlines the code.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request addresses critical memory safety issues in array extension methods by preventing object modifications during C-level iterations from causing use-after-free errors or other undefined behavior. The changes consistently apply garbage collection protection to array elements and avoid caching array lengths in loops where Ruby code execution could modify the array. The approach is sound and the fixes are comprehensive. However, I've identified one critical bug in the implementation for ary_intersect_p where an arena restore call is misplaced, which could lead to a use-after-free vulnerability. With that fix, this PR will be a great improvement to the stability of mruby.

Comment on lines 946 to 947
mrb_gc_arena_restore(mrb, ai);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The mrb_gc_arena_restore(mrb, ai) call is inside the inner for loop. This will cause p, which was protected before the outer loop, to become unprotected after the first iteration of this inner loop. If a subsequent call to mrb_equal within the same inner loop triggers a garbage collection, it could lead to a use-after-free on p.

The arena restoration should happen after the inner loop has completed to ensure p remains protected throughout all inner loop iterations.

      }
      mrb_gc_arena_restore(mrb, ai);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops

…xec()`

Several methods defined in mruby-array-ext are written in C and may call `mrb_vm_exec()`.
If array objects are modified on the Ruby side, problems may arise in subsequent processing.

  - Using objects that have been removed from the array and garbage collected
  - Using pointers or array lengths that have become invalid due to changes to the array object
  - Modifying the contents of a shared array object directly

ref: mruby#6662
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant