mruby-io: cap puts recursion depth to prevent C stack overflow

io_puts_ary recursed unconditionally on nested arrays. For cyclic
arrays (a = []; a << a; puts a) or pathologically deep arrays,
this caused a C stack overflow.

Add a depth cap (IO_PUTS_MAX_DEPTH = 16); on overflow, write
"[...]\n" and return, matching CRuby's behavior on cycles. The
pattern mirrors mruby-set's MAX_NESTED_DEPTH for the same problem
shape (pure C recursion not dispatched as a Ruby method).

Reported by OSS-Fuzz (clusterfuzz testcase 6233530857488384).

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Yukihiro "Matz" Matsumoto
2026-05-02 11:27:01 +09:00
parent 8a73faf61e
commit 7dfd560df8
+13 -3
View File
@@ -992,10 +992,20 @@ io_puts_str(mrb_state *mrb, int fd, mrb_value str)
}
}
/* Maximum nesting depth for puts with arrays; guards against cyclic and
pathologically deep arrays causing C stack overflow. */
#define IO_PUTS_MAX_DEPTH 16
/* Recursive helper for puts with arrays */
static void
io_puts_ary(mrb_state *mrb, int fd, mrb_value ary)
io_puts_ary(mrb_state *mrb, int fd, mrb_value ary, int depth)
{
if (depth >= IO_PUTS_MAX_DEPTH) {
mrb_value mark = mrb_str_new_lit(mrb, "[...]\n");
fd_write(mrb, fd, mark);
return;
}
mrb_int len = RARRAY_LEN(ary);
if (len == 0) {
@@ -1008,7 +1018,7 @@ io_puts_ary(mrb_state *mrb, int fd, mrb_value ary)
for (mrb_int i = 0; i < len; i++) {
mrb_value elem = RARRAY_PTR(ary)[i];
if (mrb_array_p(elem)) {
io_puts_ary(mrb, fd, elem); /* Recursive call for nested arrays */
io_puts_ary(mrb, fd, elem, depth + 1);
}
else {
io_puts_str(mrb, fd, elem);
@@ -1040,7 +1050,7 @@ io_puts(mrb_state *mrb, mrb_value io)
for (mrb_int i = 0; i < argc; i++) {
mrb_value arg = argv[i];
if (mrb_array_p(arg)) {
io_puts_ary(mrb, fd, arg);
io_puts_ary(mrb, fd, arg, 0);
}
else {
io_puts_str(mrb, fd, arg);