Skip to content

safe-bash: per-chunk stat+append on redirect-to-file → ~85× heap amplification, fatal OOM within Cloudflare limits #616

Description

@kamilio

Found in safe-bash deep pentest (v3). Verified with executed PoCs against current src/ on Node 22.

Severity: High — threat (d): ~85× heap amplification on redirect-to-file; fatal OOM at 128 MB within all budgets incl. the Cloudflare profile

Summary

The v2-H5 quadratic-CPU redirect write is fixed (41.2 s → 2.6 s via amortized appendOutputBytes/appendFile), but the memory side is new: the incremental() redirect sink does, per chunk: a new Uint8Array(chunk) copy, an fs.stat atEOF probe, a shell-side appendOutputBytes doubling buffer, and fs.appendFile (which copies again). seq emits one chunk per record. Result: ~85× heap-per-output-byte and fatal OOM on a 128 MB isolate even under cloudflareWorkerLimits (3.9 MB output, inside the 4 MB maxOutputBytes cap). Bonus: on remote FS adapters (S3/WebDAV) the atEOF probe is one stat round-trip per output chunk — subrequest/billing amplification (inferred from 400k measured stat calls on memory FS; not executed against a remote adapter).

Location

  • packages/safe-bash/src/shell/runtime.ts:2202-2236incremental() sink
  • packages/safe-bash/src/shell/runtime.ts:2216 — per-chunk fs.stat atEOF probe
  • packages/safe-bash/src/shell/runtime.ts:2225 — shell-side appendOutputBytes doubling buffer
  • packages/safe-bash/src/shell/runtime.ts:2226fs.appendFile per chunk (copies again)

PoC (executed)

await shell.exec("seq 1 400000 > /work/f; wc -c < /work/f");

Measured:

  • N=400000 (2.7 MB output, default limits): exit 0, 2.6 s, 400 001 appendFile + 400 002 stat calls, heap peak 228 MB (≈85× output). N=100000 → 80 MB (linear).
  • --max-old-space-size=128: fatal FATAL ERROR: Ineffective mark-compacts near heap limit.
  • Under cloudflareWorkerLimits: N=250000 (1.7 MB) → 119 MB peak, survives by a hair; N=550000 (3.9 MB — inside the 4 MB cap) → FATAL OOM at 128 MB with all budgets green.

Suggested fix

Cache atEOF state per descriptor after the first successful append-at-EOF (drop the per-chunk stat); batch chunks ≥64 KB before appendFile; drop the shell-side file.data duplicate for sequential descriptors.

Status: NEW (memory side of v2-H5; CPU side verified FIXED, commits #589/#591)

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions