From 923c2e6a7302596e93ab4e0e2cb43d7c48957911 Mon Sep 17 00:00:00 2001 From: David Korczynski Date: Wed, 29 Apr 2026 06:49:03 -0700 Subject: [PATCH] Add new fuzzing harness to be consumed by OSS-Fuzz Adds 6 new fuzzing harnesses to be consumed by OSS-Fuzz. Have confirmed locally this results in significant coverage gains relative to the current code coverage in OSS-Fuzz: https://storage.googleapis.com/oss-fuzz-coverage/mruby/reports/20260427/linux/src/report.html Signed-off-by: David Korczynski --- oss-fuzz/config/mruby.dict | 23 ++++++++ oss-fuzz/mruby_irep_fuzzer.c | 20 +++++++ oss-fuzz/mruby_misc_fuzzer.c | 67 ++++++++++++++++++++++++ oss-fuzz/mruby_numeric_fuzzer.c | 93 +++++++++++++++++++++++++++++++++ oss-fuzz/mruby_pack_fuzzer.c | 37 +++++++++++++ oss-fuzz/mruby_regexp_fuzzer.c | 43 +++++++++++++++ oss-fuzz/mruby_sprintf_fuzzer.c | 39 ++++++++++++++ 7 files changed, 322 insertions(+) create mode 100644 oss-fuzz/mruby_irep_fuzzer.c create mode 100644 oss-fuzz/mruby_misc_fuzzer.c create mode 100644 oss-fuzz/mruby_numeric_fuzzer.c create mode 100644 oss-fuzz/mruby_pack_fuzzer.c create mode 100644 oss-fuzz/mruby_regexp_fuzzer.c create mode 100644 oss-fuzz/mruby_sprintf_fuzzer.c diff --git a/oss-fuzz/config/mruby.dict b/oss-fuzz/config/mruby.dict index a332d3505..7780247b3 100644 --- a/oss-fuzz/config/mruby.dict +++ b/oss-fuzz/config/mruby.dict @@ -103,3 +103,26 @@ snippet_multi=" 1*1" string_single_q=" 'a'" string_dbl_q=" \"a\"" +regex_any="." +regex_start="^" +regex_end="$" +regex_star="*" +regex_plus="+" +regex_maybe="?" +regex_alt="|" +regex_group_start="(" +regex_group_end=")" +regex_class_start="[" +regex_class_end="]" +regex_digit="\\d" +regex_word="\\w" +regex_space="\\s" +regex_non_digit="\\D" +regex_non_word="\\W" +regex_non_space="\\S" +regex_named="(?)" +regex_atomic="(?>)" +regex_lookahead="(?=)" +regex_neg_lookahead="(?!)" +regex_lookbehind="(?<=)" +regex_neg_lookbehind="(? +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 1) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + /* mrb_load_irep_buf returns the result of the last expression */ + mrb_load_irep_buf(mrb, Data, size); + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_misc_fuzzer.c b/oss-fuzz/mruby_misc_fuzzer.c new file mode 100644 index 000000000..7a22e5731 --- /dev/null +++ b/oss-fuzz/mruby_misc_fuzzer.c @@ -0,0 +1,67 @@ +#include +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 2) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + uint8_t selector = Data[0] % 4; + const uint8_t *D = Data + 1; + size_t S = size - 1; + + if (selector == 0) { + /* Target Set */ + struct RClass *set_class_ptr = mrb_class_get(mrb, "Set"); + if (set_class_ptr) { + mrb_value set_class = mrb_obj_value(set_class_ptr); + mrb_value ary = mrb_ary_new(mrb); + for (size_t i = 0; i < S / 4 && i < 10; i++) { + mrb_ary_push(mrb, ary, mrb_str_new(mrb, (const char *)(D + i*4), 4)); + } + mrb_funcall(mrb, set_class, "new", 1, ary); + } + } else if (selector == 1) { + /* Target Time */ + struct RClass *time_class_ptr = mrb_class_get(mrb, "Time"); + if (time_class_ptr) { + mrb_value time_class = mrb_obj_value(time_class_ptr); + if (S >= 8) { + mrb_int sec = (mrb_int)D[0] | ((mrb_int)D[1] << 8) | ((mrb_int)D[2] << 16) | ((mrb_int)D[3] << 24); + mrb_funcall(mrb, time_class, "at", 1, mrb_fixnum_value(sec)); + } + mrb_funcall(mrb, time_class, "now", 0); + } + } else if (selector == 2) { + /* Target Random */ + struct RClass *random_class_ptr = mrb_class_get(mrb, "Random"); + if (random_class_ptr) { + mrb_value random_class = mrb_obj_value(random_class_ptr); + if (S >= 4) { + mrb_int seed = (mrb_int)D[0] | ((mrb_int)D[1] << 8); + mrb_value rnd = mrb_funcall(mrb, random_class, "new", 1, mrb_fixnum_value(seed)); + mrb_funcall(mrb, rnd, "rand", 1, mrb_fixnum_value(100)); + } + } + } else if (selector == 3) { + /* Target Range and Array-ext */ + if (S >= 4) { + mrb_int start = (mrb_int)D[0]; + mrb_int end = (mrb_int)D[1]; + mrb_value range = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Range", 2, mrb_fixnum_value(start), mrb_fixnum_value(end)); + mrb_funcall(mrb, range, "to_a", 0); + } + } + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_numeric_fuzzer.c b/oss-fuzz/mruby_numeric_fuzzer.c new file mode 100644 index 000000000..ac6786425 --- /dev/null +++ b/oss-fuzz/mruby_numeric_fuzzer.c @@ -0,0 +1,93 @@ +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 2) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + uint8_t selector = Data[0] % 4; + const uint8_t *D = Data + 1; + size_t S = size - 1; + + if (selector == 0) { + /* Integer / BigInt */ + if (S < 3) goto done; + uint8_t base = D[0] % 37; + if (base < 2 && base != 0) base = 10; + uint8_t op = D[1] % 5; + uint16_t split = D[2]; // Simplified split + if (S > 3) split = split % (S - 3); + else split = 0; + + mrb_value s1 = mrb_str_new(mrb, (const char *)(D + 3), split); + mrb_value s2 = mrb_str_new(mrb, (const char *)(D + 3 + split), S - 3 - split); + + mrb_value b1 = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Integer", 2, s1, mrb_fixnum_value(base)); + mrb_value b2 = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Integer", 2, s2, mrb_fixnum_value(base)); + + if (!mrb_nil_p(b1) && !mrb_nil_p(b2)) { + switch (op) { + case 0: mrb_funcall(mrb, b1, "+", 1, b2); break; + case 1: mrb_funcall(mrb, b1, "-", 1, b2); break; + case 2: mrb_funcall(mrb, b1, "*", 1, b2); break; + case 3: + if (mrb_test(mrb_funcall(mrb, b2, "!=", 1, mrb_fixnum_value(0)))) { + mrb_funcall(mrb, b1, "/", 1, b2); + } + break; + case 4: + if (mrb_test(mrb_funcall(mrb, b2, "<", 1, mrb_fixnum_value(1000)))) { + mrb_funcall(mrb, b1, "**", 1, b2); + } + break; + } + } + } else if (selector == 1) { + /* Rational */ + if (S < 4) goto done; + mrb_int n = (mrb_int)D[0] | ((mrb_int)D[1] << 8); + mrb_int d = (mrb_int)D[2] | ((mrb_int)D[3] << 8); + mrb_value r = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Rational", 2, mrb_fixnum_value(n), mrb_fixnum_value(d)); + if (S >= 8) { + mrb_int n2 = (mrb_int)D[4] | ((mrb_int)D[5] << 8); + mrb_int d2 = (mrb_int)D[6] | ((mrb_int)D[7] << 8); + mrb_value r2 = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Rational", 2, mrb_fixnum_value(n2), mrb_fixnum_value(d2)); + mrb_funcall(mrb, r, "+", 1, r2); + } + } else if (selector == 2) { + /* Complex */ + if (S < 4) goto done; + mrb_float re = (mrb_float)D[0]; + mrb_float im = (mrb_float)D[1]; + struct RClass *complex_class = mrb_class_get(mrb, "Complex"); + if (complex_class) { + mrb_value c = mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "Complex", 2, mrb_float_value(mrb, re), mrb_float_value(mrb, im)); + mrb_funcall(mrb, c, "abs", 0); + } + } else if (selector == 3) { + /* Math functions */ + if (S < 8) goto done; + double d1; + memcpy(&d1, D, sizeof(double)); + struct RClass *math_module = mrb_module_get(mrb, "Math"); + if (math_module) { + mrb_value m = mrb_obj_value(math_module); + mrb_funcall(mrb, m, "sin", 1, mrb_float_value(mrb, d1)); + mrb_funcall(mrb, m, "log", 1, mrb_float_value(mrb, d1)); + mrb_funcall(mrb, m, "sqrt", 1, mrb_float_value(mrb, d1)); + } + } + +done: + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_pack_fuzzer.c b/oss-fuzz/mruby_pack_fuzzer.c new file mode 100644 index 000000000..9ef58cc26 --- /dev/null +++ b/oss-fuzz/mruby_pack_fuzzer.c @@ -0,0 +1,37 @@ +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 2) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + uint8_t fmt_len = Data[0]; + if (fmt_len > size - 1) { + fmt_len = size - 1; + } + + mrb_value fmt = mrb_str_new(mrb, (const char *)(Data + 1), fmt_len); + mrb_value str = mrb_str_new(mrb, (const char *)(Data + 1 + fmt_len), size - 1 - fmt_len); + + /* Target String#unpack */ + mrb_funcall(mrb, str, "unpack", 1, fmt); + + /* Target Array#pack (using the result of unpack if it's an array) */ + /* Or just pack the original string as an array of bytes */ + mrb_value ary = mrb_ary_new_capa(mrb, size); + for (size_t i = 0; i < size; i++) { + mrb_ary_push(mrb, ary, mrb_fixnum_value(Data[i])); + } + mrb_funcall(mrb, ary, "pack", 1, fmt); + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_regexp_fuzzer.c b/oss-fuzz/mruby_regexp_fuzzer.c new file mode 100644 index 000000000..c7faf8f07 --- /dev/null +++ b/oss-fuzz/mruby_regexp_fuzzer.c @@ -0,0 +1,43 @@ +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 3) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + /* Use first byte for flags */ + uint8_t flags_byte = Data[0]; + mrb_value flags = mrb_fixnum_value(flags_byte & 0x07); // i, m, x flags + + /* Split remaining data into pattern and string */ + uint8_t pattern_len = Data[1]; + if (pattern_len > size - 2) { + pattern_len = size - 2; + } + + mrb_value pattern = mrb_str_new(mrb, (const char *)(Data + 2), pattern_len); + mrb_value text = mrb_str_new(mrb, (const char *)(Data + 2 + pattern_len), size - 2 - pattern_len); + + /* Target Regexp.new(pattern, flags) */ + struct RClass *regexp_class_ptr = mrb_class_get(mrb, "Regexp"); + if (regexp_class_ptr) { + mrb_value regexp_class = mrb_obj_value(regexp_class_ptr); + mrb_value regexp = mrb_funcall(mrb, regexp_class, "new", 2, pattern, flags); + + /* Target Regexp#match(text) */ + if (!mrb_nil_p(regexp)) { + mrb_funcall(mrb, regexp, "match", 1, text); + } + } + + mrb_close(mrb); + return 0; +} diff --git a/oss-fuzz/mruby_sprintf_fuzzer.c b/oss-fuzz/mruby_sprintf_fuzzer.c new file mode 100644 index 000000000..b6b4b701a --- /dev/null +++ b/oss-fuzz/mruby_sprintf_fuzzer.c @@ -0,0 +1,39 @@ +#include +#include +#include +#include +#include +#include + +int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t size) { + if (size < 1) { + return 0; + } + mrb_state *mrb = mrb_open(); + if (!mrb) { + return 0; + } + + /* Use the first byte as a format string length */ + uint8_t fmt_len = Data[0]; + if (fmt_len > size - 1) { + fmt_len = size - 1; + } + + mrb_value fmt = mrb_str_new(mrb, (const char *)(Data + 1), fmt_len); + + /* provide some arguments of different types to satisfy various format specifiers */ + mrb_value args[5]; + args[0] = mrb_fixnum_value(123); + args[1] = mrb_float_value(mrb, 3.14); + args[2] = mrb_str_new_cstr(mrb, "fuzz"); + args[3] = mrb_symbol_value(mrb_intern_cstr(mrb, "symbol")); + args[4] = mrb_ary_new(mrb); + + /* Call sprintf via mrb_funcall */ + /* We don't use all args every time, but it doesn't hurt to pass them */ + mrb_funcall(mrb, mrb_obj_value(mrb->kernel_module), "sprintf", 6, fmt, args[0], args[1], args[2], args[3], args[4]); + + mrb_close(mrb); + return 0; +}