Skip to content

Commit 3877f0f

Browse files
committed
feat(cli): kill-switch p3 wasi:filesystem when fs policy is active
p3 guests can't be matcher-gated per path op — Dir::open_at and as_dir are pub(crate) in wasmtime-wasi, and HostDescriptorWithStore's U: Send bound blocks the Accessor reprojection we'd need to delegate to the default WasiFilesystem impl. Shadow only p3 wasi:filesystem/preopens. When FsConfig.mode != Open, get_directories returns an empty vec so p3 guests can't obtain a Descriptor::Dir at all and every path op fails at the default impl. Open mode continues to expose the full preopen list. p2 gating via the FsMatcher is unchanged.
1 parent 45b0a2e commit 3877f0f

2 files changed

Lines changed: 58 additions & 0 deletions

File tree

act-cli/src/runtime/fs_policy.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,11 @@ pub struct PolicyFilesystemCtxView<'a> {
133133
pub table: &'a mut ResourceTable,
134134
pub matcher: &'a FsMatcher,
135135
pub fd_paths: &'a mut FdPathMap,
136+
/// Configured mode; drives the p3 preopens kill-switch. p3 path-taking
137+
/// ops can't be gated (upstream `Dir::open_at` is `pub(crate)`), so when
138+
/// mode is anything but `Open` we return zero preopens from p3 and p3
139+
/// guests can't acquire a `Descriptor::Dir` handle at all.
140+
pub mode: PolicyMode,
136141
}
137142

138143
/// Tracks the host path associated with each open filesystem descriptor,
@@ -476,6 +481,44 @@ impl HostDescriptor for PolicyFilesystemCtxView<'_> {
476481
}
477482
}
478483

484+
// ── p3 preopens kill-switch ───────────────────────────────────────────────
485+
//
486+
// We can't mirror the full p2 matcher on p3 because `Dir::open_at` and
487+
// friends are `pub(crate)` in wasmtime-wasi — shadowing `HostDescriptorWithStore`
488+
// would need to reproject the Accessor via a `U: WasiFilesystemView` bound
489+
// that the trait doesn't permit, and the sibling methods we'd need to call
490+
// directly (`dir.open_at`, `dir.as_dir`) are gated.
491+
//
492+
// Instead we gate at preopens: if fs mode is anything other than `Open`,
493+
// p3 `get_directories` returns an empty vec. A p3 guest with an empty
494+
// preopen list can't construct a `Descriptor::Dir` resource, so every
495+
// p3 path op fails before it reaches cap-std. Components that genuinely
496+
// need p3 filesystem access must run under `policy.filesystem = "open"`.
497+
498+
impl wasmtime_wasi::p3::bindings::filesystem::preopens::Host for PolicyFilesystemCtxView<'_> {
499+
fn get_directories(
500+
&mut self,
501+
) -> wasmtime::Result<
502+
Vec<(
503+
Resource<wasmtime_wasi::p3::bindings::filesystem::types::Descriptor>,
504+
String,
505+
)>,
506+
> {
507+
if self.mode != PolicyMode::Open {
508+
tracing::warn!(
509+
mode = ?self.mode,
510+
"p3 wasi:filesystem/preopens: returning empty; p3 path ops can't be matcher-gated",
511+
);
512+
return Ok(vec![]);
513+
}
514+
let mut inner = WasiFilesystemCtxView {
515+
ctx: self.ctx,
516+
table: self.table,
517+
};
518+
<WasiFilesystemCtxView as wasmtime_wasi::p3::bindings::filesystem::preopens::Host>::get_directories(&mut inner)
519+
}
520+
}
521+
479522
// ── HostDirectoryEntryStream ──────────────────────────────────────────────
480523

481524
impl HostDirectoryEntryStream for PolicyFilesystemCtxView<'_> {

act-cli/src/runtime/mod.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ pub struct HostState {
3131
#[allow(dead_code)] // retained for Task 10 DNS resolver hook access
3232
http_client: std::sync::Arc<crate::runtime::http_client::ActHttpClient>,
3333
fs_matcher: crate::runtime::fs_matcher::FsMatcher,
34+
fs_mode: crate::config::PolicyMode,
3435
fd_paths: crate::runtime::fs_policy::FdPathMap,
3536
}
3637

@@ -42,6 +43,7 @@ impl HostState {
4243
table: &mut self.table,
4344
matcher: &self.fs_matcher,
4445
fd_paths: &mut self.fd_paths,
46+
mode: self.fs_mode,
4547
}
4648
}
4749
}
@@ -115,6 +117,18 @@ pub fn create_linker(engine: &Engine) -> Result<Linker<HostState>> {
115117
// Add P3 bindings on top
116118
wasmtime_wasi::p3::add_to_linker(&mut linker)
117119
.map_err(|e| anyhow::anyhow!("failed to add WASI P3 to linker: {e}"))?;
120+
// Shadow only the p3 preopens interface. When fs mode ≠ Open, our impl
121+
// returns zero preopens → p3 guests can't obtain a Descriptor::Dir and
122+
// every path op fails. Matcher-level gating on individual p3 path ops
123+
// isn't possible with current wasmtime-wasi public API (Dir::open_at
124+
// is `pub(crate)`).
125+
linker.allow_shadowing(true);
126+
wasmtime_wasi::p3::bindings::filesystem::preopens::add_to_linker::<
127+
HostState,
128+
crate::runtime::fs_policy::PolicyFilesystem,
129+
>(&mut linker, |t| t.policy_fs_view())
130+
.map_err(|e| anyhow::anyhow!("failed to add policy wasi:filesystem/preopens (p3): {e}"))?;
131+
linker.allow_shadowing(false);
118132
// Add WASI HTTP bindings (P2 for wasm32-wasip2 components, P3 for async)
119133
wasmtime_wasi_http::p2::add_only_http_to_linker_async(&mut linker)
120134
.map_err(|e| anyhow::anyhow!("failed to add WASI HTTP P2 to linker: {e}"))?;
@@ -167,6 +181,7 @@ pub fn create_store(
167181
),
168182
http_client,
169183
fs_matcher: matcher,
184+
fs_mode: fs.mode,
170185
fd_paths: crate::runtime::fs_policy::FdPathMap {
171186
preopens: preopen_pairs,
172187
by_rep: Default::default(),

0 commit comments

Comments
 (0)