Phase 3.5: --prepare-system-drive flag for AppContainer host setup#387
Phase 3.5: --prepare-system-drive flag for AppContainer host setup#387MGudgin wants to merge 1 commit into
Conversation
|
End-to-end verification on real The MF-1 procedure was:
|
There was a problem hiding this comment.
Pull request overview
Adds a Windows host-prep mode to wxc-exec to permanently grant AppContainer well-known groups minimal metadata-read access on the system drive root, unblocking common shell/tool startup inside AppContainer.
Changes:
- Added
wxc-exec --prepare-system-drive/--unprepare-system-driveflags (plus hidden internal helper args) to perform host-wide DACL prep with UAC self-elevation. - Introduced
wxc_common::system_drive_prepimplementing the elevation flow and tuple-precise apply/revoke logic. - Refactored
filesystem_daclto exposeapply_explicit_aceand addedrevoke_specific_aces_for_sidfor precise removal; added PowerShell equivalents + documentation.
Show a summary per file
| File | Description |
|---|---|
| src/wxc/src/main.rs | Adds CLI flags and early-exit dispatch to the host-prep entrypoints. |
| src/wxc_common/src/system_drive_prep.rs | New Windows-only module implementing prepare/unprepare, UAC relaunch, and logging/tests. |
| src/wxc_common/src/lib.rs | Exposes the new system_drive_prep module on Windows. |
| src/wxc_common/src/filesystem_dacl.rs | Adds apply_explicit_ace and tuple-precise revoke_specific_aces_for_sid. |
| scripts/host-prep/Prepare-SystemDriveForAppContainer.ps1 | Adds an elevated PowerShell script to apply the persistent ACEs. |
| scripts/host-prep/Unprepare-SystemDriveForAppContainer.ps1 | Adds an elevated PowerShell script to precisely remove the ACEs. |
| docs/host-prep.md | Documents the new host-prep flag behavior, security scope, and verification steps. |
Copilot's findings
- Files reviewed: 7/7 changed files
- Comments generated: 2
| /// Path the elevated child writes its console output to, so the | ||
| /// unelevated parent can surface it on failure. The child's console | ||
| /// window closes immediately on exit; without this redirection the | ||
| /// user sees only `ChildFailed(<code>)` with no diagnostic. | ||
| fn helper_log_path() -> PathBuf { | ||
| std::env::temp_dir().join("wxc-exec-prepare-system-drive.log") | ||
| } |
| let priors = scan_explicit_aces_for_sid(path, sid_str)?; | ||
| let mut keeps: Vec<PriorAce> = Vec::new(); | ||
| let mut removed = 0usize; | ||
| for p in priors { | ||
| if p.access_mask == access_mask | ||
| && p.ace_type == ace_type | ||
| && p.inherit_flags == expected_inherit_flags | ||
| { | ||
| removed += 1; | ||
| } else { | ||
| keeps.push(p); | ||
| } | ||
| } | ||
|
|
||
| if removed == 0 { | ||
| return Ok(0); | ||
| } |
28dba43 to
20f859d
Compare
20f859d to
664ebfe
Compare
AppContainer processes can't read metadata of the system-drive root (GetFileAttributesW, _stat, GetAccessControl). cmd.exe, powershell.exe, pwsh.exe, and node.exe hit these during startup and fail with ERROR_ACCESS_DENIED, blocking most agent workloads. Add `wxc-exec --prepare-system-drive` and `--unprepare-system-drive` to add/remove a metadata-only allow ACE (mask 0x00120088, no inheritance) on `C:\` for the well-known SIDs S-1-15-2-1 and S-1-15-2-2. Self-elevates via UAC. Unprep is tuple-precise: removes only ACEs the prepare path would have written, so same-SID ACEs authored by other tools (e.g. via `icacls /grant`) survive. Tested: cargo fmt/check/clippy clean; 345/345 lib tests (4 new in `system_drive_prep::tests` covering round-trip, precise-revoke regression, and drive-root validator); end-to-end against real `C:\` on Windows 11 25H2. See `docs/host-prep.md`; hand-test scripts in `scripts/host-prep/`. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
664ebfe to
b518413
Compare
|
Closing without merging. Phase 3.5 was motivated by the claim that cmd/pwsh/node fail to start inside an AppContainer because they can't
The actual unblocker for pwsh/node was setting Phase 3.5 still has one confirmed empirical effect: cmd.exe's The branch |
📖 Description
Adds
wxc-exec --prepare-system-drive(and its inverse--unprepare-system-drive) ΓÇö a one-time, host-wide setup step that grants the well-known AppContainer SIDsS-1-15-2-1(ALL APPLICATION PACKAGES) andS-1-15-2-2(ALL RESTRICTED APPLICATION PACKAGES) a tiny metadata-only access mask on the system-drive root (C:\).Why
AppContainer processes can't, by default, read directory metadata of the system-drive root via APIs such as
GetFileAttributesW,_stat, or[IO.DirectoryInfo]::GetAccessControl. Common tools (cmd.exe,powershell.exe,pwsh.exe,node.exe) hit these during startup and fail withERROR_ACCESS_DENIEDinside an AppContainer. This blocks most non-trivial agent workloads on the T2/T3 paths landed in phases 3 and 4.What we grant
ALL APPLICATION PACKAGESS-1-15-2-1FILE_READ_ATTRIBUTES | FILE_READ_EA | READ_CONTROL | SYNCHRONIZE(0x00120088)ALL RESTRICTED APPLICATION PACKAGESS-1-15-2-2The mask is the metadata-read set used by
GetFileAttributesW,_stat, andGetAccessControl. It explicitly excludesFILE_LIST_DIRECTORY(the container still cannot enumerateC:\) andFILE_READ_DATA. Inheritance isNone, so descendant files and subdirectories are unaffected ΓÇö the change is scoped to the directory object itself.Code shape
wxc_common::system_drive_prepmodule (Windows-only).filesystem_dacl::apply_acerefactored into a thin wrapper over a newpub(crate)helperapply_explicit_ace(path, sid, mask, type, inheritable). Behavior-preserving.pub(crate)helperrevoke_specific_aces_for_sidfor tuple-precise revoke (only ACEs matching the apply tuple are removed; non-matching ones ΓÇö e.g. third-partyicacls /grantACEs ΓÇö are preserved).wxc/src/main.rsadds three flags (two visible, one hidden) with clapconflicts_with_allagainst the other top-level "do this and exit" flags.Hardenings (from the adversarial review on
330fabd5)--unprepare-system-driveno longer issues a bluntSetEntriesInAclW(REVOKE_ACCESS)for the SID. It scans the existing DACL, partitions explicit ACEs into matches/non-matches against the apply tuple, and only revokes matches. PowerShell scripts and Rust path now genuinely mirror each other.conflicts_with_allagainst--setup-hyperlight,--setup-wslc,--delete,--dry-run, and each other. Invalid combos are rejected at parse time instead of silently dropping the other flag.%TEMP%\wxc-exec-prepare-system-drive.log; the unelevated parent reads and prints the contents on non-zero child exit so failures surface a real diagnostic instead of an opaque exit code.WaitForSingleObjectreturn value is now checked againstWAIT_OBJECT_0.--internal-target-pathfor path-resolution hardening (SF-1): the unelevated parent resolves%SystemDrive%once and passes the resolved path to the elevated child as a hidden CLI arg. The child does not re-read%SystemDrive%from a potentially attacker-controlled environment and validates the received path is a literal drive root (X:\) before touching its DACL.Elevation model
Modifying the DACL on
C:\needsWRITE_DAC, which normal users don't hold. Windows does not allow a running process to elevate its own token, so the unelevated parent re-launches itself viaShellExecuteExW(runas)with a hidden--internal-elevated-helperflag, waits for the elevated child, and propagates the exit code. A UAC prompt is expected on the first invocation.🔗 References
Prerequisite for #296 (Phase 4: dispatcher integration for BaseContainer fallback). Phase 4 will need to be rebased onto this once it lands.
Related to #304 ("T3: directory enumeration fails even on granted paths") ΓÇö does not resolve it (that's about ancestor
FILE_TRAVERSErights forFindFirstFile, a different problem), but is on the same axis and helps unblock the same "cmd/pwsh inside AppContainer can't start up" symptom.Stacks on top of #295 (Phase 3:
filesystem_daclmodule). Phase 3 is already merged tomain, so this PR targetsmaindirectly.🔍 Validation
Automated
cargo fmt --all -- --check✅cargo check --workspace --all-targets✅cargo clippy --workspace --all-targets -- -D warnings✅cargo test -p wxc_common --lib -- --test-threads=1✅ (345/345 pass, including 4 new tests insystem_drive_prep::tests: round-trip, override seam, MF-1 precise-revoke regression, SF-1 drive-root validator)Manual (Windows 11 25H2)
Verified end-to-end with
scripts/host-prep/Prepare-SystemDriveForAppContainer.ps1on realC:\. Before the prep,icacls C:\showed no explicit ACEs for the AppContainer SIDs. Afterwxc-exec --prepare-system-drive, icacls confirmed both ACEs landed with mask(Rc,S,REA,RA)(=0x00120088) and no inheritance flags. Re-running--prepare-system-drivewas idempotent.--unprepare-system-driveremoved both. The C:\ DACL restored byte-for-byte to baseline (SDDL comparison).UAC handshake (from a non-elevated PowerShell): observed UAC consent dialog → elevated child runs → unelevated parent prints
Elevated helper completed successfully.→ exit 0.✅ Checklist
(Copilot instructions: added
docs/host-prep.mdto theKey documentation → Core referenceslist so future agents discover the new doc and thescripts/host-prep/hand-test helpers.)≡ƒôï Issue Type