bytecodealliance / bytecodealliance/wasmtime

Fuzzing coverage gap: generative testing of per-preopen FsPerms enforcement

Open
#14,103 6 comments 0 reactions 0 assignees View on GitHub
fuzzing wasi:impl
Dominant language
Rust
Stars
18.6k
Forks
1.8k
Avg merge
1d 18h
Merged PRs (30d)
126

Description

#### Feature

WASI doesn't appear to be exercised by any fuzz target. The [OSS-Fuzz `crates/` coverage report for 2026-08-09](https://storage.googleapis.com/oss-fuzz-coverage/wasmtime/reports/20260809/linux/src/wasmtime/crates/report.html) lists 15 instrumented crates — `cache`, `cli-flags`, `component-util`, `core`, `cranelift`, `environ`, `fiber`, `fuzzing`, `jit-debug`, `jit-icache-coherence`, `test-util`, `unwinder`, `wasmtime`, `wast`, `winch` — and no `wasi*` crate is among them. Grepping `fuzz/fuzz_targets/` and `crates/fuzzing/src/` for `wasi` also returns nothing.

Would a generative, stateful fuzz target for per-preopen `FsPerms` enforcement be useful? I'd rather ask than build, partly because there may be good reasons this hasn't been done that aren't visible from outside.

#### Benefit

Filesystem permissions look like they're enforced at three levels, and generative coverage seems to exist for the outer two but not the middle one:

| Layer | Mechanism | Generative coverage |
|---|---|---|
| WASIp1 `rights` | spec-level, per-fd | `wasi-testsuite` — fixed conformance cases (e.g. `truncation_rights.rs`) |
| **wasmtime `FsPerms`** | **embedder-level, per-preopen** | **none found** |
| cap-std containment | per-`Dir` handle | cap-std's `fuzz_targets/cap-primitives.rs`, with `cfg(racy_asserts)` checks |

The three recent WASI advisories all sit in that middle layer:

| Advisory | Fix | Cause |
|---|---|---|
| GHSA-2r75-cxrj-cmph (High) | `dc740c909` | `TRUNCATE` didn't set `open_mode \|= OpenMode::WRITE`, so the perms check saw a read-only open |
| GHSA-4ch3-9j33-3pmj (Moderate) | `2dc3f443d` | `link_at`/`rename_at` checked directory mutability but not permission parity |
| GHSA-3p27-qvp9-27qf (Low) | `f069f125c` | WASIp1 `fd_renumber` descriptor-table handling |

In each the permission gate runs in `crates/wasi/src/filesystem.rs` before `cap_primitives` is called, so cap-std's fuzzer isn't positioned to catch them — and `FsPerms` has no cap-std analogue to test against. The regression tests added with those fixes (`p{1,2,3}_file_truncation_readonly.rs`, `p{1,2,3}_file_{hardlink,rename}_across_perms.rs`) are targeted cases rather than generated sequences.

#### Implementation

Sketch, open to redirection:

- Two or more preopens with differing `FsPerms` — the `link`/`rename` case needs at least two.
- Random sequences of filesystem operations across them, spanning the preopen boundary.
- Oracle: with a `ReadOnly` preopen, snapshot the tree before and after and assert it's unchanged. That follows the documented contract on `FsPerms` directly and needs no model of individual operation semantics — it wouldn't need to know `open_at(TRUNCATE)` should fail, only that a file got shorter. `atime` excluded, since reading metadata is permitted.
- A second oracle asserting host file descriptors return to baseline would cover the `fd_renumber` class.
- p1, p2 and p3 separately, since the regression tests suggest fixes don't automatically carry across surfaces.

Before proposing any of it as a PR I'd calibrate: check out the commit before each of the three fixes and confirm the oracle fires, then confirm it's silent across `wasi-testsuite` and `crates/wasi/tests/`. If it can't detect the three bugs already known, it isn't worth reviewing.

#### Alternatives

**Extend the hand-written tests instead.** `FsPerms` has two states, so the space is arguably small enough to enumerate — which is roughly what the current regression tests do. My hesitation is that the real space is operations × 2 permission states × 2+ preopens × path shapes × symlink/hard-link states × 3 API surfaces, and the `link`/`rename` issue was a relational property across two guest-chosen descriptors. But it's a fair objection and I'd rather hear it now than after writing the harness.

**Fuzz at the cap-std layer.** Doesn't reach it — `FsPerms` doesn't exist there.

**Do nothing.** Entirely possible this is deliberate; filesystem fuzzing is slow and awkward under OSS-Fuzz. If so, knowing that is worth the issue, and it may be worth a line in `fuzz/README.md` so the next person doesn't retrace it.

---

Happy to take this to an RFC instead if that's a better fit.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.