carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux
2
3package host
4
5import (
6 "context"
7 "encoding/json"
8 "errors"
9 "os"
10 "path/filepath"
11 "testing"
12 "time"
13
14 "github.com/carlosframework/platform/internal/hostbox"
15 "github.com/carlosframework/platform/internal/hostclaim"
16 execlease "github.com/carlosframework/platform/internal/hostexec"
17 "github.com/carlosframework/platform/internal/hostguard"
18 "github.com/carlosframework/platform/internal/rootparticipation"
19 "golang.org/x/sys/unix"
20)
21
22type launchParticipationDisk struct{ *rollbackDisk }
23
24func (d *launchParticipationDisk) Lock(ctx context.Context, name string) (func(), error) {
25 f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_CREATE|os.O_RDWR, 0600)
26 if err != nil {
27 return nil, err
28 }
29 for {
30 if err = ctx.Err(); err != nil {
31 f.Close()
32 return nil, err
33 }
34 err = unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
35 if err == nil {
36 return func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN); _ = f.Close() }, nil
37 }
38 if !errors.Is(err, unix.EAGAIN) && !errors.Is(err, unix.EWOULDBLOCK) {
39 f.Close()
40 return nil, err
41 }
42 select {
43 case <-ctx.Done():
44 f.Close()
45 return nil, ctx.Err()
46 case <-time.After(time.Millisecond):
47 }
48 }
49}
50
51func TestPlan14ExecutionFinalLaunchUsesRootParticipation(t *testing.T) {
52 for _, atFinal := range []bool{false, true} {
53 for _, attack := range []string{"none", "lost", "deadline", "boot", "process", "missing", "participation-only", "census", "update"} {
54 t.Run(attack+"/"+map[bool]string{true: "final", false: "initial"}[atFinal], func(t *testing.T) {
55 f := newLaunchFixture(t, rev2Operation())
56 disk := f.root.executionState.(*launchParticipationDisk)
57 mutate := func() {
58 raw, err := disk.Read(f.target.Host + ".json")
59 if err != nil {
60 t.Fatal(err)
61 }
62 var r execlease.ExecutionRecord
63 if err = json.Unmarshal(raw, &r); err != nil {
64 t.Fatal(err)
65 }
66 switch attack {
67 case "lost":
68 r.Status = "lost"
69 case "deadline":
70 r.SafeUntil = 100
71 case "boot":
72 r.Peer.KernelBootID = "other-boot"
73 case "process":
74 r.Peer.Start = "other-start"
75 case "participation-only":
76 r.Authority.ParticipationOnly = true
77 case "missing":
78 if err = disk.Remove(f.target.Host + ".json"); err != nil {
79 t.Fatal(err)
80 }
81 return
82 case "census":
83 if err = disk.Remove("census.json"); err != nil {
84 t.Fatal(err)
85 }
86 return
87 case "update":
88 if err = disk.Write("update.json", []byte(`{"Schema":1,"ID":"update","Revision":1}`)); err != nil {
89 t.Fatal(err)
90 }
91 return
92 }
93 raw, _ = json.Marshal(r)
94 if err = disk.Write(f.target.Host+".json", raw); err != nil {
95 t.Fatal(err)
96 }
97 }
98 // A real held service lock and a forged future deadline cannot grant root work.
99 mirror := filepath.Join(f.root.Guard.DataDir, ".hostexec")
100 if err := os.MkdirAll(mirror, 0700); err != nil {
101 t.Fatal(err)
102 }
103 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)})
104 if err := os.WriteFile(filepath.Join(mirror, f.target.Host+".json"), raw, 0600); err != nil {
105 t.Fatal(err)
106 }
107 for _, suffix := range []string{".lock", ".live"} {
108 lock, err := os.OpenFile(filepath.Join(f.root.ExecLockDir, f.target.Host+suffix), os.O_CREATE|os.O_RDWR, 0660)
109 if err != nil {
110 t.Fatal(err)
111 }
112 if err = lock.Chmod(0660); err != nil {
113 t.Fatal(err)
114 }
115 if err = unix.Flock(int(lock.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
116 t.Fatal(err)
117 }
118 t.Cleanup(func() { lock.Close() })
119 }
120 if atFinal {
121 f.units.beforeCheck = mutate
122 } else {
123 mutate()
124 }
125 _, err := f.root.Serve(t.Context(), f.peer, f.req)
126 starts, _ := f.units.counts()
127 if attack == "none" {
128 if err != nil || starts != 1 {
129 t.Fatalf("positive start=%d err=%v", starts, err)
130 }
131 } else if err == nil || starts != 0 {
132 t.Fatalf("root refusal bypassed: start=%d err=%v", starts, err)
133 }
134 })
135 }
136 }
137}
138
139func configureLaunchParticipation(t *testing.T, f *launchFixture) {
140 t.Helper()
141 disk := &launchParticipationDisk{&rollbackDisk{dir: t.TempDir()}}
142 f.root.executionState = disk
143 raw, _ := json.Marshal(rootparticipation.Census{Schema: 1, Box: f.target.Box, Enrollment: f.target.Enrollment, Migration: 1, Revision: 1, LaunchRevision: 1, Complete: true})
144 if err := disk.Write("census.json", raw); err != nil {
145 t.Fatal(err)
146 }
147 if f.claims.obs.Claim.Op.Kind == hostclaim.KindServing {
148 return
149 }
150 writeRev2Local(t, f, "active", 1000)
151 unlock, err := disk.HoldLive(f.target.Host + ".live")
152 if err != nil {
153 t.Fatal(err)
154 }
155 t.Cleanup(unlock)
156}
157
158func TestPlan14ExecutionRootLocalIgnoresAgentAbsence(t *testing.T) {
159 for _, serving := range []bool{false, true} {
160 for _, lost := range []bool{false, true} {
161 t.Run(map[bool]string{true: "serving", false: "operation"}[serving]+"/"+map[bool]string{true: "lost", false: "valid"}[lost], func(t *testing.T) {
162 op := rev2Operation()
163 if serving {
164 op = servingOp()
165 }
166 f := newLaunchFixture(t, op)
167 disk := f.root.executionState.(*launchParticipationDisk)
168 if lost {
169 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"}
170 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{}})
171 if err := disk.Write(f.target.Host+".json", raw); err != nil {
172 t.Fatal(err)
173 }
174 }
175 p := &productionClaimLocal{Resources: ClaimRuntimeResources{Mode: "instance-control", DataDir: f.root.Guard.DataDir}, assembly: &ClaimAssembly{}, lifetime: t.Context(), executionRead: f.root.readExecution}
176 obs, _ := f.claims.Read(t.Context(), f.target.Host)
177 selection := hostbox.Selection{Box: f.actor.Box, Region: f.actor.Region, Captured: hostguard.Captured{Incarnation: f.target.Incarnation, Stamp: f.target.Stamp}}
178 for _, err := range []error{p.prepareWith(t.Context(), *f.actor, obs), p.Check(t.Context(), *f.actor, obs.Claim, selection)} {
179 if lost && err == nil {
180 t.Fatal("missing agent mirror cleared root loss")
181 }
182 if !lost && err != nil {
183 t.Fatal("valid root participation refused", err)
184 }
185 }
186 })
187 }
188 }
189}
190
191func TestPlan14ExecutionServingRequiresRootSettlement(t *testing.T) {
192 for _, status := range []string{"settled", "lost", "unknown"} {
193 for _, attack := range []string{"erase", "rename", "replace locks"} {
194 t.Run(status+"/"+attack, func(t *testing.T) {
195 f := newLaunchFixture(t, servingOp())
196 disk := f.root.executionState.(*launchParticipationDisk)
197 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"}
198 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{}})
199 if err := disk.Write(f.target.Host+".json", raw); err != nil {
200 t.Fatal(err)
201 }
202 mirror := filepath.Join(f.root.Guard.DataDir, ".hostexec")
203 if err := os.Mkdir(mirror, 0700); err != nil {
204 t.Fatal(err)
205 }
206 name := filepath.Join(mirror, f.target.Host+".json")
207 if err := os.WriteFile(name, []byte(`{"state":"lost"}`), 0600); err != nil {
208 t.Fatal(err)
209 }
210 if attack == "rename" {
211 if err := os.Rename(mirror, mirror+".old"); err != nil {
212 t.Fatal(err)
213 }
214 if err := os.Mkdir(mirror, 0700); err != nil {
215 t.Fatal(err)
216 }
217 } else {
218 if err := os.Remove(name); err != nil {
219 t.Fatal(err)
220 }
221 }
222 if attack == "replace locks" {
223 for _, suffix := range []string{".lock", ".live"} {
224 name := filepath.Join(f.root.ExecLockDir, f.target.Host+suffix)
225 file, err := os.OpenFile(name, os.O_CREATE|os.O_RDWR, 0660)
226 if err != nil {
227 t.Fatal(err)
228 }
229 if err = unix.Flock(int(file.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
230 t.Fatal(err)
231 }
232 t.Cleanup(func() { file.Close() })
233 if err = os.Rename(name, name+".old"); err != nil {
234 t.Fatal(err)
235 }
236 if err = os.WriteFile(name, nil, 0660); err != nil {
237 t.Fatal(err)
238 }
239 }
240 }
241 _, err := f.root.Serve(t.Context(), f.peer, f.req)
242 starts, _ := f.units.counts()
243 if status == "settled" {
244 if err != nil || starts != 1 {
245 t.Fatalf("settled serving refused: starts=%d err=%v", starts, err)
246 }
247 } else if err == nil || starts != 0 {
248 t.Fatalf("agent absence granted serving: starts=%d err=%v", starts, err)
249 }
250 })
251 }
252 }
253}
254
255func (d *rollbackDisk) HoldLive(name string) (func(), error) {
256 f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_CREATE|os.O_RDWR, 0600)
257 if err != nil {
258 return nil, err
259 }
260 lock := unix.Flock_t{Type: unix.F_WRLCK}
261 if err := unix.FcntlFlock(f.Fd(), unix.F_OFD_SETLK, &lock); err != nil {
262 f.Close()
263 return nil, err
264 }
265 return func() { f.Close() }, nil
266}
267func (d *rollbackDisk) LiveHeld(name string) (bool, error) {
268 f, err := os.OpenFile(filepath.Join(d.dir, name), os.O_RDWR, 0)
269 if err != nil {
270 return false, err
271 }
272 defer f.Close()
273 lock := unix.Flock_t{Type: unix.F_RDLCK}
274 err = unix.FcntlFlock(f.Fd(), unix.F_OFD_GETLK, &lock)
275 return lock.Type == unix.F_WRLCK && lock.Pid == -1, err
276}
277