Root cause
Other / insufficient public mechanism detail introduced
mruby-array-ext: implement Array#fill in C
How AI contributed: Direct introductionPublished Nov 7, 2025 in
mruby/mruby
Ruby-level Array#fill normalized negative indices (beg += self.size if beg < 0); C rewrite ary_fill_exec takes raw start/length: ptr = ARY_PTR(ary) + start, for i<length ptr[i]=obj -> negative start writes before the buffer (OOB write), and start+length can overflow past the resize guard.
Root cause
mruby-array-ext: implement Array#fill in C
Fix
mruby-array-ext: validate start and length in fill operation; fix #6650
AI-assisted fix: Claude Code · Yukihiro "Matz" Matsumoto
Case facts
@@ -209,65 +209,21 @@ def fill(arg0=nil, arg1=nil, arg2=nil, &block) raise ArgumentError, "wrong number of arguments (given 0, expected 1..3)" end - beg = len = 0- if block- if arg0.nil? && arg1.nil? && arg2.nil?- # ary.fill { |index| block } -> ary- beg = 0- len = self.size- elsif !arg0.nil? && arg0.kind_of?(Range)- # ary.fill(range) { |index| block } -> ary- beg = arg0.begin- beg += self.size if beg < 0- len = arg0.end- len += self.size if len < 0- len += 1 unless arg0.exclude_end?- elsif !arg0.nil?- # ary.fill(start [, length] ) { |index| block } -> ary- beg = arg0.__to_int- beg += self.size if beg < 0- if arg1.nil?- len = self.size- else- len = beg + arg1.__to_int- end- end- else- if !arg0.nil? && arg1.nil? && arg2.nil?- # ary.fill(obj) -> ary- beg = 0- len = self.size- elsif !arg0.nil? && !arg1.nil? && arg1.kind_of?(Range)- # ary.fill(obj, range ) -> ary- beg = arg1.begin- beg += self.size if beg < 0- len = arg1.end- len += self.size if len < 0- len += 1 unless arg1.exclude_end?- elsif !arg0.nil? && !arg1.nil?- # ary.fill(obj, start [, length]) -> ary- beg = arg1.__to_int- beg += self.size if beg < 0- if arg2.nil?- len = self.size- else- len = beg + arg2.__to_int- end- end- end+ # Use shared C argument parser for all cases+ start, length = __fill_parse_arg(arg0, arg1, arg2, &block) - i = beg if block- while i < len+ # Block-based filling in Ruby+ i = start+ while i < start + length self[i] = block.call(i) i += 1 end else- while i < len- self[i] = arg0- i += 1- end+ # Use fast C implementation for value filling+ __fill_exec(start, length, arg0) end+ self end @@ -806,6 +806,132 @@ ary_intersect_p(mrb_state *mrb, mrb_value self) return mrb_false_value(); } +/*+ * Shared argument parser for Array#fill that handles all the complex+ * argument parsing logic including ranges, negative indices, etc.+ * Returns normalized start and length values.+ */++static mrb_value+ary_fill_parse_arg(mrb_state *mrb, mrb_value self)+{+ mrb_value arg0 = mrb_nil_value(), arg1 = mrb_nil_value(), arg2 = mrb_nil_value();+ mrb_value block = mrb_nil_value();+ mrb_int argc;++ argc = mrb_get_args(mrb, "|ooo&", &arg0, &arg1, &arg2, &block);++ struct RArray *ary = mrb_ary_ptr(self);+ mrb_int ary_len = ARY_LEN(ary);+ mrb_int start = 0, length = 0;++ if (!mrb_nil_p(block)) {+ if (argc == 0 || (argc >= 1 && mrb_nil_p(arg0))) {+ /* fill { |index| block } */+ start = 0;+ length = ary_len;+ }+ else if (argc >= 1 && mrb_range_p(arg0)) {+ /* fill(range) { |index| block } */+ mrb_int range_beg, range_end;++ if (mrb_range_beg_len(mrb, arg0, &range_beg, &range_end, ary_len, 1)) {+ start = range_beg;+ length = range_end;+ }+ }+ else if (argc >= 1 && !mrb_nil_p(arg0)) {+ /* fill(start [, length]) { |index| block } */+ start = mrb_int(mrb, arg0);+ if (start < 0) start += ary_len;+ if (start < 0) start = 0;++ if (argc == 1 || mrb_nil_p(arg1)) {+ length = ary_len - start;+ }+ else {+ length = mrb_int(mrb, arg1);+ if (length < 0) length = 0;+ }+ }+ }+ else {+ if (argc >= 1 && !mrb_nil_p(arg0)) {+ if (argc == 1 || (argc >= 2 && mrb_nil_p(arg1) && mrb_nil_p(arg2))) {+ /* fill(obj) */+ start = 0;+ length = ary_len;+ }+ else if (argc >= 2 && mrb_range_p(arg1)) {+ /* fill(obj, range) */+ mrb_int range_beg, range_end;++ if (mrb_range_beg_len(mrb, arg1, &range_beg, &range_end, ary_len, 1)) {+ start = range_beg;+ length = range_end;+ }+ }+ else if (argc >= 2 && !mrb_nil_p(arg1)) {+ /* fill(obj, start [, length]) */+ start = mrb_int(mrb, arg1);+ if (start < 0) start += ary_len;+ if (start < 0) start = 0;++ if (argc == 2 || mrb_nil_p(arg2)) {+ length = ary_len - start;+ }+ else {+ length = mrb_int(mrb, arg2);+ if (length < 0) length = 0;+ }+ }+ }+ }++ /* Return [start, length] array */+ mrb_value result = mrb_ary_new_capa(mrb, 2);+ mrb_ary_push(mrb, result, mrb_fixnum_value(start));+ mrb_ary_push(mrb, result, mrb_fixnum_value(length));@@ -395,8 +395,14 @@ def assert_repeated_combination(exp, receiver, *args) assert_equal [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6].fill('x', -2...-2) assert_equal [1, 2, 3, 4, 'x', 6], [1, 2, 3, 4, 5, 6].fill('x', -2..-2) assert_equal [1, 2, 3, 4, 5, 6], [1, 2, 3, 4, 5, 6].fill('x', -2..0)++ # Test extending array+ a = [1, 2]+ assert_equal [1, 2, nil, nil, "x"], a.fill("x", 4, 1) end ++ assert("Array#reverse_each") do a = [ "a", "b", "c", "d" ] b = []@@ -956,6 +956,13 @@ ary_fill_exec(mrb_state *mrb, mrb_value self) mrb_get_args(mrb, "iio", &start, &length, &obj); + if (start < 0) {+ mrb_raise(mrb, E_ARGUMENT_ERROR, "negative start index");+ }+ if (length < 0) {+ mrb_raise(mrb, E_ARGUMENT_ERROR, "negative length");+ }+ struct RArray *ary = mrb_ary_ptr(self); mrb_int ary_len = ARY_LEN(ary); AI-assisted change 00f53a7ee6ef80b2dfbc7635634689f335c508a732968aea75e0d31cc4c286f0 · Fix 9103c6c8378061ff76c2583e22023ed1e5031323ef954fe91d977022736ce5cf