Skip to content

Add shared plumbing for the Windows Sandbox rewrite (Phase 0)#576

Open
MGudgin wants to merge 1 commit into
mainfrom
user/gudge/wsb-phase0-shared-plumbing
Open

Add shared plumbing for the Windows Sandbox rewrite (Phase 0)#576
MGudgin wants to merge 1 commit into
mainfrom
user/gudge/wsb-phase0-shared-plumbing

Conversation

@MGudgin

@MGudgin MGudgin commented Jun 26, 2026

Copy link
Copy Markdown
Member

Summary

Phase 0 of the Windows Sandbox rewrite stack: land the small, additive
wxc_common plumbing first so the larger lifecycle/one-shot PR stays focused.
Nothing on main references the new items yet, so this merges independently.

Details

  • filesystem_dacl: add set_owner_only_dacl (+ owner_is_self and the
    current_user_sid / current_token_owner_sid / self_owner_sids helpers)
    to stamp an owner-only, optionally-inheritable, PROTECTED DACL (current user
    • Local SYSTEM) on a path MXC owns. owner_is_self fails closed when a
      shared-temp path was pre-created by another user (a foreign owner retains
      implicit WRITE_DAC/READ_CONTROL regardless of the DACL). This is the
      primitive the rewrite's nonce / state-aware daemon.json files use to keep
      other users on a shared host off them.
  • models: add two FailurePhase variants -- Rejected (request can't be
    honored without changing input/host) and PostLaunchFailed (guest/sandbox
    infra failed after a successful launch) -- and sharpen the doc comments on
    the existing variants. Purely additive: the only match on FailurePhase
    (mxc-sdk map_spawn_error) has a wildcard arm, so adding variants is
    non-breaking.

Tests

  • cargo test -p wxc_common: 363 passed, including the new
    set_owner_only_dacl_strips_inheritance_and_grants_owner, owner_is_self_*,
    and failure_phase_serde_round_trips_all_variants.
  • cargo fmt -p wxc_common -- --check and
    cargo clippy -p wxc_common --all-targets -- -D warnings: clean.
  • cargo check -p mxc-sdk -p appcontainer_common: clean (verified the new
    enum variants don't break downstream consumers).

Related Issues

Part of the Windows Sandbox rewrite (user/gudge/windows_sandbox_rewrite),
split into a 0-3 stacked-PR series; this is Phase 0.

Microsoft Reviewers: Open in CodeFlow

This PR adds the small, additive `wxc_common` pieces that the upcoming
Windows Sandbox rewrite stack builds on, landed first so the larger
lifecycle/one-shot PR stays focused.

Details

* filesystem_dacl: add `set_owner_only_dacl` (+ `owner_is_self` and the
  `current_user_sid` / `current_token_owner_sid` / `self_owner_sids`
  helpers) to stamp an owner-only, optionally-inheritable DACL on a path.
  This is the primitive the rewrite's nonce/daemon-record files use to
  keep other users off them.
* models: add two `FailurePhase` variants -- `Rejected` (request can't be
  honored without changing input/host) and `PostLaunchFailed` (guest/
  sandbox infra failed after a successful launch) -- and sharpen the
  doc comments on the existing variants. Purely additive: the only
  `match` on `FailurePhase` (mxc-sdk `map_spawn_error`) has a wildcard
  arm, so adding variants is non-breaking.

Tests

* `cargo test -p wxc_common`: 363 passed, including the new
  `set_owner_only_dacl_strips_inheritance_and_grants_owner`,
  `owner_is_self_*`, and `failure_phase_serde_round_trips_all_variants`.
* `cargo fmt -p wxc_common -- --check` and
  `cargo clippy -p wxc_common --all-targets -- -D warnings`: clean.
* `cargo check -p mxc-sdk -p appcontainer_common`: clean (verified the
  new enum variants don't break downstream consumers).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generated-with: claude-opus-4.8
Copilot AI review requested due to automatic review settings June 26, 2026 18:20
@MGudgin MGudgin requested a review from a team as a code owner June 26, 2026 18:20

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.

Pull request overview

Adds foundational, additive wxc_common primitives intended to support the upcoming Windows Sandbox rewrite without yet wiring them into mainline execution paths.

Changes:

  • Introduces FailurePhase::Rejected and FailurePhase::PostLaunchFailed, and refines phase documentation for clearer error classification.
  • Adds Windows filesystem security helpers to detect “foreign-owned” paths (owner_is_self) and to stamp an owner-only, PROTECTED DACL (set_owner_only_dacl), plus unit tests around the new behavior.
Show a summary per file
File Description
src/core/wxc_common/src/models.rs Adds new FailurePhase variants and a serde round-trip test for the enum.
src/core/wxc_common/src/filesystem_dacl.rs Adds helpers to read token/user/owner SIDs, validate filesystem ownership, and apply a protected owner-only DACL with tests.

Review details

  • Files reviewed: 2/2 changed files
  • Comments generated: 2
  • Review effort level: Low

Comment on lines +712 to +728
#[test]
fn failure_phase_serde_round_trips_all_variants() {
let cases = [
(FailurePhase::None, "\"None\""),
(FailurePhase::LaunchFailed, "\"LaunchFailed\""),
(FailurePhase::Rejected, "\"Rejected\""),
(FailurePhase::PostLaunchFailed, "\"PostLaunchFailed\""),
(FailurePhase::ProcessExited, "\"ProcessExited\""),
(FailurePhase::BackendUnavailable, "\"BackendUnavailable\""),
];
for (variant, wire) in cases {
let s = serde_json::to_string(&variant).unwrap();
assert_eq!(s, wire, "serialize {variant:?}");
let back: FailurePhase = serde_json::from_str(wire).unwrap();
assert_eq!(back, variant, "round-trip {wire}");
}
}
Comment on lines +1341 to +1344
pub fn set_owner_only_dacl(path: &Path, inheritable: bool) -> Result<(), DaclError> {
let user = current_user_sid()?;
let system = OwnedSid::parse("S-1-5-18")?; // Local SYSTEM.

OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut token)
.map_err(|e| win32_err_str(token_path, &format!("OpenProcessToken: {e}")))?;

let mut len = 0u32;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Missed adding the comment // First call sizes the buffer (returns ERROR_INSUFFICIENT_BUFFER).

.map_err(|e| win32_err_str(token_path, &format!("OpenProcessToken: {e}")))?;

let mut len = 0u32;
let _ = GetTokenInformation(token, TokenOwner, None, 0, &mut len);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Why is throwing away the GetTokenInformation result okay? I am suspicious when return codes are ignored.

/// be owned by: the token user SID and (when elevated) the token owner SID.
fn self_owner_sids() -> Result<Vec<OwnedSid>, DaclError> {
let mut sids = vec![current_user_sid()?];
// Best-effort: only meaningful (and only differs) under elevation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What is the difference under elevation and why is it meaningful?

}
}
unsafe {
let _ = LocalFree(Some(HLOCAL(sd.0)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I'm new to rust and I might not understand the code flow correctly. Is LocalFree called if the code returns on line 1311?

Ok(is_self)
}

///

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

NIT: newline with no comment.

grfInheritance: ACE_FLAGS(inheritance),
Trustee: trustee_for(sid),
};
let ea = [grant(&user), grant(&system)];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What does ea represent? Each Access, Every Access, Early Access? I can not figure it out from code.

// Note: the analogous compile-time `const _: () = assert!(...)`
/// A directory THIS process creates must be reported as owned by us.
#[test]
fn owner_is_self_true_for_dir_we_create() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

+1 for including the expected result in the test name. :)

@dhoehna dhoehna left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I do not know the architecture of the code base and I do not feel comfortable approving the PR.

A comment for the change in general. Consider expanding variable names to describe what they are for instead of the type. Aside from well known monikers like rc.

Otherwise the code looks sound.

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