Harden the Windows Sandbox state-aware daemon and lifecycle#658
Open
MGudgin wants to merge 1 commit into
Open
Conversation
|
Azure Pipelines: There may be pipelines that require an authorized user to comment /azp run to run. |
Contributor
There was a problem hiding this comment.
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
EXECso a frozen-but-alive guest can’t wedge the single-flight slot indefinitely. - Introduce IPC protocol versioning in
DaemonRecordand refuse streaming exec against incompatible live daemons; add a distinct daemon exit code for VM-slot contention and map it tobackend_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
MGudgin
force-pushed
the
user/gudge/wsb-daemon-hardening
branch
2 times, most recently
from
July 17, 2026 15:42
afa7b0e to
c762738
Compare
huzaifa-d
reviewed
Jul 21, 2026
huzaifa-d
reviewed
Jul 21, 2026
MGudgin
force-pushed
the
user/gudge/wsb-daemon-hardening
branch
2 times, most recently
from
July 23, 2026 00:15
531cd4e to
038a9f5
Compare
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
force-pushed
the
user/gudge/wsb-daemon-hardening
branch
from
July 23, 2026 00:52
038a9f5 to
7126cdd
Compare
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.
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
[wsb-daemon]diagnostics, and the VM-owning detached process is exactly where boot /
teardown / orphan-reclaim failures need to be observable.
spawn_daemonredirects the daemon's stderr to
daemon.login the per-sandbox owner-onlyrecord directory (append), and the start-timeout error names the concrete log
path.
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'sselect!loop and ishonoured 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).
HostVmLock::try_acquiredistinguishes genuine contention (the wait timed out) from a real create/wait
failure: on contention the daemon exits with a distinct
DAEMON_EXIT_VM_SLOT_BUSYcode thatstartmaps tobackend_unavailable,while a real mutex failure propagates as an opaque backend error (with
daemon.log diagnostics) rather than being mis-reported as busy. (
startdoesnot 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.)
handshake. The client declares its version as the trailing token of the
EXECline (EXEC <nonce> <proto>); the daemon refuses a mismatched ormalformed version with
ERR protocolbefore any frame I/O, so adifferently-framed request is never parsed. An absent token maps to
LEGACY_IPC_PROTOCOL_VERSION, so a future daemon cleanly refuses a legacyclient instead of misframing it.
DaemonRecordalso carries aprotocol_version(added with#[serde(default)]so a pre-versioning recordreads 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/deprovisionkeep using the stable single-line
STOPcommand (explicitOKack) so anorphaned VM can always be reclaimed.
docs/windows-sandbox/windows-sandbox-reference.mdcovers thedaemon.log sink, the exec watchdog, the VM-slot busy mapping, and the
wire-protocol handshake.
Tests
protocol_compatibleand a serde-default case (a pre-versioningrecord deserialises and reads back protocol-incompatible),
parse_client_protocol,ensure_daemon_protocolaccept/refuse, thevm_slot_busyexit-codeclassifier, a two-thread
named_mutex_try_acquiretest asserting a contendedmutex returns
Ok(None)(busy) rather thanErr, the daemonEXECmatching/mismatched/malformed-version handshake, the
ERR protocolstatusmapping to
backend_unavailable, and a watchdog frame-boundary regressionasserting no partial data frame precedes the terminal frame.
cargo fmt --all -- --checkandcargo clippy(affected crates,-D warnings): clean.cargo test(aarch64-pc-windows-msvc): windows_sandbox_lifecycle 180,wxc_windows_sandbox_daemon 27 — all pass.