You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(wasm): bound blocking sleep by execution timeout (#2460)
### Motivation
- Non-JS wasm builds used a synchronous spin for `sleep` that could
block the interpreter poll and let attacker-controlled `sleep` calls
bypass the configured `ExecutionLimits::timeout`, enabling bounded CPU
DoS in the no-JS/no-WASI embedding.
### Description
- Cap the non-JS wasm `sleep` at the active execution deadline by
reading `ExecutionDeadline` from execution extensions and passing a
budget-bounded duration to `time_compat::sleep` in
`crates/bashkit/src/builtins/sleep.rs`.
- Make the non-JS `time_compat::timeout` path check the wall-clock
deadline after every interpreter poll (including a poll that returns
`Ready`) so an expired deadline takes precedence over a late Ready
result in `crates/bashkit/src/time_compat/mod.rs`.
- Add a small unit helper and test `effective_sleep_duration` covering
boundary behavior for the cap, and update existing `sleep` unit tests to
cover the change.`
- Update documentation and threat-model artifacts to record the
mitigation (changed wording in `knowledge/runtimes/non-js-wasm.md`,
`knowledge/security/threat-model.md`, and
`crates/bashkit/docs/threat-model.md`).
### Testing
- Ran `cargo fmt --all --check` and `cargo clippy -p bashkit --lib -- -D
warnings`, both succeeded.
- Ran unit tests for the sleep builtin with `cargo test -p bashkit
builtins::sleep::tests --lib`, all tests passed (6 passed).
- Ran the integration test that reproduces the timeout bypass with
`cargo test -p bashkit --test integration direct_sleep_respects_timeout
-- --nocapture`, which passed (1 passed).
- Built the non-JS component path with `RUSTFLAGS='--cfg
getrandom_backend="custom" -D warnings' cargo check --manifest-path
examples/hyperlight/Cargo.toml --target wasm32-unknown-unknown`, which
completed successfully under the configured flags.
- Ran repository checks `just check-okf` and `just check-doc-links`,
both succeeded.
------
[Codex
Task](https://chatgpt.com/codex/cloud/tasks/task_e_6a95fdec65d8832b9d5ab85458af25eb)
| Silent truncation at builtin caps (TM-DOS-109) |`seq 200000`, an awk loop past its cap, or an oversized `sprintf` expression returns incomplete output with exit 0 | Caps report `<cmd>: <what> limit (<N>) exceeded` on stderr and exit non-zero; awk caps and formatting errors are fatal |**MITIGATED**|
113
113
| Silent scalar assignment rejection (TM-DOS-111) | A variable write over the byte or count limit is dropped while the script exits 0 | The first rejected write fails execution with a memory-limit error; a later exec can reuse the session |**MITIGATED**|
Copy file name to clipboardExpand all lines: knowledge/security/threat-model.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -269,7 +269,7 @@ runaway scripts without permanently breaking the session.
269
269
| TM-DOS-054 |`glob --files` inherits ExtGlob blowup | `glob --files "+(a|aa)" /dir` dispatches to `glob_match` with same exponential cost as TM-DOS-031 | Same as TM-DOS-031, `glob_match_impl` recursion-depth cap covers `glob --files` callers |**MITIGATED**|
270
270
| TM-DOS-055 |`split` file count amplification |`split -l 1 bigfile` creates one output file per line; bounded by `max_file_count` FS limit | FS limits (TM-DOS-006) |**MITIGATED**|
271
271
| TM-DOS-056 |`source` self-recursion stack overflow | Script that sources itself recurses unboundedly |`source` shares the function call-depth counter; self-/mutual recursion hits `max_function_depth` with a clean error, not SIGABRT |**MITIGATED**|
272
-
| TM-DOS-057 |`sleep` bypasses execution timeout |`sleep`, `(sleep N)`, `echo x \| sleep N`, `sleep N & wait`, `timeout N sleep N` all ignore `ExecutionLimits::timeout`|`Bash::exec_impl` wraps execution in `time_compat::timeout(limits.timeout, …)` on every target; native uses tokio and JS-host wasm uses `setTimeout` through `gloo-timers`. `sleep`, builtin `timeout`, and tool deadlines use the same portable timer. Synchronous wasm CPU work cannot yield to the host timer, so command, loop, parser-fuel, and memory limits remain its deterministic backstop. Browser regressions: `sleep yields to the host wall clock`, `timeout enforces a host wall-clock deadline`, `options: timeoutMs bounds a pending async builtin`|**MITIGATED**|
272
+
| TM-DOS-057 |`sleep` bypasses execution timeout |`sleep`, `(sleep N)`, `echo x \| sleep N`, `sleep N & wait`, `timeout N sleep N` all ignore `ExecutionLimits::timeout`|`Bash::exec_impl` wraps execution in `time_compat::timeout(limits.timeout, …)` on every target; native uses tokio and JS-host wasm uses `setTimeout` through `gloo-timers`. Non-JS wasm clamps its blocking sleep spin to the shared execution deadline and checks expiry after every interpreter poll, including a ready result. Synchronous wasm CPU work cannot otherwise yield to a host timer, so command, loop, parser-fuel, and memory limits remain its deterministic backstop. Browser regressions: `sleep yields to the host wall clock`, `timeout enforces a host wall-clock deadline`, `options: timeoutMs bounds a pending async builtin`; native unit coverage verifies the non-JS clamp.|**MITIGATED**|
273
273
| TM-DOS-058 | Single-builtin unbounded output |`seq 1 1000000` produces 1M lines despite command limit; single builtin call generates unbounded output (see also #648) |`ExecutionLimits::max_stdout_bytes` and `max_stderr_bytes` truncate captured output (defaults set in `ExecutionLimits::new()`); see #648|**MITIGATED**|
| TM-DOS-060 | Sparse array huge-index allocation |`arr[999999999]=x` could allocate ~1B empty slots if arrays are Vec-backed; negative indices could cause OOB | HashMap-based arrays; `max_array_entries` caps total entries |**MITIGATED**|
0 commit comments