Skip to content

use-after-realloc in mrb_ary_sort_bang #6649

@tim-becker

Description

@tim-becker

Description: In Array#sort! (mrb_ary_sort_bang), the heap sort captures the raw data pointer (variable `a`) once at the start and continues to use it for element accesses even if the user’s comparator block mutates the array (e.g. calls slice!), which can shrink or reallocate the underlying storage. After such a mutation frees or relocates the buffer, the sort routine dereferences the stale pointer and feeds invalid mrb_value entries into cmpnum, which then calls mrb_obj_is_kind_of on garbage data and crashes.

Trigger conditions: Triggered by sorting an array larger than SMALL_ARRAY_SORT_THRESHOLD with a custom block that modifies the array during comparison—specifically, a Ruby fuzzer input `a=(0..99).to_a; a.sort!{|x,y| a.slice!(1,98); (x<y)? -1 : 1 }` causes slice! to shrink/reallocate the array storage while heap sort still uses the old pointer, leading to a use-after-free crash.

Harness name: mruby_fuzzer

Crashing input:

a=(0..99).to_a; a.sort!{|x,y| a.slice!(1,98); (x<y)? -1 : 1 }

Crash output:

+ FUZZER=mruby_fuzzer
+ shift
+ '[' '!' -v TESTCASE ']'
+ TESTCASE=/testcase
+ '[' '!' -f /testcase ']'
+ export RUN_FUZZER_MODE=interactive
+ RUN_FUZZER_MODE=interactive
+ export FUZZING_ENGINE=libfuzzer
+ FUZZING_ENGINE=libfuzzer
+ export SKIP_SEED_CORPUS=1
+ SKIP_SEED_CORPUS=1
+ run_fuzzer mruby_fuzzer /testcase
sysctl: setting key "vm.mmap_rnd_bits", ignoring: Read-only file system
Dictionary: 102 entries
/out/mruby_fuzzer: Running 1 inputs 1 time(s) each.
Running: /testcase
UndefinedBehaviorSanitizer:DEADLYSIGNAL
==43==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address (pc 0x562071c2ae0d bp 0x7ffec38313a0 sp 0x7ffec38312b0 T43)
==43==The signal is caused by a READ memory access.
==43==Hint: this fault was caused by a dereference of a high value address (see register values below).  Disassemble the provided pc to learn which register was used.
Stack Frame #0 in mrb_obj_is_kind_of (/out/mruby_fuzzer+0x5ece0d)
Stack Frame #1 in cmpnum numeric.c
Stack Frame #2 in num_lt numeric.c
Stack Frame #3 in mrb_vm_exec (/out/mruby_fuzzer+0x52b71c)
Stack Frame #4 in mrb_vm_run (/out/mruby_fuzzer+0x5091b5)
Stack Frame #5 in mrb_run vm.c
Stack Frame #6 in yield_with_attr vm.c
Stack Frame #7 in mrb_yield_argv (/out/mruby_fuzzer+0x50e462)
Stack Frame #8 in sort_cmp array.c
Stack Frame #9 in heapify array.c
Stack Frame #10 in mrb_ary_sort_bang array.c
Stack Frame #11 in mrb_vm_exec (/out/mruby_fuzzer+0x52b71c)
Stack Frame #12 in mrb_vm_run (/out/mruby_fuzzer+0x5091b5)
Stack Frame #13 in mrb_top_run (/out/mruby_fuzzer+0x58e618)
Stack Frame #14 in mrb_load_exec (/out/mruby_fuzzer+0x48ab09)
Stack Frame #15 in mrb_load_nstring_cxt (/out/mruby_fuzzer+0x48bacc)
Stack Frame #16 in mrb_load_string_cxt (/out/mruby_fuzzer+0x48bc00)
Stack Frame #17 in mrb_load_string (/out/mruby_fuzzer+0x48bc78)
Stack Frame #18 in LLVMFuzzerTestOneInput (/out/mruby_fuzzer+0x448807)
Stack Frame #19 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:614:13
Stack Frame #20 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:327:6
Stack Frame #21 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:862:9
Stack Frame #22 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
Stack Frame #23 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16
Stack Frame #24 in _start (/out/mruby_fuzzer+0x38e0fd)

DEDUP_TOKEN: mrb_obj_is_kind_of--cmpnum--num_lt--mrb_vm_exec--mrb_vm_run
UndefinedBehaviorSanitizer can not provide additional info.
SUMMARY: UndefinedBehaviorSanitizer: SEGV (/out/mruby_fuzzer+0x5ece0d) in mrb_obj_is_kind_of
==43==ABORTING

/out/mruby_fuzzer -rss_limit_mb=2560 -timeout=25 /testcase -dict=mruby.dict -only_ascii=1 < /dev/null

Patch:

--- a/src/array.c
+++ b/src/array.c
@@ -1887,9 +1887,13 @@
     mrb_int right_index = left_index + 1;
 
     if (left_index < size && sort_cmp(mrb, ary, a, a[left_index], a[max], blk)) {
+      /* Refresh array pointer after potential modification */
+      a = RARRAY_PTR(ary);
       max = left_index;
     }
     if (right_index < size && sort_cmp(mrb, ary, a, a[right_index], a[max], blk)) {
+      /* Refresh array pointer after potential modification */
+      a = RARRAY_PTR(ary);
       max = right_index;
     }
 
@@ -1903,6 +1907,8 @@
     a[max] = a[index];
     a[index] = tmp;
 
+    /* Refresh array pointer for next iteration */
+    a = RARRAY_PTR(ary);
     /* Continue with the affected child subtree */
     index = max;
   }
@@ -1917,6 +1923,8 @@
 
     /* Move elements that are greater than key to one position ahead */
     while (j >= 0 && sort_cmp(mrb, ary, a, a[j], key, blk)) {
+      /* Refresh array pointer after potential modification */
+      a = RARRAY_PTR(ary);
       a[j + 1] = a[j];
       j--;
     }
@@ -1943,23 +1951,33 @@
   ary_modify(mrb, mrb_ary_ptr(ary));
   mrb_get_args(mrb, "&", &blk);
 
-  mrb_value *a = RARRAY_PTR(ary);
-
   /* Algorithm selection based on array size */
   if (n <= SMALL_ARRAY_SORT_THRESHOLD) {
     /* Use insertion sort for small arrays */
+    mrb_value *a = RARRAY_PTR(ary);
     insertion_sort(mrb, ary, a, n, blk);
   }
   else {
     /* Use heap sort for larger arrays */
+    mrb_value *a = RARRAY_PTR(ary);
     for (mrb_int i = n / 2 - 1; i >= 0; i--) {
       heapify(mrb, ary, a, i, n, blk);
+      /* Check if array was modified during heapify */
+      if (RARRAY_LEN(ary) != n) {
+        mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during sort");
+      }
+      a = RARRAY_PTR(ary);
     }
     for (mrb_int i = n - 1; i > 0; i--) {
+      a = RARRAY_PTR(ary);
       mrb_value tmp = a[0];
       a[0] = a[i];
       a[i] = tmp;
       heapify(mrb, ary, a, 0, i, blk);
+      /* Check if array was modified during heapify */
+      if (RARRAY_LEN(ary) != n) {
+        mrb_raise(mrb, E_RUNTIME_ERROR, "array modified during sort");
+      }
     }
   }
   return ary;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions