Skip to content

fix(store): do not trust PID files left behind by a previous boot - #5437

Merged
AkihiroSuda merged 1 commit into
lima-vm:masterfrom
ekalinin:fix/stale-pid-files
Aug 24, 2026
Merged

fix(store): do not trust PID files left behind by a previous boot#5437
AkihiroSuda merged 1 commit into
lima-vm:masterfrom
ekalinin:fix/stale-pid-files

Conversation

@ekalinin

@ekalinin ekalinin commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Problem

store.ReadPIDFile() decides whether a PID file belongs to a running process by signaling the PID. Within a single boot that is fine: the PID file of a process that has already exited is removed. Across a reboot it is not, because the recorded PID may have been reused by an unrelated process. osutil.ProcessAlive() also treats EPERM as "alive", which makes a false positive likely when the reused PID belongs to a process of another user.

The instance is then reported as Broken, either with <vmtype> driver is running but host agent is not, or by trying to connect to a stale ha.sock. limactl start fails immediately, and a LaunchDaemon with KeepAlive crash-loops it.

This is the root cause of bug 2 of #5087 and of #5075.

Changes

  • osutil.BootSessionID() returns an identifier of the current boot: the kern.bootsessionuuid sysctl on macOS, /proc/sys/kernel/random/boot_id on Linux. On other platforms it returns ErrBootSessionNotSupported and nothing changes.
  • store.WritePIDFile() writes a PID file and keeps a boot-session.tmp marker in the same directory. ReadPIDFile() ignores a PID file whose marker is from a different boot, and never signals its PID.
  • The host agent, the vz driver and the krunkit driver write their PID files through WritePIDFile(). qemu.pid is written by QEMU itself, but the marker is per instance directory, so it is covered too.
  • StartWithPaths() checked for ha.pid with os.Stat(), which bypassed all of this; it now uses ReadPIDFile(), like limactl hostagent already did.

The marker is shared by the PID files of a directory, so the ordering matters:

  • A writer that finds a marker of a previous boot removes every PID file of the directory before it refreshes the marker. Refreshing the marker must never make a PID file of a previous boot look current.
  • A reader checks the boot before it reads the PID, and leaves the removal to the next writer, so that a reader cannot remove a PID file that another process is writing at that very moment.
  • The PID file itself is renamed into place, so a reader never sees a partially written one.
  • When the boot session ID cannot be determined, the PID file is not written at all: a marker left over from a previous boot would otherwise void a PID file that is current. Only the platforms without support for a boot session ID fall back to the previous behavior.

An instance that was started by an older version of Lima has no marker, and keeps the previous behavior.

The .tmp suffix of the marker is deliberate: it is then already covered by the cleanup in StopForcibly() and by the skip list used when cloning an instance.

Testing

go test ./pkg/store/... covers writing the marker, a PID file from a previous boot (the PID used in the test is alive, and is still reported as gone), the sweep of the PID files of a previous boot, a truncated PID file from a previous boot, a failure to determine the boot session ID on both the read and the write path, instances without a marker, and platforms without support for a boot session ID.

go test ./..., go test -race ./pkg/store/..., make golangci-lint and cross builds for linux and windows are clean.

Assisted-by: Claude Code

@AkihiroSuda

Copy link
Copy Markdown
Member

Within a single boot that is fine: the PID file of a process that has already exited is removed. Across a reboot it is not, because the recorded PID may have been reused by an unrelated process.

Even within a single boot, PID can be reused. Less likely though.

Comment thread pkg/store/pidfile.go
Comment thread pkg/store/pidfile.go Outdated
@ekalinin
ekalinin force-pushed the fix/stale-pid-files branch from 83e2073 to c24a290 Compare August 23, 2026 18:57
`ReadPIDFile` decides whether a PID file belongs to a running process by signaling
the PID. Within a single boot this is fine, because a PID file of a process that
has already exited gets removed. Across a reboot it is not: the recorded PID may
have been reused by an unrelated process, and Lima then reports the instance as
`Broken`, either with "driver is running but host agent is not", or by trying to
connect to a stale `ha.sock`. `limactl start` fails, and a LaunchDaemon with
`KeepAlive` crash-loops it.

Record the boot session ID of the host next to the PID files of an instance, and
ignore a PID file that was written during a different boot. The ID is read from
the `kern.bootsessionuuid` sysctl on macOS and from
`/proc/sys/kernel/random/boot_id` on Linux; on platforms without such an
identifier the behavior is unchanged, as it is for instances that were started by
an older version of Lima and have no marker.

The marker is shared by the PID files of the directory, so writing a PID file
during a new boot removes all of them before the marker is refreshed: refreshing
it must never make a PID file of a previous boot look current. Readers check the
boot before they read the PID, and leave the removal to the next writer, so that
a reader can never remove a PID file that another process is writing at that very
moment. The PID file itself is renamed into place.

When the boot session ID cannot be determined the PID file is not written at all,
because a marker left over from a previous boot would otherwise void a PID file
that is current. Only the platforms without support for a boot session ID fall
back to the previous behavior.

The PID of a stale PID file is never signaled, because it may belong to an
unrelated process.

`StartWithPaths` checked for `ha.pid` with `os.Stat`, which bypassed this logic;
it now uses `ReadPIDFile` as well.

Related to lima-vm#5087
Related to lima-vm#5075

Signed-off-by: Eugene Kalinin <e.v.kalinin@gmail.com>
@ekalinin
ekalinin force-pushed the fix/stale-pid-files branch from c24a290 to e529ce0 Compare August 23, 2026 19:13
@AkihiroSuda AkihiroSuda added this to the v2.3.0 milestone Aug 24, 2026

@AkihiroSuda AkihiroSuda left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks

@AkihiroSuda
AkihiroSuda merged commit 1341f0f into lima-vm:master Aug 24, 2026
36 checks passed
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.

2 participants