Skip to content

fix(interpreter): charge input budget for numbered input redirects - #2696

Open
chaliy wants to merge 4 commits into
mainfrom
yolop/security-input-redirect-budget
Open

chaliy wants to merge 4 commits into
mainfrom
yolop/security-input-redirect-budget

Conversation

@chaliy

@chaliy chaliy commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

What

Every numbered input redirect (N<file, N<<<str, N<&M) retained a full StreamData clone in the redirect list with no budget accounting, and re-redirecting the same fd appended instead of replacing. Each clone now charges consume_input (aggregate) + lease_bytes (live, held for the redirect list lifetime) before the clone/allocation, across all five input branches, and replace_own_fd gives the fd list replace semantics (latest-wins reads unchanged).

Why

Unbounded transient memory: 3</big 4</big ... duplicates the buffer once per fd (plus a second copy inside StreamData's bytes+text pair) with no budget trip.

Before / After

New tests in input_redirect_budget_tests.rs (verified failing-first via stash):

  • numbered_input_redirects_charge_aggregate_input_budget: : 3</big 4</big (2x64B, 100B aggregate cap) failed before (exec succeeded), now errors InputBytes.
  • numbered_input_redirects_hold_live_bytes_budget: same shape against max_live_intermediate_bytes now errors LiveBytes.
  • superseded_numbered_input_fd_is_replaced: cat 3</a 3</b <&3 still prints the second file (behavior guard).

Validation

  • cargo fmt --check, cargo clippy -p bashkit --all-targets: clean
  • 226 related integration tests (redirect/fd/heredoc/stdin/dup/input/budget): pass
  • No {:?} added (TM-INF-022)

Produced by yolop

Each N<file / N<<< / N<&M redirect retained a StreamData clone in the
redirect list with no aggregate or live-bytes accounting, and
re-redirecting the same fd appended instead of replacing. Charge
consume_input + lease_bytes before every clone/retained allocation
across all input branches and replace superseded fd entries.
@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 6f46a4a Commit Preview URL

Branch Preview URL
Oct 12 2026, 06:06 AM

…th buffers

StreamData retains bytes plus decoded text; charge retained_len so the
live/aggregate admission covers the full backing store. Store each
live-bytes lease with its fd entry so replacing a superseded descriptor
releases the old lease while aggregate consumption stays cumulative.

Tests: exact 63/64 and 71/72 boundaries, live release on replace (2x
transient), cumulative-across-replace, all reachable retaining branches,
1024x /dev/zero shape, session recovery, dup-chain parity, 1MiB
heredoc bound.
…p/child bounds

The DupInput-into-stdin clone dropped its live lease while retained. The
stdin slot now carries the clone lease and the setup struct hands it to
callers, who hold it for stdin's lifetime; replacing stdin drops both.

Tests: two-phase DupInput proof (source fits, target trips InputBytes),
ReadWrite trip, stdin-clone 3x64B live overlap (192 ok / 191 LiveBytes)
with recovery, bounded-child original 1024x/dev/zero denial naming
aggregate input bytes with clean exit 1.
Under cargo-llvm-cov the spawned CLI inherits LLVM_PROFILE_FILE and
flushes a multi-MiB profraw at exit, tripping the child's 1MiB -f cap
(SIGXFSZ, wait status 153). Point the child's profile at a controlled
temp sink (coverage stays enabled), raise -f to an explicit 128MiB
artifact allowance with a size receipt plus cleanup, and stop equating
signal death with OOM in assertions.
@chaliy

chaliy commented Oct 12, 2026

Copy link
Copy Markdown
Contributor Author

Required bounded-child proof for current 6f46a4a

The stdin-clone lease handoff, nonvacuous DupInput/overlap tests and exact default-limit original shape are genuine repairs. The SIGXFSZ diagnosis is also more precise than calling every signal OOM. Preserve those improvements.

Before merge, original_shape_bounded_child_default_limits still needs the bounds already requested in the recovery:

  1. The commit/comments advertise an explicit 128 MiB artifact allowance, but the actual launcher is still ulimit -f 2048. Moving LLVM_PROFILE_FILE into a temporary directory does not exempt that file from RLIMIT_FSIZE. Prove the instrumented child's effective limits and the actual artifact size/exit under matching Linux coverage; do not silently increase all limits or disable coverage. Use separate explicitly bounded diagnostic output and coverage-artifact allowance, with OS units/readback recorded accurately.
  2. Polling the child before draining both pipes can block the producer on a full pipe. wait_with_output() after a successful poll and blocking wait() after kill are not a finite total completion proof. A pipe is not an FSIZE output cap; only stdout length is checked, and only after collection. Reuse the reviewed fail-closed bounded helper/equivalent with concurrent capped stdout/stderr, effective CPU/stack/memory/wall receipt and finite owned kill/reap/reader completion. Keep expected raw producer1 distinct from assertion-harness0.
  3. The sink is removed only on the ordinary path; setup/spawn/poll/reap/assertion panics can skip cleanup, metadata failures are silently ignored, and removal errors are discarded. Give this test-owned directory/child guard-before-assert ownership and a truthful artifact/cleanup receipt. No universal production process-tree redesign is requested, and no reproduced hang/leak is claimed from these static test gaps.

Retain the original 1024 /dev/zero/default-budget case and exact aggregate-input denial. Keep unchanged Linux coverage/full CI and finite Bash compatibility/recovery; do not accept signal death, raise stack, skip the test, or waive failed CI. Read this complete comment with every issue/formal/inline review before merge. A fresh green check alone does not resolve missing bounds assertions.

This branch has not been deployed

No deployments
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