//go:build linux package host import ( "context" "os" "path/filepath" "testing" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/release" "github.com/carlosframework/platform/internal/rootparticipation" "golang.org/x/sys/unix" ) func TestPlan14GenerationPayloadPhysicalIdentity(t *testing.T) { for _, attack := range []string{"none", "directory", "missing", "replaced", "symlink", "parent-symlink", "root-symlink", "wrong-device", "wrong-inode", "wrong-type", "intended", "intended-missing", "escape", "unknown-root", "changed-during-kernel"} { t.Run(attack, func(t *testing.T) { f, disk, _ := generationLaunchFixture(t) if _, err := f.root.Serve(t.Context(), f.peer, f.req); err != nil { t.Fatal(err) } r := readGenerationLaunch(t, disk) views, err := f.root.Guard.Root.GenerationInstallations(t.Context()) if err != nil { t.Fatal(err) } launch, err := f.journal.Read(t.Context(), f.target) if err != nil { t.Fatal(err) } dir := t.TempDir() t.Setenv("CARLOS_DATA_DIR", dir) parent := filepath.Join(dir, "state") if err := os.Mkdir(parent, 0700); err != nil { t.Fatal(err) } path := filepath.Join(parent, "retained") if attack == "directory" { err = os.Mkdir(path, 0700) } else { err = os.WriteFile(path, []byte("retained payload"), 0600) } if err != nil { t.Fatal(err) } var st unix.Stat_t if err := unix.Lstat(path, &st); err != nil { t.Fatal(err) } entry := hostbox.StopInventoryEntry{Identity: inventoryIdentityFixture(t, dir, "state/retained"), Kind: "path", ArtifactID: "retained", ManagedRoot: "data", RelativePath: "state/retained", Ownership: "owner", Materialization: "materialized", Device: uint64(st.Dev), Inode: st.Ino, Type: st.Mode & unix.S_IFMT} switch attack { case "missing", "replaced", "symlink": if err := os.Rename(path, path+".old"); err != nil { t.Fatal(err) } if attack == "replaced" { err = os.WriteFile(path, []byte("replacement"), 0600) } else if attack == "symlink" { err = os.Symlink("retained.old", path) } case "parent-symlink": if err := os.Rename(parent, parent+".old"); err != nil { t.Fatal(err) } err = os.Symlink("state.old", parent) case "root-symlink": link := dir + ".link" err = os.Symlink(dir, link) t.Cleanup(func() { os.Remove(link) }) t.Setenv("CARLOS_DATA_DIR", link) case "wrong-device": entry.Device++ case "wrong-inode": entry.Inode++ case "wrong-type": entry.Type = unix.S_IFDIR case "intended", "intended-missing": entry.Materialization = "intended" if attack == "intended-missing" { entry.Device, entry.Inode, entry.Type = 0, 0, 0 err = os.Remove(path) } case "escape": entry.RelativePath = "../" + filepath.Base(dir) + "/state/retained" case "unknown-root": entry.ManagedRoot = "other" } if err != nil { t.Fatal(err) } snapshot := generationInventory{Census: rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}, Installations: views, Launches: []hostbox.LaunchInventory{launch}, Payloads: generationPayloadFixture(views)} snapshot.Payloads[0].Inventory.Entries = append(snapshot.Payloads[0].Inventory.Entries, entry) v := generationReconciler{read: func(context.Context) (generationInventory, error) { return snapshot, nil }, kernel: func(context.Context) (string, string, error) { if attack == "changed-during-kernel" { if err := os.Rename(path, path+".old"); err != nil { t.Fatal(err) } } return "boot", r.Binding.Digest, nil }} _, err = v.reconcile(t.Context(), []release.GenerationReservation{r}) if (err == nil) != (attack == "none" || attack == "directory") { t.Fatalf("physical payload %s: %v", attack, err) } }) } }