[stable] backport: cap IO#puts recursion depth on cyclic arrays (mruby-io)#6876
Conversation
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 <[email protected]> (cherry picked from commit 7dfd560)
There was a problem hiding this comment.
Code Review
This pull request introduces a maximum nesting depth limit of 16 for printing nested arrays in io_puts_ary to prevent C stack overflows caused by cyclic or pathologically deep arrays. When the limit is reached, it outputs a placeholder string. The reviewer recommends managing the GC arena using mrb_gc_arena_save and mrb_gc_arena_restore when allocating the temporary placeholder string to prevent potential GC arena overflow during repeated deep recursion.
| if (depth >= IO_PUTS_MAX_DEPTH) { | ||
| mrb_value mark = mrb_str_new_lit(mrb, "[...]\n"); | ||
| fd_write(mrb, fd, mark); | ||
| return; | ||
| } |
There was a problem hiding this comment.
When allocating temporary Ruby objects (like mark via mrb_str_new_lit) inside a recursive or loop-heavy C function, it is highly recommended to manage the GC arena using mrb_gc_arena_save and mrb_gc_arena_restore.
If a large array contains many cyclic or deeply nested arrays, this function will be called repeatedly, leading to many string allocations. Without restoring the arena, this can easily exceed the default GC arena limit (usually 100) and cause an arena overflow or excessive memory consumption.
Wrapping the allocation and write in an arena save/restore block prevents this accumulation.
if (depth >= IO_PUTS_MAX_DEPTH) {
int ai = mrb_gc_arena_save(mrb);
mrb_value mark = mrb_str_new_lit(mrb, "[...]\n");
fd_write(mrb, fd, mark);
mrb_gc_arena_restore(mrb, ai);
return;
}
stable still has the unbounded io_puts_ary recursion, so
a=[]; a<<a; puts a(or any deeply self-nested array) blows the C stack. master fixed it on 2026-05-02 (7dfd560) by capping the depth at IO_PUTS_MAX_DEPTH and printing[...]on overflow, the same way mruby-set already does — but stable never got it.checked it on stable with an ASan build:
a=[]; a<<a; puts aaborts with a stack overflow before the fix, and just prints[...]after cherry-picking 7dfd560. originally an OSS-Fuzz find (clusterfuzz 6233530857488384).plain cherry-pick (-x), no conflicts, original author kept. happy to rebase if you'd rather take it differently.
upstream: 7dfd560df8d0