carlos / platform Public

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

Plain git — no account needed to clone.

Download

Download this file

1package host
2
3import (
4 "context"
5 "errors"
6 "os"
7 "strings"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/rootstate"
11)
12
13type plan14UnitFiles struct {
14 body []byte
15 id rootstate.FileIdentity
16 creates, retires int
17}
18
19func (f *plan14UnitFiles) read(string) ([]byte, rootstate.FileIdentity, error) {
20 if f.body == nil {
21 return nil, rootstate.FileIdentity{}, os.ErrNotExist
22 }
23 return append([]byte(nil), f.body...), f.id, nil
24}
25func (f *plan14UnitFiles) create(_ string, b []byte) error {
26 if f.body != nil {
27 return os.ErrExist
28 }
29 f.body = append([]byte(nil), b...)
30 f.id = plan14UnitIdentity(2)
31 f.creates++
32 return nil
33}
34func (f *plan14UnitFiles) replace(_ string, id rootstate.FileIdentity, b []byte) error {
35 if id != f.id {
36 return os.ErrPermission
37 }
38 f.body = append([]byte(nil), b...)
39 f.id.Inode++
40 f.retires++
41 return nil
42}
43func TestPlan14UnitMaterialization(t *testing.T) {
44 for _, attack := range []string{"none", "foreign file", "busy install", "replacement", "changed bytes", "lost intent", "lost materialized", "busy", "retired"} {
45 t.Run(attack, func(t *testing.T) {
46 b, in, shape := plan14UnitBindingFixture(t, "console.service", "", "")
47 shape.Program = strings.Repeat("a", 64)
48 b.canonical = func(context.Context, string) (claimUnitShape, error) { return shape, nil }
49 binding, err := b.establish(t.Context(), in.Host)
50 if err != nil {
51 t.Fatal(err)
52 }
53 files := &plan14UnitFiles{}
54 state := &plan14UnitBindingFault{Store: &rollbackMemory{files: map[string][]byte{}}}
55 m := claimUnitMaterializer{idle: func(string) error { return nil }, state: state, read: files.read, create: files.create, replace: files.replace}
56 if attack == "foreign file" {
57 files.body = []byte("foreign")
58 files.id = plan14UnitIdentity(9)
59 }
60 if attack == "busy install" {
61 m.idle = func(string) error { return os.ErrPermission }
62 }
63 if attack == "lost intent" {
64 state.fail = true
65 }
66 err = m.install(t.Context(), binding)
67 if attack == "foreign file" || attack == "lost intent" || attack == "busy install" {
68 if err == nil || files.creates != 0 {
69 t.Fatal("effect before durable intent", err, files.creates)
70 }
71 return
72 }
73 if err != nil {
74 t.Fatal(err)
75 }
76 if files.creates != 1 {
77 t.Fatal(files.creates)
78 }
79 if err = m.install(t.Context(), binding); err != nil || files.creates != 1 {
80 t.Fatal("replay", err)
81 }
82 switch attack {
83 case "replacement":
84 files.id.Inode++
85 case "changed bytes":
86 files.body = append(files.body, 'x')
87 case "lost materialized":
88 record, err := m.load(in.Host)
89 if err != nil {
90 t.Fatal(err)
91 }
92 record.Identity = rootstate.FileIdentity{}
93 if err = m.save(record); err != nil {
94 t.Fatal(err)
95 }
96 if err = m.install(t.Context(), binding); err != nil || files.creates != 1 {
97 t.Fatal("lost materialization recovery", err)
98 }
99 }
100 gone := func() error {
101 if attack == "busy" {
102 return os.ErrPermission
103 }
104 return nil
105 }
106 err = m.retire(t.Context(), binding, gone)
107 if attack == "replacement" || attack == "changed bytes" || attack == "busy" {
108 if err == nil || files.retires != 0 {
109 t.Fatal("retired foreign or running unit", err)
110 }
111 return
112 }
113 if err != nil || files.retires != 1 {
114 t.Fatal("retire", err, files.retires)
115 }
116 if !strings.Contains(string(files.body), "RefuseManualStart=yes") {
117 t.Fatal("retirement exposed old unit")
118 }
119 if err = m.retire(t.Context(), binding, gone); err != nil || files.retires != 1 {
120 t.Fatal("retire replay", err)
121 }
122 if err = m.install(t.Context(), binding); !errors.Is(err, os.ErrPermission) {
123 t.Fatal("retired unit reinstalled", err)
124 }
125 })
126 }
127}
128
129func plan14UnitIdentity(inode uint64) rootstate.FileIdentity {
130 return rootstate.FileIdentity{Device: 1, Inode: inode, Boot: "fixture-boot", Filesystem: "fixture-filesystem", Handle: "fixture-handle", HandleType: 1, Mount: 1, MountRoot: "/", MountPoint: "/", MountNamespace: "fixture-namespace", Mode: 0100644}
131}
132