Phase 4: dispatcher integration for BaseContainer fallback#296
Merged
Conversation
a192705 to
92bffbc
Compare
049b0d6 to
2e61e6c
Compare
92bffbc to
f5a4059
Compare
2e61e6c to
5fcfe7f
Compare
f5a4059 to
1fe3a5a
Compare
5fcfe7f to
586203f
Compare
1fe3a5a to
2def674
Compare
586203f to
11903ec
Compare
2def674 to
725b597
Compare
11903ec to
f3b502d
Compare
725b597 to
75b342c
Compare
f3b502d to
2578a20
Compare
75b342c to
31954e4
Compare
2578a20 to
9b2d0af
Compare
31954e4 to
3e41ad1
Compare
9b2d0af to
35de22a
Compare
ce5c49a to
00e06e6
Compare
35de22a to
1697293
Compare
7c19b24 to
a5b4875
Compare
1a35a6f to
3a53a74
Compare
7 tasks
3a53a74 to
72bf7e3
Compare
2f65da2 to
a8267c3
Compare
This PR adds a tier dispatcher for `containment: "ProcessContainer"` so
configs on hosts without the BaseContainer OS API no longer fail hard
— the dispatcher detects host capability and policy, then selects
BaseContainer (T1), AppContainer + BFS (T2, the `bfscfg.exe`-driven
filesystem policy), or AppContainer + host-DACL augmentation (T3).
The dispatcher is only engaged when `--experimental` is passed or the
schema version is ≥ 0.5; schema 0.4 and all other `ContainmentBackend`
variants are unchanged.
Details
* New `wxc_common::dispatcher` module wires Phases 0–3 together:
consults `fallback_detector::detect()`, constructs the appropriate
runner, and for T2-deny / T3 applies host-filesystem ACEs via
`DaclManager`. Per-tier `paths_to_pathbufs` allocation — T1 and the
no-deny T2 fast path skip the path-Vec work entirely.
* T1 deliberately does NOT stamp host-DACL deny ACEs: the AppContainer
SID derived from `container_name(request)` is not guaranteed to
match the opaque principal `Experimental_CreateProcessInSandbox`
runs the child under, and a mismatch would silently un-enforce
`deniedPaths`. T1 delegates filesystem policy to BaseContainer's
native API (including native deny once that lands upstream).
* New `wxc_common::appcontainer_runner::derive_sid_string` produces
the string form of an AppContainer SID for ACE targeting, threading
`FreeSid` / `LocalFree` cleanup through both the success and failure
paths and defensively null-checking the post-conversion `PWSTR`.
* `AppContainerScriptRunner` gains `FilesystemMode { Bfs, Dacl }` and
`with_filesystem_mode_and_sid_string(...)`; the dispatcher derives
the SID once and hands it to the runner so `get_principal_id`
doesn't re-run `ConvertSidToStringSidW`.
* `wxc/src/main.rs` routes `ProcessContainer` through
`dispatch_with_fallback` when fallback is in effect. Drop-order
contract documented in a forcing-function comment at the
`_dacl_guard` declaration: `runner` drops before `_dacl_guard`,
every `process::exit` after dispatch goes through the explicit
drops, panic strategy is `unwind` so panics also tear down ACEs.
* `dispatcher.rs` module doc gains a `# Performance` section: T3
stamps and reverses N host-DACL ACEs per invocation — tens of ms at
typical N (6–12 paths), linear at larger N. Parent-rollup and
session-scoped `DaclManager` caching are tracked as follow-ups.
* `MXC_FORCE_TIER` and `MXC_BFSCFG_PATH` test seams (and the test-only
`MXC_DACL_STATE_DIR` override helper) migrate from
`#[cfg(debug_assertions)]` to `#[cfg(test)]`. The previous gate
silently elided the tier-selection tests under CI's `cargo test
--profile release` invocation — they only ran on dev-profile local
runs. Production `wxc-exec.exe` binaries (both release and dev) are
unchanged: `cfg(test)` is only set when building this crate's test
binary, so the env-var seams cannot be exploited from a shipped
binary. New `wxc_common::test_env` module owns a single crate-wide
`ENV_LOCK` plus `ForceTierGuard` / `BfscfgPathGuard` /
`ScopedStateDir` RAII helpers; this unifies the previously
per-module locks that could race on `MXC_DACL_STATE_DIR` when
`filesystem_dacl::tests` and `dispatcher::tests` ran concurrently.
Tests
* `cargo fmt --all -- --check` and
`cargo clippy --workspace --all-targets --all-features --profile
release --locked -- -D warnings` clean on the pinned 1.93 toolchain.
* `cargo test -p wxc_common --lib --all-features --profile release
--locked` (CI's exact invocation) passes 551/551 — up from 534
before the `cfg(test)` migration because 17 previously-elided
tier-selection tests now actually run. The dispatcher tier
tests covered include T1+no-deny (no DaclManager), T1+deny (still
no DaclManager — see the T1 bullet above), T2+deny, T3, the
`fallback.allowDaclMutation` gate, and warning propagation.
* `cargo test -p wxc_common --lib --all-features --locked` (dev
profile) passes 552/552 — one extra over release because
`system_drive_prep::tests::target_path_honors_override` is still
`#[cfg(debug_assertions)]`-gated (intentional: that override is a
different security boundary from `MXC_FORCE_TIER`).
* `cargo test --workspace --exclude wxc_e2e_tests --all-features
--profile release --locked` passes 666/666 across the rest of the
workspace. The `wxc_e2e_tests` MicroVM scenarios are excluded
because they require a real MicroVM environment we don't have
locally; they're independent of this PR's diff.
* End-to-end coverage (the T3 access matrix over rw / ro / denied /
control paths) lives in `Win25H2Safe-Tests.ps1` on the held Phase 5
branch and will land with that PR.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
a8267c3 to
13bfd9f
Compare
shschaefer
approved these changes
May 24, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Phase 4: ProcessContainer dispatcher with downlevel fallback
This PR adds a tier dispatcher for
containment: "ProcessContainer"soconfigs on hosts without the BaseContainer OS API no longer fail hard
— the dispatcher detects host capability and policy, then selects
BaseContainer (T1), AppContainer + bypass-traverse (T2), or
AppContainer + DACL augmentation (T3). The dispatcher is only engaged
when
--experimentalis passed or the schema version is ≥ 0.5;schema 0.4 and all other
ContainmentBackendvariants are unchanged.Details
wxc_common::dispatchermodule wires Phases 0–3 together:consults
fallback_detector::detect(), constructs the appropriaterunner, and for T3 applies host-filesystem ACEs via
DaclManager.AppContainerScriptRunnergainsFilesystemMode { Bfs, Dacl };::new()defaults toBfs, so existing callers are unchanged.wxc/src/main.rsroutesProcessContainerthroughdispatch_with_fallbackwhen fallback is in effect; declares theDaclManagerguard before the runner binding so Drop tears downACEs after the child completes, not mid-run.
filesystem.deniedPathsis set butProcessContaineris requested without fallback in effect, so theuser knows the field will be ignored.
Tests
cargo fmt --check,cargo clippy --workspace --all-targets --all-features --profile release --locked -- -D warnings, andcargo test -p wxc_common --lib --all-features --profile release --lockedall clean on the pinned 1.93 toolchain. 534/534 lib testspass, including dispatcher unit tests covering tier selection
across BaseContainer present/absent,
deniedPathsempty/set, andthe
fallback.allowDaclMutationpolicy gate.denied / control paths) lives in
Win25H2Safe-Tests.ps1on theheld Phase 5 branch and will land with that PR.
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com