Skip to content

Harden the Windows Sandbox state-aware daemon and lifecycle#658

Open
MGudgin wants to merge 1 commit into
mainfrom
user/gudge/wsb-daemon-hardening
Open

Harden the Windows Sandbox state-aware daemon and lifecycle#658
MGudgin wants to merge 1 commit into
mainfrom
user/gudge/wsb-daemon-hardening

Conversation

@MGudgin

@MGudgin MGudgin commented Jul 17, 2026

Copy link
Copy Markdown
Member

This PR hardens the Windows Sandbox state-aware daemon and lifecycle: it makes
daemon diagnostics observable, bounds a frozen guest with a cancellation-safe
exec watchdog, reports VM-slot contention coherently, and versions the
client/daemon IPC wire protocol behind a bilateral, daemon-enforced handshake.
Each change is backend-only, with no SDK or wire-schema impact.

Details

  • Daemon diagnostics are captured. The daemon emits ~40 [wsb-daemon]
    diagnostics, and the VM-owning detached process is exactly where boot /
    teardown / orphan-reclaim failures need to be observable. spawn_daemon
    redirects the daemon's stderr to daemon.log in the per-sandbox owner-only
    record directory (append), and the start-timeout error names the concrete log
    path.
  • The streaming exec path has a cancellation-safe host-side watchdog. A
    frozen-but-alive guest (the VM crash watchdog only fires once the VM processes
    are gone) would otherwise wedge the single-flight slot indefinitely. The
    watchdog deadline lives inside stream_exec_on_guest's select! loop and is
    honoured only at frame boundaries, so a timeout can never truncate a data
    frame and desync the client's decoder. On expiry the daemon marks the guest
    slot Unusable and sends the client a clean synthetic timeout exit frame (the
    client reads with no deadline once streaming starts, so it is freed too).
  • VM-slot contention is reported coherently. HostVmLock::try_acquire
    distinguishes genuine contention (the wait timed out) from a real create/wait
    failure: on contention the daemon exits with a distinct
    DAEMON_EXIT_VM_SLOT_BUSY code that start maps to backend_unavailable,
    while a real mutex failure propagates as an opaque backend error (with
    daemon.log diagnostics) rather than being mis-reported as busy. (start does
    not hold the slot itself — the daemon must own it for the VM's lifetime, so
    holding it across the spawn would deadlock; the distinct-exit-code mapping
    achieves the same observable outcome without that hazard.)
  • The IPC wire protocol is versioned behind a bilateral, daemon-enforced
    handshake.
    The client declares its version as the trailing token of the
    EXEC line (EXEC <nonce> <proto>); the daemon refuses a mismatched or
    malformed version with ERR protocol before any frame I/O, so a
    differently-framed request is never parsed. An absent token maps to
    LEGACY_IPC_PROTOCOL_VERSION, so a future daemon cleanly refuses a legacy
    client instead of misframing it. DaemonRecord also carries a
    protocol_version (added with #[serde(default)] so a pre-versioning record
    reads back as incompatible) as a client-side fast-path (ensure_daemon_protocol).
    An orphaned daemon from a prior install authenticates (its nonce is in the
    record) but is refused when its wire version differs. stop/deprovision
    keep using the stable single-line STOP command (explicit OK ack) so an
    orphaned VM can always be reclaimed.
  • Docs: docs/windows-sandbox/windows-sandbox-reference.md covers the
    daemon.log sink, the exec watchdog, the VM-slot busy mapping, and the
    wire-protocol handshake.

Tests

  • Unit tests: protocol_compatible and a serde-default case (a pre-versioning
    record deserialises and reads back protocol-incompatible), parse_client_protocol,
    ensure_daemon_protocol accept/refuse, the vm_slot_busy exit-code
    classifier, a two-thread named_mutex_try_acquire test asserting a contended
    mutex returns Ok(None) (busy) rather than Err, the daemon EXEC
    matching/mismatched/malformed-version handshake, the ERR protocol status
    mapping to backend_unavailable, and a watchdog frame-boundary regression
    asserting no partial data frame precedes the terminal frame.
  • cargo fmt --all -- --check and cargo clippy (affected crates,
    -D warnings): clean.
  • cargo test (aarch64-pc-windows-msvc): windows_sandbox_lifecycle 180,
    wxc_windows_sandbox_daemon 27 — all pass.

Copilot AI review requested due to automatic review settings July 17, 2026 04:44
@MGudgin
MGudgin requested a review from a team as a code owner July 17, 2026 04:44
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

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

Hardens the Windows Sandbox state-aware lifecycle + detached daemon by improving observability, preventing single-flight wedging on streaming exec, adding explicit VM-slot contention signaling, and versioning the daemon IPC protocol to avoid driving stale/orphaned daemons with mismatched framing.

Changes:

  • Capture the detached daemon’s diagnostics to a per-sandbox daemon.log, and surface the concrete log path on start-time daemon failures.
  • Add a host-side watchdog to streaming EXEC so a frozen-but-alive guest can’t wedge the single-flight slot indefinitely.
  • Introduce IPC protocol versioning in DaemonRecord and refuse streaming exec against incompatible live daemons; add a distinct daemon exit code for VM-slot contention and map it to backend_unavailable.
Show a summary per file
File Description
src/backends/windows_sandbox/lifecycle/src/state_aware.rs Adds daemon log path handling, VM-slot-busy classification, and protocol compatibility checks before streaming exec.
src/backends/windows_sandbox/lifecycle/src/control_plane.rs Defines IPC protocol versioning + VM-slot-busy exit code and extends DaemonRecord with protocol_version.
src/backends/windows_sandbox/lifecycle/src/bridge.rs Exposes the host watchdog grace + deadline helper so the daemon can share one-shot watchdog semantics.
src/backends/windows_sandbox/daemon/src/main.rs Implements distinct VM-slot-busy exit behavior and stamps daemon IPC protocol version into the record.
src/backends/windows_sandbox/daemon/src/control_server.rs Wraps streaming exec in a watchdog and synthesizes a terminal exit frame on watchdog expiry while poisoning the slot.
docs/windows-sandbox/windows-sandbox-reference.md Documents daemon.log, streaming watchdog behavior, VM-slot busy mapping, and protocol versioning behavior.

Review details

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

Comment thread src/backends/windows_sandbox/daemon/src/main.rs Outdated
@MGudgin
MGudgin force-pushed the user/gudge/wsb-daemon-hardening branch 2 times, most recently from afa7b0e to c762738 Compare July 17, 2026 15:42
Comment thread src/backends/windows_sandbox/lifecycle/src/control_plane.rs
Comment thread src/backends/windows_sandbox/daemon/src/control_server.rs Outdated
@MGudgin
MGudgin force-pushed the user/gudge/wsb-daemon-hardening branch 2 times, most recently from 531cd4e to 038a9f5 Compare July 23, 2026 00:15
@MGudgin MGudgin changed the title Harden the Windows Sandbox state-aware daemon (Phase 2 follow-ups) Harden the Windows Sandbox state-aware daemon and lifecycle Jul 23, 2026
This change hardens the Windows Sandbox state-aware daemon and lifecycle: it
makes daemon diagnostics observable, bounds a frozen guest with a
cancellation-safe exec watchdog, reports VM-slot contention coherently, and
versions the client/daemon IPC wire protocol behind a bilateral, daemon-enforced
handshake.

Details:
* Daemon diagnostics are captured. spawn_daemon redirects the detached daemon's
  stderr to daemon.log in the per-sandbox owner-only record directory (append),
  so boot / teardown / orphan-reclaim failures in the VM-owning process are
  observable; the start-timeout error names the concrete log path.
* The streaming exec path has a cancellation-safe host-side watchdog. The
  deadline lives inside stream_exec_on_guest's select loop and is honoured only
  at frame boundaries, so a timeout can never truncate a data frame and desync
  the client's decoder; on expiry the daemon marks the guest slot Unusable and
  sends a clean synthetic timeout exit frame, so a frozen-but-alive guest cannot
  wedge the single-flight slot indefinitely.
* VM-slot contention is reported coherently. HostVmLock::try_acquire
  distinguishes genuine contention (the wait timed out) from a real create/wait
  failure: on contention the daemon exits DAEMON_EXIT_VM_SLOT_BUSY, which start
  maps to backend_unavailable, while a real mutex failure propagates as an
  opaque backend error rather than being mis-reported as busy.
* The IPC wire protocol is versioned behind a bilateral, daemon-enforced
  handshake. The client declares its version as the trailing token of the EXEC
  line (EXEC <nonce> <proto>); the daemon refuses a mismatched or malformed
  version with ERR protocol before any frame I/O, so a differently-framed
  request is never parsed. An absent token maps to LEGACY_IPC_PROTOCOL_VERSION
  so a future daemon cleanly refuses a legacy client. DaemonRecord also carries
  protocol_version as a client-side fast-path (ensure_daemon_protocol).
  stop/deprovision keep using the stable single-line STOP so an orphaned VM can
  always be reclaimed.
* Docs: windows-sandbox-reference.md covers the daemon.log sink, the exec
  watchdog, the VM-slot busy mapping, and the wire-protocol handshake.

Testing:
* Unit tests: protocol_compatible + serde-default record case, parse_client_protocol,
  ensure_daemon_protocol accept/refuse, the vm_slot_busy exit-code classifier, a
  two-thread named_mutex_try_acquire (contended mutex yields Ok(None), not Err),
  daemon EXEC matching/mismatched/malformed-version handshake, the ERR protocol
  status mapping to backend_unavailable, and a watchdog frame-boundary regression
  asserting no partial data frame precedes the terminal frame.
* cargo test (aarch64-pc-windows-msvc): windows_sandbox_lifecycle 180,
  wxc_windows_sandbox_daemon 27 pass; cargo fmt --all -- --check and cargo clippy
  (affected crates, -D warnings) clean.

Co-authored-by: gudge <gudge@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Generated-with: claude-opus-4.8
Copilot-Session: 04bee154-4393-4733-b45f-994a4e9664f5
@MGudgin
MGudgin force-pushed the user/gudge/wsb-daemon-hardening branch from 038a9f5 to 7126cdd Compare July 23, 2026 00:52
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