Skip to content

[stable] backport: cap IO#puts recursion depth on cyclic arrays (mruby-io)#6876

Merged
matz merged 1 commit into
mruby:stablefrom
dkgkdfg65:backport/7dfd560d-stable
May 30, 2026
Merged

[stable] backport: cap IO#puts recursion depth on cyclic arrays (mruby-io)#6876
matz merged 1 commit into
mruby:stablefrom
dkgkdfg65:backport/7dfd560d-stable

Conversation

@dkgkdfg65
Copy link
Copy Markdown

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 a aborts 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

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)
@dkgkdfg65 dkgkdfg65 requested a review from matz as a code owner May 30, 2026 12:24
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread mrbgems/mruby-io/src/io.c
Comment on lines +1003 to +1007
if (depth >= IO_PUTS_MAX_DEPTH) {
mrb_value mark = mrb_str_new_lit(mrb, "[...]\n");
fd_write(mrb, fd, mark);
return;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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;
  }

@matz matz merged commit 31ea493 into mruby:stable May 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants