Skip to content

mruby-task: size the proc_set stack from the task's frame - #7308

Merged
matz merged 2 commits into
mruby:masterfrom
harukasan:fix-task-proc-set-deep-frame
Aug 23, 2026
Merged

mruby-task: size the proc_set stack from the task's frame#7308
matz merged 2 commits into
mruby:masterfrom
harukasan:fix-task-proc-set-deep-frame

Conversation

@harukasan

@harukasan harukasan commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

This PR is a follow-up to #7279, fixing two more issues in mrb_task_proc_set().

Problem

1. Out-of-bounds write in suspended tasks

The function determines whether the task's stack needs to grow by comparing the proc's nregs against the total capacity (stend - stbase) instead of the available room above the current frame (ci->stack):

size_t cur = (size_t)(c->stend - c->stbase);
size_t need = (size_t)proc->body.irep->nregs;
if (need > cur) { ... }

A task suspended mid-call-chain holds its frame at an offset from the stack base. When the proc fits the total capacity but the frame offset + proc does not, the extension is skipped, and the resumed task writes past the stack. This is the edge case left open by #7279 (which only covered the reset_context + proc_set path where the frame sits exactly at the stack base).

Additionally, the error path has a related hole. A caller with no jmpbuf gets no re-raise from MRB_ENSURE. Consequently, after a failed stack extension, the function previously went on to install the proc on the stack it just failed to grow, leaving mrb->exc set but unchecked.

2. Invalid memory access via union aliasing and heap envs

The env resize logic below the stack growth blindly reads proc->body.irep->nlocals for any proc. Since body is a union, body.irep holds a C function on a cfunc proc and an mrb_sym on an alias proc. Reading it in these cases feeds MRB_ENV_SET_LEN an invalid value read through the wrong union member.

Furthermore, the final install crashes on an alias proc because CI_PROC_SET refuses aliases. Passing NULL also dies on the flag test before reaching CI_PROC_SET's NULL branch.

Finally, the block widens an env that may no longer be backed by the task's stack. A heap env owns exactly MRB_ENV_LEN slots, so artificially growing its length sends GC marking and the unshare copy past its allocation boundaries.

Fix

  • Stack growth: Compare the required size against the room above the frame using a wrap-safe pointer comparison. An exact fit is allowed, meaning a proc that the frame already holds does not unnecessarily touch the allocator.
  • Error handling: Bail out when the extend fails. This keeps the old proc instead of installing one that the stack cannot hold.
  • Alias resolution: Follow the alias chain to the actual target proc (similar to mrb_proc_arity()), tolerate NULL, and size/install the properly resolved proc.
  • Env resizing: Widen the base env only while it is strictly backed by the task's stack.

Testing

Added a regression test that suspends a task deep in a call chain and then sets a larger proc. Without the fix, it fails with the stack left at 64 slots, which cannot hold the required frame offset of 26 plus 43 registers.

Summary by CodeRabbit

  • Bug Fixes

    • Improved task procedure replacement when procedures are aliased or have different execution types.
    • Prevented invalid installation when task stack expansion fails.
    • Improved stack sizing for procedures installed on deeply nested suspended tasks.
    • Made environment resizing safer during task updates.
  • Tests

    • Added regression coverage for replacing procedures on suspended tasks with deep call stacks.

harukasan and others added 2 commits August 23, 2026 10:24
mrb_task_proc_set() grew the task's stack only when the proc's nregs
exceeded the total capacity, stend - stbase. The proc is installed on
the task's current frame, so the room that matters is above ci->stack.
A task suspended mid-call-chain holds its frame at an offset, and a
proc that fits the capacity but not the frame skipped the extend and
the resumed task wrote past the stack. Compare against the room above
the frame instead, allowing an exact fit so a proc the frame already
holds does not touch the allocator.

Bail out after a failed extend as well. A caller with no jmpbuf gets
no re-raise from MRB_ENSURE, and the function went on to install the
proc on the stack it just failed to grow, with mrb->exc set but
unchecked.

Add a regression test that suspends a task deep in a call chain
before setting a larger proc. The probe task sleeps forever, so both
the C helper and the Ruby test terminate it on every path. Leaving it
suspended would keep a later Task.run from terminating.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The env resize in mrb_task_proc_set() read proc->body.irep->nlocals
for any proc. body.irep aliases body.func for a cfunc proc and an
mrb_sym for an alias proc, so both fed MRB_ENV_SET_LEN a value read
through the wrong union member. The final install crashed on an
alias proc too, CI_PROC_SET refuses aliases, and passing NULL died
on the flag test before reaching CI_PROC_SET's NULL branch.

Follow the alias chain to the real proc the way mrb_proc_arity()
and mrb_proc_eql() do, tolerate NULL, and size and install the
resolved proc. Widen the base env only while it is still backed by
the task's stack. A heap env owns exactly MRB_ENV_LEN slots, so
growing the length would send GC marking and the unshare copy past
its allocation.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@harukasan
harukasan requested a review from matz as a code owner August 23, 2026 01:35
@coderabbitai

coderabbitai Bot commented Aug 23, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 91c93289-ae4d-48d2-809d-57763fb1e02a

📥 Commits

Reviewing files that changed from the base of the PR and between 1c3e2b7 and cd3d68a.

📒 Files selected for processing (3)
  • mrbgems/mruby-task/src/task.c
  • mrbgems/mruby-task/test/proc_set_stack.rb
  • mrbgems/mruby-task/test/tasktest.c

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

mrb_task_proc_set now resolves procedure aliases, handles cfuncs safely, sizes stack growth from the active frame, preserves failures, and resizes on-stack environments. New test support validates deep-frame stack sizing.

Changes

Task procedure replacement

Layer / File(s) Summary
Procedure resolution and stack updates
mrbgems/mruby-task/src/task.c
mrb_task_proc_set resolves aliases, excludes cfuncs from irep handling, grows the stack from the current frame offset, restores VM context after allocation, and installs the procedure only after stack and environment checks pass.
Deep-frame regression coverage
mrbgems/mruby-task/test/tasktest.c, mrbgems/mruby-task/test/proc_set_stack.rb
The test helper measures frame and stack requirements during protected procedure replacement. The regression test validates capacity for a proc installed on a suspended deep frame and cleans up the probe task.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to cd3d6

This localized change addresses task stack sizing, error handling, proc alias resolution, and environment resizing; no actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: matz, hasumikin

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the primary stack-sizing change in mrb_task_proc_set().
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matz
matz merged commit bc9093c into mruby:master Aug 23, 2026
21 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