fix(AGENT-001): anti-retarget re-resolution for inline customer-confirmed wipe
handleDiskFormat's customer-confirmed branch formatted the mutable req.Device path; the durable id only bound the confirmation, never the mkfs target. A /dev reassignment between inspect and mkfs could wipe the wrong physical disk. Now mirrors signedjobs.WipeExecutor: resolve confirmed durable id -> current device, re-derive + require exact match, re-inspect (still data-bearing), then format THAT device. Any refusal -> 409, no mkfs. New antiRetargetResolve helper (injected deps, unit-tested: mismatch/gone/blank/empty all refuse). Injectable reresolveWipe seam on Server (defaults to real storage funcs). BRANCH ONLY — pending supervised review/deploy (see AGENT-001-FIX-NOTES.md). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,71 @@
|
||||
package localapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.dooplex.hu/admin/felhom-agent/internal/storage"
|
||||
)
|
||||
|
||||
// antiRetargetResolve mirrors signedjobs.WipeExecutor.Execute's anti-retarget
|
||||
// sequence for the inline customer-confirmed wipe path [AGENT-001].
|
||||
//
|
||||
// The bug: handleDiskFormat inspected and gate-bound the device by its durable
|
||||
// id but then formatted the mutable caller-supplied /dev path (req.Device). If a
|
||||
// USB re-enumeration reassigned that /dev node to a DIFFERENT physical disk
|
||||
// between inspection and mkfs, the wipe hit the wrong drive — the classic
|
||||
// classify→mkfs TOCTOU the signed-jobs path already guards against.
|
||||
//
|
||||
// The fix: resolve the confirmed durable id to the CURRENT device, re-derive the
|
||||
// device's durable id and require an exact match (a path now pointing at a
|
||||
// different disk derives a different id → refuse), then re-inspect and require it
|
||||
// to STILL be data-bearing. Returns the re-resolved device to format — never the
|
||||
// caller-supplied path. resolve/derive/inspect are injected so this is unit
|
||||
// testable without touching real /dev (the Server wires the real storage funcs).
|
||||
func antiRetargetResolve(
|
||||
durableID string,
|
||||
resolve func(string) (string, error),
|
||||
derive func(string) (string, error),
|
||||
inspect func(string) (storage.DeviceProbe, error),
|
||||
) (string, error) {
|
||||
if durableID == "" {
|
||||
// A path-only binding is exactly what the durable id exists to prevent.
|
||||
return "", fmt.Errorf("no durable_id — refusing a path-only wipe binding")
|
||||
}
|
||||
device, err := resolve(durableID)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("durable id %q no longer resolves (device removed/replaced?) — refusing: %w", durableID, err)
|
||||
}
|
||||
got, err := derive(device)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("cannot re-derive durable id for %s — refusing: %w", device, err)
|
||||
}
|
||||
if got != durableID {
|
||||
return "", fmt.Errorf("durable-id mismatch — %s now has id %q, confirmed id was %q — refusing", device, got, durableID)
|
||||
}
|
||||
probe, err := inspect(device)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("re-inspect %s failed — refusing: %w", device, err)
|
||||
}
|
||||
if !probe.Probed {
|
||||
return "", fmt.Errorf("%s did not probe cleanly at execution — refusing", device)
|
||||
}
|
||||
if !probe.DataBearing() {
|
||||
// The customer confirmed wiping a DATA-BEARING device; if it is now blank,
|
||||
// the target changed since confirmation — refuse rather than wipe blindly.
|
||||
return "", fmt.Errorf("%s is no longer data-bearing (target changed since confirmation) — refusing", device)
|
||||
}
|
||||
return device, nil
|
||||
}
|
||||
|
||||
// reresolveDurableForWipe wires antiRetargetResolve with the real storage funcs
|
||||
// and the server's disk inspector. Returns the device to format, or an error to
|
||||
// refuse on.
|
||||
func (s *Server) reresolveDurableForWipe(ctx context.Context, durableID string) (string, error) {
|
||||
return antiRetargetResolve(
|
||||
durableID,
|
||||
storage.ResolveDurableDevice,
|
||||
storage.DeviceDurableID,
|
||||
func(dev string) (storage.DeviceProbe, error) { return s.disks.InspectDevice(ctx, dev) },
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user