Skip to content

Phase 4: dispatcher integration for BaseContainer fallback#296

Merged
MGudgin merged 1 commit into
mainfrom
user/gudge/downlevel_phase4
May 24, 2026
Merged

Phase 4: dispatcher integration for BaseContainer fallback#296
MGudgin merged 1 commit into
mainfrom
user/gudge/downlevel_phase4

Conversation

@MGudgin

@MGudgin MGudgin commented May 12, 2026

Copy link
Copy Markdown
Member

Phase 4: ProcessContainer dispatcher with downlevel fallback

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 + bypass-traverse (T2), or
AppContainer + 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 T3 applies host-filesystem ACEs via DaclManager.
  • AppContainerScriptRunner gains FilesystemMode { Bfs, Dacl };
    ::new() defaults to Bfs, so existing callers are unchanged.
  • wxc/src/main.rs routes ProcessContainer through
    dispatch_with_fallback when fallback is in effect; declares the
    DaclManager guard before the runner binding so Drop tears down
    ACEs after the child completes, not mid-run.
  • Validation warning when filesystem.deniedPaths is set but
    ProcessContainer is requested without fallback in effect, so the
    user knows the field will be ignored.

Tests

  • cargo fmt --check, cargo clippy --workspace --all-targets --all-features --profile release --locked -- -D warnings, and
    cargo test -p wxc_common --lib --all-features --profile release --locked all clean on the pinned 1.93 toolchain. 534/534 lib tests
    pass, including dispatcher unit tests covering tier selection
    across BaseContainer present/absent, deniedPaths empty/set, and
    the fallback.allowDaclMutation policy gate.
  • End-to-end coverage (the 8-cell 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

@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch 2 times, most recently from a192705 to 92bffbc Compare May 12, 2026 21:07
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 049b0d6 to 2e61e6c Compare May 12, 2026 21:07
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from 92bffbc to f5a4059 Compare May 12, 2026 21:17
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 2e61e6c to 5fcfe7f Compare May 12, 2026 21:17
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from f5a4059 to 1fe3a5a Compare May 12, 2026 21:32
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 5fcfe7f to 586203f Compare May 12, 2026 21:32
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from 1fe3a5a to 2def674 Compare May 12, 2026 22:38
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 586203f to 11903ec Compare May 12, 2026 22:38
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from 2def674 to 725b597 Compare May 14, 2026 13:51
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 11903ec to f3b502d Compare May 14, 2026 13:51
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from 725b597 to 75b342c Compare May 14, 2026 22:28
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from f3b502d to 2578a20 Compare May 14, 2026 22:28
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from 75b342c to 31954e4 Compare May 14, 2026 23:00
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 2578a20 to 9b2d0af Compare May 14, 2026 23:00
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch from 31954e4 to 3e41ad1 Compare May 14, 2026 23:12
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 9b2d0af to 35de22a Compare May 14, 2026 23:12
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase3 branch 5 times, most recently from ce5c49a to 00e06e6 Compare May 20, 2026 15:03
Base automatically changed from user/gudge/downlevel_phase3 to main May 20, 2026 15:55
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 35de22a to 1697293 Compare May 20, 2026 16:33
Copilot AI review requested due to automatic review settings May 20, 2026 16:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch 2 times, most recently from 7c19b24 to a5b4875 Compare May 20, 2026 20:16
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch 4 times, most recently from 1a35a6f to 3a53a74 Compare May 22, 2026 16:20
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from 3a53a74 to 72bf7e3 Compare May 22, 2026 19:23
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch 4 times, most recently from 2f65da2 to a8267c3 Compare May 23, 2026 21:45
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>
@MGudgin MGudgin force-pushed the user/gudge/downlevel_phase4 branch from a8267c3 to 13bfd9f Compare May 23, 2026 23:13
@MGudgin MGudgin merged commit 77b9de9 into main May 24, 2026
18 checks passed
@MGudgin MGudgin deleted the user/gudge/downlevel_phase4 branch May 24, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants