Skip to content

Commit 50e6d54

Browse files
committed
Verify Wasm host callbacks over guest inputs
1 parent bd1904d commit 50e6d54

9 files changed

Lines changed: 59 additions & 23 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ behavior when the change improves security or project direction.
4747
- Add crate-level Wasm resource ceilings, checked execution deadlines,
4848
semaphore-capacity validation, fallible compile-worker creation, and
4949
before/after deadline checks for the finite non-blocking host-call boundary.
50+
- Make panic-free, total-over-`i32` behavior part of the native Wasm host-call
51+
contract and property-test every current guest-ID decoder.
5052

5153
## 1.7.7 - 2026-07-10
5254

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/fluxheim-server/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ rustix = { version = "1.1.4", features = ["fs", "net", "process"] }
8080

8181
[dev-dependencies]
8282
flate2 = "1.1.5"
83+
proptest = "1.11.0"
8384
rcgen = { version = "0.14.8", default-features = false, features = ["pem", "ring"] }
8485
tempfile = "3.27.0"
8586
wat = "1.253.0"

crates/fluxheim-server/src/native_http1_route_proxy_tests/wasm.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,15 @@ async fn native_wasm_wasi_stdio_import_remains_denied_with_other_grants() {
213213
async fn native_wasm_access_decision_fails_closed_on_timeout() {
214214
let fixture = WasmRouteFixture::new(&[("busy", WasmPluginBody::BusyLoop)]);
215215
let upstream = super::upstream_expect_path("/never", "unexpected").await;
216-
let router = NativeHttp1HostRouter::from_config(
217-
&fixture.config_with_attachments(upstream, vec![wasm_attachment("busy", "route", 100)]),
218-
DownstreamHttp1Policy::default(),
219-
0,
220-
)
221-
.unwrap();
216+
let mut config =
217+
fixture.config_with_attachments(upstream, vec![wasm_attachment("busy", "route", 100)]);
218+
config.wasm.plugins[0]
219+
.limits
220+
.as_mut()
221+
.expect("busy-loop test plugin has explicit limits")
222+
.timeout_ms = 5;
223+
let router =
224+
NativeHttp1HostRouter::from_config(&config, DownstreamHttp1Policy::default(), 0).unwrap();
222225
let proxy = router_listener(router).await;
223226

224227
let response = downstream_get(proxy, "/route").await;
@@ -2245,8 +2248,8 @@ fn wasm_plugin(root: &Path, name: &str, body: WasmPluginBody) -> fluxheim_config
22452248
WasmPluginBody::BusyLoop | WasmPluginBody::CacheLookupBusy
22462249
) {
22472250
Some(fluxheim_config::WasmSandboxLimitsConfig {
2248-
fuel: 1_000_000_000,
2249-
timeout_ms: 150,
2251+
fuel: fluxheim_config::config_wasm::MAX_WASM_FUEL,
2252+
timeout_ms: 50,
22502253
compile_timeout_ms: 5_000,
22512254
..Default::default()
22522255
})

crates/fluxheim-server/src/native_http1_route_wasm.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1892,6 +1892,20 @@ impl NativeWasmAdmissionScope {
18921892
#[cfg(test)]
18931893
mod tests {
18941894
use super::*;
1895+
use proptest::prelude::*;
1896+
1897+
proptest! {
1898+
#[test]
1899+
fn wasm_guest_id_decoders_are_total(first in any::<i32>(), second in any::<i32>()) {
1900+
let _ = wasm_request_header_mutation(first, second);
1901+
let _ = wasm_response_header_mutation(first, second);
1902+
let _ = wasm_response_removable_header(first);
1903+
let _ = wasm_cache_key_component(first, second);
1904+
let _ = wasm_cache_ttl(first);
1905+
let _ = wasm_cache_tag(first);
1906+
let _ = wasm_cache_store_header(first, second);
1907+
}
1908+
}
18951909

18961910
#[test]
18971911
fn proxy_preview_namespace_does_not_receive_native_phase_functions() {

crates/fluxheim-wasm/src/runtime.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,12 @@ pub struct FluxWasmCompiledModuleIdentity {
4949
#[derive(Clone)]
5050
/// Synchronous native host callback for Fluxheim's bounded policy ABI.
5151
///
52-
/// Callbacks must be finite, non-blocking, and free of I/O, sleeps, IPC, and
52+
/// Callbacks must be finite, non-blocking, panic-free, and total over every
53+
/// possible `i32` input. They must use checked arithmetic and bounds-checked
54+
/// access and remain free of I/O, sleeps, IPC, assertion-based APIs, and
5355
/// contended lock acquisition. Wasmtime epoch interruption cannot preempt
54-
/// native Rust while a callback is running.
56+
/// native Rust while a callback is running, and Fluxheim releases abort on
57+
/// panic rather than unwind.
5558
pub struct WasmI32HostFunction {
5659
module: &'static str,
5760
name: &'static str,

docs/versioning-plan.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3167,11 +3167,14 @@ Stable scope:
31673167
families must run through `scripts/test_starter.py`, the stable/deep release
31683168
gates must include the appropriate Wasm checks, and the docs must clearly
31693169
describe supported capability parity and unsupported syntax/runtime parity.
3170-
Keep all in-process native host callbacks finite and non-blocking. Before any
3171-
host-call capability introduces blocking I/O, IPC, sleeps, contended waits,
3170+
Keep all in-process native host callbacks finite, non-blocking, panic-free,
3171+
and total over arbitrary guest inputs, with property tests for every ID
3172+
decoder. Before any host-call capability introduces blocking I/O, IPC, sleeps,
3173+
contended waits, assertion-based operations, unchecked indexing/arithmetic,
31723174
or third-party native callback code, design and prove a killable subprocess
31733175
runner with bounded authenticated IPC, process admission, timeout termination,
3174-
and crash cleanup; do not represent thread timeouts as hard preemption.
3176+
and crash cleanup; do not represent thread timeouts or `catch_unwind` as hard
3177+
isolation in an abort-on-panic release.
31753178
- `v1.7.11`: zero-downtime upgrade planning and first implementation slice
31763179
after the Wasm line is stable. Add a documented design for native binary and
31773180
Podman deployments that can swap Fluxheim versions without a listener gap:

docs/wasm-extensibility.md

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,14 +152,18 @@ before semaphore construction, and execution deadlines use checked arithmetic.
152152

153153
Native host callbacks execute synchronously while Wasmtime is inside native
154154
Rust and therefore cannot be forcibly preempted by epoch interruption. Every
155-
current Fluxheim callback is a finite in-memory operation; callbacks that
156-
perform filesystem/network I/O, IPC, sleeps, or potentially contended waits are
157-
prohibited. Fluxheim checks the absolute execution deadline before and after
158-
each callback, so a late result fails as a timeout. A callback that never
159-
returns still cannot be killed safely inside the process. Before Fluxheim adds
155+
current Fluxheim callback is a finite, panic-free in-memory operation that is
156+
total over every possible guest `i32` input. Callback implementations use
157+
explicit matching, checked arithmetic, bounds-checked access, and fallible lock
158+
handling; property tests exercise arbitrary guest IDs. Callbacks that perform
159+
filesystem/network I/O, IPC, sleeps, assertion-based operations, unchecked
160+
indexing/arithmetic, or potentially contended waits are prohibited. Fluxheim
161+
checks the absolute execution deadline before and after each callback, so a
162+
late result fails as a timeout. A callback that never returns or panics still
163+
cannot be isolated safely inside an abort-on-panic process. Before Fluxheim adds
160164
any blocking or third-party native host callback, that capability requires a
161-
killable subprocess runner with bounded IPC; thread-based timeout wrappers are
162-
not an acceptable substitute.
165+
killable subprocess runner with bounded IPC; thread-based timeout wrappers and
166+
`catch_unwind` are not acceptable substitutes.
163167

164168
Compiled modules carry a stable identity that includes the loaded plugin digest,
165169
the manifest ABI version, the host-call namespace, the native hook feature
@@ -439,9 +443,10 @@ native `fluxheim-policy-v1` admission or blocking capacity.
439443
- Host calls must never expose admin tokens, ACME/EAB secrets, private keys,
440444
authorization headers, cookies, raw request bodies, or filesystem paths unless
441445
explicitly allowed and redacted.
442-
- Host callbacks must remain finite and non-blocking. Adding I/O, IPC, sleeps,
443-
or contended waits requires process isolation rather than an in-process
444-
callback timeout.
446+
- Host callbacks must remain finite, non-blocking, panic-free, and total for all
447+
guest integers. Adding I/O, IPC, sleeps, contended waits, unchecked
448+
indexing/arithmetic, assertion-based operations, or third-party native code
449+
requires process isolation rather than an in-process callback timeout.
445450
- Plugins must not control routing destinations or upstream TLS verification
446451
directly. Cache-key influence is allowed only through constrained typed hook
447452
outputs that Fluxheim validates, bounds, and records.

release-notes/RELEASE_NOTES_1.7.8.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ general-purpose WASI application hosting.
8383
- Check the absolute execution deadline before and after every synchronous host
8484
callback so late callback results fail as timeouts. Keep blocking callbacks
8585
prohibited until a killable subprocess runner exists.
86+
- Require in-process native Wasm callbacks to be panic-free and total for every
87+
guest integer, and property-test all current guest-ID decoders over arbitrary
88+
`i32` inputs. Keep panic-prone or third-party native callbacks behind the
89+
future subprocess-isolation boundary.
8690

8791
## Validation
8892

0 commit comments

Comments
 (0)