Skip to content

fix(wasm): bound blocking sleep by execution timeout - #2460

Merged
chaliy merged 2 commits into
mainfrom
2026-09-25-propose-fix-for-wasm-sleep-vulnerability
Sep 25, 2026
Merged

chaliy merged 2 commits into
mainfrom
2026-09-25-propose-fix-for-wasm-sleep-vulnerability

Conversation

@chaliy

@chaliy chaliy commented Sep 25, 2026

Copy link
Copy Markdown
Contributor

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

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Sep 25, 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 cfdf325 Commit Preview URL Sep 25 2026, 10:32 AM

The clamp only compiles for wasm32-unknown-unknown without wasm_js, so the
wasmtime tier cannot reach it. Say what covers it today and what an e2e case
would need, instead of leaving the gap implicit.

Claude-Session: https://claude.ai/code/session_01MJBT5na4uL5yZZXwwH1FMy
@chaliy
chaliy force-pushed the 2026-09-25-propose-fix-for-wasm-sleep-vulnerability branch from a203fb2 to cfdf325 Compare September 25, 2026 10:31

chaliy commented Sep 25, 2026

Copy link
Copy Markdown
Contributor Author

Reviewed and rebased onto latest main. Looks correct, and the scoping is the important part — I checked it specifically.

Blast radius is genuinely limited to non-JS wasm

The time_compat::timeout change makes an expired deadline win over a Poll::Ready, which would be a real semantic change if it applied broadly: a future completing exactly at the deadline gets its result discarded. Confirmed it sits inside #[cfg(all(target_arch = "wasm32", target_os = "unknown", not(feature = "wasm_js")))]. The native/tokio path and the wasm_js/gloo-timers path are untouched, so nothing on the shipped targets changes.

In that one world the precedence is necessary rather than merely defensible: the timer is a synchronous spin, so without it sleep 60 under a 1 s timeout spins the full 60 s and then returns Ready, reporting success — the bypass this PR is about. The sleep clamp handles the common case and the timeout check backstops any other non-yielding operation.

Coverage boundary, documented rather than left implicit

The clamp only compiles for this target, so the wasmtime tier in examples/hyperlight/build-and-run.sh can't reach it — the unit test covers Duration::min arithmetic, and the wiring is compile-checked. That's the same situation as the inline host_call driver, which knowledge/runtimes/non-js-wasm.md already calls out, so I added a matching note in its Testing section saying what covers the clamp today and what an e2e case would need (the example host taking a configurable timeout — the default 30 s deadline makes the observable case too slow for CI as things stand).

Not asking for that here: this target is an unpublished experiment, and 30 s of spin per CI run isn't a good trade for it.

Validation

  • cargo test -p bashkit --lib builtins::sleep — 6 passed, including non_js_wasm_sleep_is_capped_by_execution_budget
  • cargo fmt --check clean; just check-okf conformant
  • rebased onto main @ 1308956

Generated by Claude Code

@chaliy
chaliy merged commit 0bac9a8 into main Sep 25, 2026
48 checks passed
@chaliy
chaliy deleted the 2026-09-25-propose-fix-for-wasm-sleep-vulnerability branch September 25, 2026 16:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant