Skip to content

Add Windows snapshots and forks - #433

Open
sjmiller609 wants to merge 8 commits into
hypeship/windows-networkingfrom
hypeship/windows-snapshots
Open

Add Windows snapshots and forks#433
sjmiller609 wants to merge 8 commits into
hypeship/windows-networkingfrom
hypeship/windows-snapshots

Conversation

@sjmiller609

@sjmiller609 sjmiller609 commented Aug 20, 2026

Copy link
Copy Markdown
Collaborator

summary

  • enable Windows standby, restore, stopped snapshots, and forks
  • preserve disk, Secure Boot NVRAM, TPM state, and QEMU configuration for same-instance restores
  • assign fork identity according to memory-restore versus cold-boot semantics
  • require forkable Windows images to declare bitlocker=disabled
  • guard captured VioSock CIDs and rotate them on cold starts
  • reduce final Windows KVM coverage to one comprehensive guest/network/standby/memory-fork lifecycle scenario plus one stopped-fork scenario
  • document snapshot identity, TPM, VioSock, and BitLocker decisions in the instance README

targeted tests

  • Windows image-policy, fork-identity, and hypervisor-config unit tests
  • final lifecycle and stopped-fork integration test compilation/skip paths

Long-running KVM and full CI suites were not run for this review pass.


Note

High Risk
Changes core VM lifecycle (snapshots, forks, TPM, vsock CIDs) and guest machine identity; incorrect semantics could cause CID collisions, shared TPM attestation, or unsafe forked BitLocker disks.

Overview
Enables standby, restore, stopped snapshots, and forks for Windows QEMU instances by removing the prior blanket rejection of snapshot lifecycle operations on windows/amd64.

Fork admission and BitLocker: Windows images can declare io.hypeman.machine-image.bitlocker (disabled vs reseal-required). Only disabled images may be forked; the policy is stored on the instance at create time. Same-instance snapshots remain allowed for other policies because Hypeman does not clone BitLocker-encrypted disks without resealing to a child TPM.

Fork identity semantics: Stopped (cold) forks clear copied TPM state, assign a new vsock CID on start, and call the guest agent to set a new MachineGuid. Memory (standby) forks keep the parent’s TPM and captured VioSock CID in guest memory; restore blocks concurrent use when another running instance shares that CID, and running forks from a live Windows source require target_state=Stopped (or stopping the snapshot source before a running standby fork).

Guest agent: Adds RebindIdentity gRPC (registry MachineGuid + Hypeman instance marker) and host-side RebindInstanceIdentity, invoked on start/restore when WindowsIdentityPending is set after a fork.

CI and docs: New KVM integration test for stopped forks alongside the expanded lifecycle test; docs/windows-snapshots.md and image label docs describe TPM, vsock, and identity behavior.

Reviewed by Cursor Bugbot for commit 8c18bc9. Bugbot is set up for automated code reviews on this repo. Configure here.

@sjmiller609
sjmiller609 force-pushed the hypeship/windows-snapshots branch 3 times, most recently from ddd1d35 to 7886185 Compare August 20, 2026 14:14
@sjmiller609
sjmiller609 force-pushed the hypeship/windows-snapshots branch from 7886185 to 2723b54 Compare August 20, 2026 19:26
@sjmiller609
sjmiller609 force-pushed the hypeship/windows-snapshots branch 2 times, most recently from 19ab53d to ec253b0 Compare August 20, 2026 23:41
@sjmiller609
sjmiller609 force-pushed the hypeship/windows-snapshots branch from ec253b0 to 8c18bc9 Compare August 21, 2026 00:13
@sjmiller609
sjmiller609 marked this pull request as ready for review August 25, 2026 20:48

@cursor cursor Bot 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.

Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8c18bc9. Configure here.

Comment thread lib/instances/fork.go
resetWindowsTPM := source.State != StateStandby
if err := m.prepareWindowsForkIdentity(&forkMeta, resetWindowsTPM); err != nil {
return nil, false, err
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stopped Windows forks keep parent TPM

High Severity

TPM reset is keyed only on whether the source is currently standby, not on whether the child will cold-boot. A running Windows source can only be forked with target_state=Stopped, but that path standbys first and then treats the child as a memory fork, so resetWindowsTPM stays false. applyForkTargetState later discards the snapshot without clearing the copied TPM directory. The child therefore cold-boots with the parent's endorsement key, despite stopped-fork semantics promising a new TPM identity.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8c18bc9. Configure here.

Comment thread lib/instances/windows.go
return fmt.Errorf("%w: Windows snapshot restore requires instance %s with the same captured vsock CID to be stopped", ErrInvalidState, instance.Id)
}
}
return nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Windows vsock CID check is racy

Medium Severity

ensureWindowsVsockCIDAvailable only snapshots currently running instances and does not reserve the captured VioSock CID. Memory-restored Windows forks inherit that CID, and restore of source versus child uses different instance locks, so two restores can both pass the check and start QEMU with the same guest-cid. That breaks the invariant that no two concurrently runnable instances share a Vsock CID and can fail as a host vsock collision instead of a state error.

Additional Locations (1)
Fix in Cursor Fix in Web

Triggered by learned rule: Stopped forks must get fresh VsockCIDs before they can be started

Reviewed by Cursor Bugbot for commit 8c18bc9. Configure here.

Comment thread lib/instances/fork.go
}
if isWindowsPlatform(source.Platform) && source.State == StateRunning && targetState != StateStopped {
return nil, "", false, fmt.Errorf("%w: Windows forks from a running source require target_state=%s", ErrNotSupported, StateStopped)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

BitLocker fork check runs after standby

Medium Severity

The new Windows from-running gate only rejects a non-Stopped target state before mutating the source. validateWindowsForkPolicy runs later inside forkInstanceFromStoppedOrStandby, after the running source has already been put into standby. A fork of a Windows VM whose image is unlabeled or reseal-required therefore pauses and snapshots a live guest, then fails admission. Restore is best-effort; if it fails the source is left in standby.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 8c18bc9. Configure here.

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.

1 participant