From 121b05978e462cd6c38fe48ad6b42609a5a03c5b Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Mon, 7 Sep 2026 22:57:15 -0300 Subject: [PATCH 1/2] Split the fingerprint's identity out, so a memo can key on all of it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reading the three files is the expensive half of a fingerprint — 76 MB of SHA-256 — and a caller that memoises it needs the other half whole to key the memo on. There was no way to get it: shape was reachable, the device topology and the conditional host CPU were not, so a caller had to keep a hand-written list of the fields it thought mattered. That list goes stale silently, and the symptom is a stale fingerprint — a template matching a machine it does not describe, restored into rather than refused. Identity is that half, and Fingerprint is now the files plus it, so there is nothing to keep in step. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a --- machine/machine.go | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/machine/machine.go b/machine/machine.go index 89fa3c7..4ae1b94 100644 --- a/machine/machine.go +++ b/machine/machine.go @@ -642,8 +642,6 @@ func (s Spec) Args() ([]string, error) { // every VM restored from one have their memory in a file whatever the spec being // asked was configured with. func (s Spec) Fingerprint() (string, error) { - shape := s.TemplateShape() - h := sha256.New() // Length-prefixed, so that no two different machines can produce the same @@ -675,6 +673,32 @@ func (s Spec) Fingerprint() (string, error) { } write(f.name, sum) } + ident, err := s.Identity() + if err != nil { + return "", err + } + write("identity", ident) + + return hex.EncodeToString(h.Sum(nil)), nil +} + +// Identity is everything the fingerprint hashes except the contents of those +// three files: the machine's shape, its device topology, and the host's own CPU +// when the guest is being shown it. +// +// It is separate because reading the three files is the expensive half — 76 MB +// of SHA-256, 29 ms on a machine measured — and a caller that memoises that half +// needs the other half whole to key the memo on. Under-keying it is the failure +// that has no symptom: a stale fingerprint is a template that matches a machine +// it does not describe, and a restore into it is undefined rather than an error. +// So there is no list here for a caller to keep in step; there is this. +func (s Spec) Identity() (string, error) { + shape := s.TemplateShape() + + var b strings.Builder + write := func(key, value string) { + _, _ = fmt.Fprintf(&b, "%s=%d:%s\n", key, len(value), value) + } write("machine", shape.Machine) write("cpu", shape.CPU) write("smp", shape.SMP) @@ -703,7 +727,7 @@ func (s Spec) Fingerprint() (string, error) { write("host-cpu", cpu) } - return hex.EncodeToString(h.Sum(nil)), nil + return b.String(), nil } // topology is the machine's device list — which models, at which slots — with From 4a006f3f2f334c7ff0e8e822b4d52812ac8e92ca Mon Sep 17 00:00:00 2001 From: Manuel de Brito Fontes Date: Mon, 7 Sep 2026 22:59:26 -0300 Subject: [PATCH 2/2] tsc=reliable, which was left behind when the command line moved here MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A statement about this machine's hardware, so it belongs with the rest of them: the clocksource watchdog exists for silicon whose TSC drifts or stops, and this machine's TSC is the host's, which KVM advertises as invariant and the guest is told about through CPUID. It was on the command line of the project this one was extracted from and did not come across — an omission, not a decision, found by diffing the two. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_019GHRRFvbnZi5q3KSTvyW5a --- machine/cmdline.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/machine/cmdline.go b/machine/cmdline.go index e2e7b71..27d8094 100644 --- a/machine/cmdline.go +++ b/machine/cmdline.go @@ -134,8 +134,13 @@ func (c Cmdline) String() string { // Timing shortcuts a KVM guest can take: // no_timer_check skip the boot-time timer IRQ delivery probe, // which exists for hardware that misroutes it. + // tsc=reliable trust the TSC and skip the clocksource watchdog. + // The watchdog exists for silicon whose TSC drifts + // or stops; this machine's TSC is the host's, which + // KVM advertises as invariant, and the guest is + // told so through CPUID. // rcupdate.rcu_expedited=1 expedite RCU grace periods during boot. - parts = append(parts, "no_timer_check", "rcupdate.rcu_expedited=1") + parts = append(parts, "no_timer_check", "tsc=reliable", "rcupdate.rcu_expedited=1") parts = append(parts, c.Extra...)