- mruby revision: 93619f0
- build configuration file:
build_config/default.rb
% cat uaf-array-cmp.rb N = 30 u = Object.new a = [u] * N $b = [0] * N class << u def <=>(o) $b.replace([0] * N) 0 end end p a <=> $b % valgrind -- build/host/bin/mruby uaf-array-cmp.rb ==33553== Memcheck, a memory error detector ==33553== Copyright (C) 2002-2024, and GNU GPL'd, by Julian Seward et al. ==33553== Using Valgrind-3.25.1 and LibVEX; rerun with -h for copyright info ==33553== Command: build/host/bin/mruby uaf-array-cmp.rb ==33553== ==33553== Invalid read of size 8 ==33553== at 0x2643F1: mrb_ary_cmp (array.c:1963) ==33553== by 0x2A6019: mrb_vm_exec (vm.c:0) ==33553== by 0x2B0EC1: mrb_load_exec (parse.y:7342) ==33553== by 0x2B11DF: mrb_load_detect_file_cxt (parse.y:7385) ==33553== by 0x260005: main (mruby.c:353) ==33553== Address 0x5643f28 is 8 bytes inside a block of size 240 free'd ==33553== at 0x48502BC: free (vg_replace_malloc.c:993) ==33553== by 0x280DB1: mrb_basic_alloc_func (allocf.c:30) ==33553== by 0x260FE7: ary_replace (array.c:572) ==33553== by 0x264F84: mrb_ary_replace (array.c:610) ==33553== by 0x264F84: mrb_ary_replace_m (array.c:632) ==33553== by 0x2A6019: mrb_vm_exec (vm.c:0) ==33553== by 0x29F539: mrb_run (vm.c:3411) ==33553== by 0x29F539: mrb_funcall_with_block (vm.c:833) ==33553== by 0x28B62D: mrb_cmp (numeric.c:2266) ==33553== by 0x2643FD: mrb_ary_cmp (array.c:1963) ==33553== by 0x2A6019: mrb_vm_exec (vm.c:0) ==33553== by 0x2B0EC1: mrb_load_exec (parse.y:7342) ==33553== by 0x2B11DF: mrb_load_detect_file_cxt (parse.y:7385) ==33553== by 0x260005: main (mruby.c:353) ==33553== Block was alloc'd at ==33553== at 0x4853371: realloc (vg_replace_malloc.c:1807) ==33553== by 0x27E5F3: mrb_realloc_simple (gc.c:202) ==33553== by 0x27E5F3: mrb_realloc (gc.c:216) ==33553== by 0x27E5F3: mrb_malloc (gc.c:232) ==33553== by 0x2604BE: ary_new_capa (array.c:57) ==33553== by 0x26398E: mrb_ary_times (array.c:674) ==33553== by 0x2A6019: mrb_vm_exec (vm.c:0) ==33553== by 0x2B0EC1: mrb_load_exec (parse.y:7342) ==33553== by 0x2B11DF: mrb_load_detect_file_cxt (parse.y:7385) ==33553== by 0x260005: main (mruby.c:353) ==33553== 0 ==33553== ==33553== HEAP SUMMARY: ==33553== in use at exit: 0 bytes in 0 blocks ==33553== total heap usage: 831 allocs, 831 frees, 237,381 bytes allocated ==33553== ==33553== All heap blocks were freed -- no leaks are possible ==33553== ==33553== For lists of detected and suppressed errors, rerun with: -s ==33553== ERROR SUMMARY: 29 errors from 1 contexts (suppressed: 0 from 0)
Problem Description
Outside the for loop, the array's entity address is obtained and referenced within the loop.
However, within the loop, it is possible to call mrb_vm_exec() via mrb_cmp(). By manipulating the array object with Ruby code, the array's entity address can be invalidated.
After that, the address of the array's entity address is not updated within the loop.
As a result, this leads to an invalid memory reference.
04af58d #diff-fb625ea37f59dbae7d0f5e5f7b70b887b9785a354179fab07eb4d2083fdb51b4R1963
Additional Notes
Another recent change also now caches addresses outside the loop.
Similarly, cached addresses are invalidated by calling the indirect mrb_vm_exec() within the loop.
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R120
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R497
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R613
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R623
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R717
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R734
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R846
- 6e01f9d #diff-5d87f447ea7673e4a1d88e4f0dd169b46cc2b09f739cf1994bcdc6fff74f49f6R1028
Additionally, while not part of the most recent changes, I noticed that the section obtaining the array length outside the loop is located near the most recent modifications.
Even when the array length is truncated and no address changes occur, there is a possibility of referencing objects that have been garbage collected.