Description: kset_init_embedded allocates a new combined memory block for keys and flags and assigns it to s->data without freeing any previously allocated memory, so calling Set#__init more than once on the same object leaks the first allocation
Trigger conditions: Invoke Set#__init (which calls kset_init_embedded) multiple times on the same Set object (e.g., s=Set.allocate; s.__init; s.__init) causing the original s->data pointer to be overwritten and leaked.
Crashing input:
s = Set.allocate s.__init # First init to get some memory allocated s.__init # Second init to overwrite pointer without freeing it s.add(1) # Try to use potentially dangling memory
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
=================================================================
==43==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 66 byte(s) in 1 object(s) allocated from:
Stack Frame #0 in realloc /src/llvm-project/compiler-rt/lib/asan/asan_malloc_linux.cpp:82:3
Stack Frame #1 in mrb_basic_alloc_func (/out/mruby_fuzzer+0x2a9955)
Stack Frame #2 in mrb_realloc_simple (/out/mruby_fuzzer+0x41ecc0)
Stack Frame #3 in mrb_realloc (/out/mruby_fuzzer+0x41f417)
Stack Frame #4 in mrb_malloc (/out/mruby_fuzzer+0x41f5c8)
Stack Frame #5 in kset_init_embedded set.c
Stack Frame #6 in set_init set.c
Stack Frame #7 in mrb_vm_exec (/out/mruby_fuzzer+0x2e9297)
Stack Frame #8 in mrb_vm_run (/out/mruby_fuzzer+0x2d18e2)
Stack Frame #9 in mrb_top_run (/out/mruby_fuzzer+0x32d589)
Stack Frame #10 in mrb_load_exec (/out/mruby_fuzzer+0x3c61c5)
Stack Frame #11 in mrb_load_nstring_cxt (/out/mruby_fuzzer+0x3c712c)
Stack Frame #12 in mrb_load_string_cxt (/out/mruby_fuzzer+0x3c7260)
Stack Frame #13 in mrb_load_string (/out/mruby_fuzzer+0x3c72d8)
Stack Frame #14 in LLVMFuzzerTestOneInput (/out/mruby_fuzzer+0x2a7c5b)
Stack Frame #15 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:614:13
Stack Frame #16 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:327:6
Stack Frame #17 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:862:9
Stack Frame #18 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
Stack Frame #19 in __libc_start_main /build/glibc-B3wQXB/glibc-2.31/csu/../csu/libc-start.c:308:16
DEDUP_TOKEN: __interceptor_realloc--mrb_basic_alloc_func--mrb_realloc_simple--mrb_realloc--mrb_malloc
SUMMARY: AddressSanitizer: 66 byte(s) leaked in 1 allocation(s).
/out/mruby_fuzzer -rss_limit_mb=2560 -timeout=25 /testcase -dict=mruby.dict -only_ascii=1 < /dev/null
Patch:
--- a/mrbgems/mruby-set/src/set.c
+++ b/mrbgems/mruby-set/src/set.c
@@ -341,6 +341,11 @@
static void
kset_init_embedded(mrb_state *mrb, kset_t *s)
{
+ /* Free any existing data to prevent memory leak on re-initialization */
+ if (s->data) {
+ mrb_free(mrb, s->data);
+ }
+
kset_int_t size = KSET_DEFAULT_SIZE;
if (size < KSET_MIN_SIZE) {
size = KSET_MIN_SIZE;