//go:build linux package host import ( "context" "encoding/json" "errors" "os" "path/filepath" "testing" "time" "github.com/carlosframework/platform/internal/hostbox" "github.com/carlosframework/platform/internal/hostclaim" execlease "github.com/carlosframework/platform/internal/hostexec" "github.com/carlosframework/platform/internal/hostguard" "github.com/carlosframework/platform/internal/rootparticipation" "golang.org/x/sys/unix" ) type launchParticipationDisk struct{ *rollbackDisk } func (d *launchParticipationDisk) Lock(ctx context.Context, name string) (func(), error) { f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } for { if err = ctx.Err(); err != nil { f.Close() return nil, err } err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB) if err == nil { return func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN); _ = f.Close() }, nil } if !errors.Is(err, unix.EAGAIN) && !errors.Is(err, unix.EWOULDBLOCK) { f.Close() return nil, err } select { case <-ctx.Done(): f.Close() return nil, ctx.Err() case <-time.After(time.Millisecond): } } } func TestPlan14ExecutionFinalLaunchUsesRootParticipation(t *testing.T) { for _, atFinal := range []bool{false, true} { for _, attack := range []string{"none", "lost", "deadline", "boot", "process", "missing", "participation-only", "census", "update"} { t.Run(attack+"/"+map[bool]string{true: "final", false: "initial"}[atFinal], func(t *testing.T) { f := newLaunchFixture(t, rev2Operation()) disk := f.root.executionState.(*launchParticipationDisk) mutate := func() { raw, err := disk.Read(f.target.Host + ".json") if err != nil { t.Fatal(err) } var r execlease.ExecutionRecord if err = json.Unmarshal(raw, &r); err != nil { t.Fatal(err) } switch attack { case "lost": r.Status = "lost" case "deadline": r.SafeUntil = 100 case "boot": r.Peer.KernelBootID = "other-boot" case "process": r.Peer.Start = "other-start" case "participation-only": r.Authority.ParticipationOnly = true case "missing": if err = disk.Remove(f.target.Host + ".json"); err != nil { t.Fatal(err) } return case "census": if err = disk.Remove("census.json"); err != nil { t.Fatal(err) } return case "update": if err = disk.Write("update.json", []byte(`{"Schema":1,"ID":"update","Revision":1}`)); err != nil { t.Fatal(err) } return } raw, _ = json.Marshal(r) if err = disk.Write(f.target.Host+".json", raw); err != nil { t.Fatal(err) } } // A real held service lock and a forged future deadline cannot grant root work. mirror := filepath.Join(f.root.Guard.DataDir, ".hostexec") if err := os.MkdirAll(mirror, 0700); err != nil { t.Fatal(err) } raw, _ := json.Marshal(execlease.State{V: 1, Binding: execlease.Binding{Host: f.target.Host, Incarnation: f.target.Incarnation, Op: f.target.Op, Box: f.target.Box, Enrollment: f.target.Enrollment, BootID: f.peer.BootID, ControllerID: "agent"}, Process: execlease.ControllerProcess{KernelBootID: f.peer.BootID, PID: f.peer.PID, Start: f.peer.ProcessStart}, State: "active", SafeUntil: 1 << 62, LeaseETag: "forged", Expires: time.Now().Add(365 * 24 * time.Hour)}) if err := os.WriteFile(filepath.Join(mirror, f.target.Host+".json"), raw, 0600); err != nil { t.Fatal(err) } for _, suffix := range []string{".lock", ".live"} { lock, err := os.OpenFile(filepath.Join(f.root.ExecLockDir, f.target.Host+suffix), os.O_CREATE|os.O_RDWR, 0660) if err != nil { t.Fatal(err) } if err = lock.Chmod(0660); err != nil { t.Fatal(err) } if err = unix.Flock(int(lock.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { t.Fatal(err) } t.Cleanup(func() { lock.Close() }) } if atFinal { f.units.beforeCheck = mutate } else { mutate() } _, err := f.root.Serve(t.Context(), f.peer, f.req) starts, _ := f.units.counts() if attack == "none" { if err != nil || starts != 1 { t.Fatalf("positive start=%d err=%v", starts, err) } } else if err == nil || starts != 0 { t.Fatalf("root refusal bypassed: start=%d err=%v", starts, err) } }) } } } func configureLaunchParticipation(t *testing.T, f *launchFixture) { t.Helper() disk := &launchParticipationDisk{&rollbackDisk{dir: t.TempDir()}} f.root.executionState = disk raw, _ := json.Marshal(rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true}) if err := disk.Write("census.json", raw); err != nil { t.Fatal(err) } if f.claims.obs.Claim.Op.Kind == hostclaim.KindServing { return } writeRev2Local(t, f, "active", 1000) unlock, err := disk.HoldLive(f.target.Host + ".live") if err != nil { t.Fatal(err) } t.Cleanup(unlock) } func TestPlan14ExecutionRootLocalIgnoresAgentAbsence(t *testing.T) { for _, serving := range []bool{false, true} { for _, lost := range []bool{false, true} { t.Run(map[bool]string{true: "serving", false: "operation"}[serving]+"/"+map[bool]string{true: "lost", false: "valid"}[lost], func(t *testing.T) { op := rev2Operation() if serving { op = servingOp() } f := newLaunchFixture(t, op) disk := f.root.executionState.(*launchParticipationDisk) if lost { q := execlease.ExecutionRequest{Schema: 1, ID: "old", Operation: "executionBegin", Host: f.target.Host, Incarnation: f.target.Incarnation, Stamp: f.target.Stamp, Session: f.target.Session, Op: "operation"} raw, _ := json.Marshal(execlease.ExecutionRecord{Schema: 1, Request: q, Token: "lost", Status: "lost", Authority: execlease.ExecutionAuthority{Migration: 1, Box: f.actor.Box, Enrollment: f.actor.Enrollment}, Replay: map[string]execlease.ExecutionReplay{}}) if err := disk.Write(f.target.Host+".json", raw); err != nil { t.Fatal(err) } } p := &productionClaimLocal{Resources: ClaimRuntimeResources{Mode: "instance-control", DataDir: f.root.Guard.DataDir}, assembly: &ClaimAssembly{}, lifetime: t.Context(), executionRead: f.root.readExecution} obs, _ := f.claims.Read(t.Context(), f.target.Host) selection := hostbox.Selection{Box: f.actor.Box, Region: f.actor.Region, Captured: hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}} for _, err := range []error{p.prepareWith(t.Context(), *f.actor, obs), p.Check(t.Context(), *f.actor, obs.Claim, selection)} { if lost && err == nil { t.Fatal("missing agent mirror cleared root loss") } if !lost && err != nil { t.Fatal("valid root participation refused", err) } } }) } } } func TestPlan14ExecutionServingRequiresRootSettlement(t *testing.T) { for _, status := range []string{"settled", "lost", "unknown"} { for _, attack := range []string{"erase", "rename", "replace locks"} { t.Run(status+"/"+attack, func(t *testing.T) { f := newLaunchFixture(t, servingOp()) disk := f.root.executionState.(*launchParticipationDisk) q := execlease.ExecutionRequest{Schema: 1, ID: "old", Operation: "executionBegin", Host: f.target.Host, Incarnation: f.target.Incarnation, Stamp: f.target.Stamp, Session: f.target.Session, Op: "prior-operation"} raw, _ := json.Marshal(execlease.ExecutionRecord{Schema: 1, Request: q, Token: "old", Status: status, Authority: execlease.ExecutionAuthority{Migration: 1, Box: f.actor.Box, Enrollment: f.actor.Enrollment}, Replay: map[string]execlease.ExecutionReplay{}}) if err := disk.Write(f.target.Host+".json", raw); err != nil { t.Fatal(err) } mirror := filepath.Join(f.root.Guard.DataDir, ".hostexec") if err := os.Mkdir(mirror, 0700); err != nil { t.Fatal(err) } name := filepath.Join(mirror, f.target.Host+".json") if err := os.WriteFile(name, []byte(`{"state":"lost"}`), 0600); err != nil { t.Fatal(err) } if attack == "rename" { if err := os.Rename(mirror, mirror+".old"); err != nil { t.Fatal(err) } if err := os.Mkdir(mirror, 0700); err != nil { t.Fatal(err) } } else { if err := os.Remove(name); err != nil { t.Fatal(err) } } if attack == "replace locks" { for _, suffix := range []string{".lock", ".live"} { name := filepath.Join(f.root.ExecLockDir, f.target.Host+suffix) file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0660) if err != nil { t.Fatal(err) } if err = unix.Flock(int(file.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil { t.Fatal(err) } t.Cleanup(func() { file.Close() }) if err = os.Rename(name, name+".old"); err != nil { t.Fatal(err) } if err = os.WriteFile(name, nil, 0660); err != nil { t.Fatal(err) } } } _, err := f.root.Serve(t.Context(), f.peer, f.req) starts, _ := f.units.counts() if status == "settled" { if err != nil || starts != 1 { t.Fatalf("settled serving refused: starts=%d err=%v", starts, err) } } else if err == nil || starts != 0 { t.Fatalf("agent absence granted serving: starts=%d err=%v", starts, err) } }) } } } func (d *rollbackDisk) HoldLive(name string) (func(), error) { f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, err } lock := unix.Flock_t{Type: unix.F_WRLCK} if err := unix.FcntlFlock(f.Fd(), unix.F_OFD_SETLK, &lock); err != nil { f.Close() return nil, err } return func() { f.Close() }, nil } func (d *rollbackDisk) LiveHeld(name string) (bool, error) { f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_RDWR, 0) if err != nil { return false, err } defer f.Close() lock := unix.Flock_t{Type: unix.F_RDLCK} err = unix.FcntlFlock(f.Fd(), unix.F_OFD_GETLK, &lock) return lock.Type == unix.F_WRLCK && lock.Pid == -1, err }