daemon: persist teardown fallbacks and reject unsafe import paths
Preserve cleanup after daemon restarts and harden OCI and tar imports against filenames that debugfs cannot encode safely. Mirror tap, loop, and dm teardown identity onto VM.Runtime, teach cleanup and reconcile to fall back to those persisted fields when handles.json is missing or corrupt, and clear the recovery state on stop, error, and delete paths. Reject debugfs-hostile entry names during flattening and in ApplyOwnership itself, then add regression coverage for corrupt handles.json recovery and unsafe import paths. Verified with targeted go tests, make lint-go, make lint-shell, and make build.
This commit is contained in:
parent
86a56fedb3
commit
d743a8ba4b
15 changed files with 272 additions and 81 deletions
|
|
@ -175,6 +175,66 @@ func TestReconcileStopsStaleRunningVMAndClearsRuntimeHandles(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReconcileWithCorruptHandlesFileFallsBackToPersistedRuntimeTeardownState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
ctx := context.Background()
|
||||
db := openDaemonStore(t)
|
||||
apiSock := filepath.Join(t.TempDir(), "fc.sock")
|
||||
if err := os.WriteFile(apiSock, []byte{}, 0o644); err != nil {
|
||||
t.Fatalf("WriteFile(api sock): %v", err)
|
||||
}
|
||||
vmDir := t.TempDir()
|
||||
vm := testVM("corrupt", "image-corrupt", "172.16.0.10")
|
||||
vm.State = model.VMStateRunning
|
||||
vm.Runtime.State = model.VMStateRunning
|
||||
vm.Runtime.APISockPath = apiSock
|
||||
vm.Runtime.VMDir = vmDir
|
||||
vm.Runtime.DNSName = ""
|
||||
vm.Runtime.TapDevice = "tap-fc-corrupt"
|
||||
vm.Runtime.BaseLoop = "/dev/loop20"
|
||||
vm.Runtime.COWLoop = "/dev/loop21"
|
||||
vm.Runtime.DMName = "fc-rootfs-corrupt"
|
||||
vm.Runtime.DMDev = "/dev/mapper/fc-rootfs-corrupt"
|
||||
upsertDaemonVM(t, ctx, db, vm)
|
||||
|
||||
if err := os.WriteFile(handlesFilePath(vmDir), []byte("{not json"), 0o600); err != nil {
|
||||
t.Fatalf("WriteFile(handles.json): %v", err)
|
||||
}
|
||||
|
||||
runner := &scriptedRunner{
|
||||
t: t,
|
||||
steps: []runnerStep{
|
||||
{call: runnerCall{name: "pgrep", args: []string{"-n", "-f", apiSock}}, err: errors.New("exit status 1")},
|
||||
sudoStep("", nil, "dmsetup", "remove", "fc-rootfs-corrupt"),
|
||||
sudoStep("", nil, "losetup", "-d", "/dev/loop21"),
|
||||
sudoStep("", nil, "losetup", "-d", "/dev/loop20"),
|
||||
sudoStep("", nil, "ip", "link", "del", "tap-fc-corrupt"),
|
||||
},
|
||||
}
|
||||
d := &Daemon{store: db, runner: runner}
|
||||
wireServices(d)
|
||||
|
||||
if err := d.reconcile(ctx); err != nil {
|
||||
t.Fatalf("reconcile: %v", err)
|
||||
}
|
||||
runner.assertExhausted()
|
||||
|
||||
got, err := db.GetVM(ctx, vm.ID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetVM: %v", err)
|
||||
}
|
||||
if got.State != model.VMStateStopped || got.Runtime.State != model.VMStateStopped {
|
||||
t.Fatalf("vm state after reconcile = %s/%s, want stopped", got.State, got.Runtime.State)
|
||||
}
|
||||
if got.Runtime.TapDevice != "" || got.Runtime.BaseLoop != "" || got.Runtime.COWLoop != "" || got.Runtime.DMName != "" || got.Runtime.DMDev != "" {
|
||||
t.Fatalf("runtime teardown state not cleared after reconcile: %+v", got.Runtime)
|
||||
}
|
||||
if _, err := os.Stat(handlesFilePath(vmDir)); !os.IsNotExist(err) {
|
||||
t.Fatalf("handles.json still present after reconcile: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRebuildDNSIncludesOnlyLiveRunningVMs(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue