//go:build linux package hostbox import ( "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostguard" "golang.org/x/sys/unix" ) func TestLaunchIntentionRecoveryRefusesForeignChildren(t *testing.T) { for _, mode := range []string{"absent", "empty", "populated", "descendant", "symlink", "parent-replaced", "reboot-absent-parent", "reboot-absent-ancestor", "reboot-reused-parent"} { t.Run(mode, func(t *testing.T) { ctx := t.Context() root := t.TempDir() parentPath := filepath.Join(root, "delegate", "parent") if err := os.MkdirAll(parentPath, 0700); err != nil { t.Fatal(err) } var st unix.Stat_t if err := unix.Stat(parentPath, &st); err != nil { t.Fatal(err) } parent := LaunchGroup{RelativePath: "delegate/parent", Device: uint64(st.Dev), Inode: st.Ino} target := hostguard.FenceTarget{Host: "app.test", Incarnation: "inc", Stamp: 1, Session: 1, Box: "box", Enrollment: "enroll", BootID: "boot", ControllerID: "ctrl"} j, err := OpenLaunchJournal(filepath.Join(t.TempDir(), "launch.db")) if err != nil { t.Fatal(err) } defer j.Close() if err := j.Prelaunch(ctx, LaunchInventory{Target: target, ControllerPID: 42, ProcessStart: "start", Groups: []LaunchGroup{parent}, NoState: true}); err != nil { t.Fatal(err) } r := LaunchReservation{Target: target, ID: "first:main", Role: "main", InvocationID: "first", Inputs: "inputs", Parent: parent, Group: LaunchGroup{RelativePath: "delegate/parent/role-first", InvocationID: "first"}, Executable: LaunchFile{Device: 1, Inode: 2}} if err := j.IntendRole(ctx, r); err != nil { t.Fatal(err) } childPath := filepath.Join(root, r.Group.RelativePath) reboot := false switch mode { case "reboot-absent-ancestor": reboot = true if err := os.RemoveAll(filepath.Join(root, "delegate")); err != nil { t.Fatal(err) } case "reboot-absent-parent": reboot = true if err := os.Remove(parentPath); err != nil { t.Fatal(err) } case "reboot-reused-parent": reboot = true case "parent-replaced": if err := os.Rename(parentPath, parentPath+".old"); err != nil { t.Fatal(err) } if err := os.Mkdir(parentPath, 0700); err != nil { t.Fatal(err) } case "symlink": if err := os.Symlink(t.TempDir(), childPath); err != nil { t.Fatal(err) } case "empty", "populated", "descendant": if err := os.Mkdir(childPath, 0700); err != nil { t.Fatal(err) } value := "populated 0\n" if mode == "populated" { value = "populated 1\n" } if err := os.WriteFile(filepath.Join(childPath, "cgroup.events"), []byte(value), 0600); err != nil { t.Fatal(err) } if mode == "descendant" { if err := os.Mkdir(filepath.Join(childPath, "unknown"), 0700); err != nil { t.Fatal(err) } } } err = j.ResolveRoleIntentions(ctx, target, root, reboot) accepted := mode == "absent" || mode == "empty" || mode == "reboot-absent-parent" || mode == "reboot-absent-ancestor" if (err == nil) != accepted { t.Fatalf("accepted=%v err=%v", accepted, err) } in, readErr := j.Read(ctx, target) if readErr != nil { t.Fatal(readErr) } want := "intended" if accepted { want = "settled" } if in.Reservations[0].State != want { t.Fatalf("changed refused intention: %+v", in) } if accepted { if err := j.ResolveRoleIntentions(ctx, target, root, reboot); err != nil { t.Fatal("replay", err) } } }) } }