agent v0.36.4: serialize AttachDrive/DetachDrive (no double-bind TOCTOU race)

A GuestBinder mutex prevents a concurrent reconnect + periodic reconcile from
both passing isHostMountpoint and double-binding a stable path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 20:19:07 +02:00
parent cff9284453
commit a356d6def4
4 changed files with 16 additions and 1 deletions
+5
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"log/slog"
"strconv"
"sync"
"gitea.dooplex.hu/admin/felhom-agent/internal/proxmox"
)
@@ -35,6 +36,10 @@ const felhomDataNS = "felhom-data"
type GuestBinder struct {
runner proxmox.Runner
logger *slog.Logger
// mountMu serializes AttachDrive/DetachDrive so a concurrent reconnect (controller-triggered) and the
// agent's periodic reconcile can't both pass the isHostMountpoint check and double-bind the same
// stable path (a TOCTOU race — observed live as 2 stacked binds).
mountMu sync.Mutex
}
// NewGuestBinder builds a binder over the given root-CLI runner.
+4
View File
@@ -151,6 +151,8 @@ func (b *GuestBinder) installSharedParentUnit(ctx context.Context) error {
// (umount + mount) to fire a fresh propagation event into the current guest namespace. Idempotent when
// the guest already sees it.
func (b *GuestBinder) AttachDrive(ctx context.Context, vmid int, where string) (string, error) {
b.mountMu.Lock() // serialize vs a concurrent DetachDrive/AttachDrive (no double-bind TOCTOU)
defer b.mountMu.Unlock()
stable := StablePathForRaw(where)
if stable == "" {
return "", fmt.Errorf("guest-attach: %q is not a /mnt/<name> mount", where)
@@ -289,6 +291,8 @@ func hostBtime() string {
// leaving the bare HOST-ROOT-owned stable dir → fail-closed (the guest can't write to it even as root,
// since host uid 0 is unmapped). No pct, no reboot. Idempotent: a non-mountpoint is a no-op.
func (b *GuestBinder) DetachDrive(ctx context.Context, where string) error {
b.mountMu.Lock() // serialize vs a concurrent AttachDrive (so detach can't race a re-bind)
defer b.mountMu.Unlock()
stable := StablePathForRaw(where)
if stable == "" {
return fmt.Errorf("guest-detach: %q is not a /mnt/<name> mount", where)