Skip to content

fix(sed): budget pattern/hold growth against live-intermediate limit - #2698

Merged
chaliy merged 3 commits into
mainfrom
yolop/security-sed-loop-budget
Oct 12, 2026
Merged

chaliy merged 3 commits into
mainfrom
yolop/security-sed-loop-budget

Conversation

@chaliy

@chaliy chaliy commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes the sed loop-budget reset finding: the per-cycle step budget resets on N while N preserves pattern space, so sed -n ':a;N;100~100{h;G};ba' doubles pattern+hold every 100 input lines without tripping the step limit. Growth model: ~13 MB of pattern from ~3 KB of input at 1500 lines, unbounded beyond (419 MB at 2000 lines).

Fix

  • Pattern and hold spaces are now BudgetedStrings charged against the live-intermediate budget before growth (same primitive the output sink already used).
  • Every command charges aggregate work plus cancellation/deadline checkpoints; the segment loop checks them per restart.
  • Growth refusal aborts through the sink error slot (sed: <error>, exit 1) instead of warn-and-continue with a truncated pattern.
  • Same-size rewrites (read-next, s///, y, D drain) drop the old charge first so legit scripts near the limit are not double-counted; restart/step guards keep their input-progress resets, so input-driven loops ($!N;P;D dedup, G;D) stay unbounded.

Before / After

Before (1 MB live cap, PoC): exit 0, amplification succeeds (~13 MB allocated, no refusal) — reproduced by the new test failing pre-fix.
After: refused in ~0.04 s with a bounded diagnostic (live intermediate bytes …), nonzero status, no OOM. Ordinary loops, s///, y, w, r/R output paths all still pass (55 sed integration + 79 lib tests green).

Tests

  • pattern_hold_doubling_stops_at_live_intermediate_budget (PoC regression, written failing first)
  • sed_commands_stop_at_work_budget, sed_lines_stop_at_input_budget (aggregate metering)
  • Unit: pre-cancelled budget refuses (incl. empty program/input edge), live budget runs
  • Mutation battery: removing the budgeted type, the step/input-progress resets, the restart cap, or the per-command work/input charges flips the corresponding test. Growth-error propagation and the explicit checkpoints overlap with the framework fail-closed lease allocator (refusal poisons the shared budget, every growth op re-checks cancel/deadline internally) — verified still bounded under those mutants.

Verification

  • cargo fmt --check clean, cargo clippy -p bashkit --all-targets 0 warnings
  • sed suites green; full workspace cargo test running

Produced by yolop

The per-cycle step budget resets on N while N preserves pattern space,
so :a;N;100~100{h;G};ba doubles pattern+hold every 100 lines without
tripping the step limit (~13 MB from ~3 KB of input at 1500 lines,
unbounded beyond).

Pattern and hold spaces are now BudgetedStrings charged against the
live-intermediate budget before growth; every command charges aggregate
work plus cancellation/deadline checkpoints. Growth refusal aborts via
the sink error slot (exit 1) instead of warn-and-continue.

Legit input-driven loops ($!N;P;D dedup, G;D, s///, y) are unaffected:
same-size rewrites drop the old charge first, and restart/step guards
keep their input-progress resets.

Tests: PoC regression (1 MB cap), per-command work cap, aggregate input
cap, and deterministic cancel/checkpoint unit tests. Mutation battery:
removing the budgeted type, step/input-progress resets, restart cap, or
per-command work/input charges flips the corresponding test; growth-error
propagation and explicit checkpoints overlap with the framework's
fail-closed lease allocator (poison + sink park), verified still bounded.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Oct 12, 2026 •

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
bashkit bcd8ed2 Commit Preview URL

Branch Preview URL
Oct 12 2026, 06:59 AM

The no_debug_fmt_in_builtin_source static scan (CI failure on the
previous push) requires test-only {:?} in builtins to carry a
// debug-ok: reason on the same line. All flagged sites are #[cfg(test)]
assertion details, never user-facing stderr.
- mod.rs maps Machine::new failure to sed exit 1 (fail closed).
- PoC test asserts sed's own status (no pipe) plus a default-budget
  finite control; work/input knob tests cover aggregate metering.
@chaliy
chaliy merged commit d02b159 into main Oct 12, 2026
46 checks passed
@chaliy
chaliy deleted the yolop/security-sed-loop-budget branch October 12, 2026 07:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant