| 1 | //go:build linux |
| 2 | |
| 3 | package instancectl |
| 4 | |
| 5 | import ( |
| 6 | "context" |
| 7 | "encoding/json" |
| 8 | "net" |
| 9 | "os" |
| 10 | "path/filepath" |
| 11 | "strings" |
| 12 | "testing" |
| 13 | ) |
| 14 | |
| 15 | func TestRev3ReservedUnitChecksAtTrampolineBoundary(t *testing.T) { |
| 16 | for _, name := range []string{"valid", "expired during setup", "wrong peer", "wrong reservation", "replacement invocation", "main PID handed over"} { |
| 17 | t.Run(name, func(t *testing.T) { |
| 18 | u, ctl, in, _ := claimUnitFixture(t) |
| 19 | dir, err := os.MkdirTemp("", "rg") |
| 20 | if err != nil { |
| 21 | t.Fatal(err) |
| 22 | } |
| 23 | defer os.RemoveAll(dir) |
| 24 | u.ExeRoot = filepath.Join(dir, "exe") |
| 25 | u.UnitRoot = filepath.Join(dir, "unit") |
| 26 | u.DataRoot = filepath.Join(dir, "data") |
| 27 | p := ClaimExecPermit{Reservation: "a:main", Inputs: "hash", BootID: "boot", SafeUntil: 200, Containment: 1} |
| 28 | setup := false |
| 29 | checks := 0 |
| 30 | received := make(chan error, 1) |
| 31 | original := u.Systemctl |
| 32 | u.Systemctl = func(ctx context.Context, args ...string) (string, error) { |
| 33 | out, err := original(ctx, args...) |
| 34 | if name == "main PID handed over" && args[0] == "show" && strings.Contains(strings.Join(args, ","), "MainPID") { |
| 35 | ctl.mu.Lock() |
| 36 | ctl.pid = os.Getpid() |
| 37 | ctl.mu.Unlock() |
| 38 | } |
| 39 | if args[0] != "start" || err != nil { |
| 40 | return out, err |
| 41 | } |
| 42 | setup = true |
| 43 | ctl.mu.Lock() |
| 44 | ctl.pid = os.Getpid() |
| 45 | if name == "wrong peer" || name == "main PID handed over" { |
| 46 | ctl.pid++ |
| 47 | } |
| 48 | ctl.mu.Unlock() |
| 49 | source, dropin, _ := u.launchPaths(in.Host) |
| 50 | body, e := os.ReadFile(filepath.Join(dropin, "60-claim-launch.conf")) |
| 51 | if e != nil || !strings.Contains(string(body), "BindReadOnlyPaths=/proc/sys/kernel/random/boot_id:"+ClaimExecBootID) || !strings.Contains(string(body), "instance-exec -claim-reservation="+ClaimExecSnapshot+" run %I\n") { |
| 52 | t.Errorf("missing trusted trampoline binds/argument: %s %v", body, e) |
| 53 | } |
| 54 | go func() { |
| 55 | c, e := net.Dial("unix", filepath.Join(source, "exec.sock")) |
| 56 | if e != nil { |
| 57 | received <- e |
| 58 | return |
| 59 | } |
| 60 | defer c.Close() |
| 61 | q := p |
| 62 | if name == "wrong reservation" { |
| 63 | q.Reservation = "other" |
| 64 | } |
| 65 | if e = json.NewEncoder(c).Encode(q); e == nil { |
| 66 | var got ClaimExecPermit |
| 67 | e = json.NewDecoder(c).Decode(&got) |
| 68 | } |
| 69 | received <- e |
| 70 | }() |
| 71 | return out, nil |
| 72 | } |
| 73 | l := Launch{Schema: LaunchSchema, Host: in.Host, Argv: []string{LiveDir + "/" + in.Host}} |
| 74 | started, err := u.StartReserved(t.Context(), in, l, p, func() error { |
| 75 | checks++ |
| 76 | if setup && name == "expired during setup" { |
| 77 | return ErrClaimUnit |
| 78 | } |
| 79 | if setup && name == "replacement invocation" { |
| 80 | ctl.mu.Lock() |
| 81 | ctl.invocation = "replacement" |
| 82 | ctl.mu.Unlock() |
| 83 | } |
| 84 | return nil |
| 85 | }) |
| 86 | clientErr := <-received |
| 87 | if name == "valid" || name == "main PID handed over" { |
| 88 | if err != nil || clientErr != nil || checks != 2 || started.MainPID != os.Getpid() { |
| 89 | t.Fatalf("start=%v gate=%v checks=%d", err, clientErr, checks) |
| 90 | } |
| 91 | source, _, _ := u.launchPaths(in.Host) |
| 92 | if _, err := os.Lstat(filepath.Join(source, "exec.sock")); err != nil { |
| 93 | t.Fatalf("ExecStopPost bind source removed early: %v", err) |
| 94 | } |
| 95 | _ = u.Stop(t.Context(), in.Host) |
| 96 | if _, err := os.Lstat(filepath.Join(source, "exec.sock")); !os.IsNotExist(err) { |
| 97 | t.Fatalf("stopped gate source retained: %v", err) |
| 98 | } |
| 99 | } else if err == nil || clientErr == nil { |
| 100 | t.Fatalf("unsafe start=%v gate=%v", err, clientErr) |
| 101 | } |
| 102 | if !strings.Contains(ctl.joined(), "start ") { |
| 103 | t.Fatal("did not reach asynchronous setup") |
| 104 | } |
| 105 | }) |
| 106 | } |
| 107 | } |
| 108 | |