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 unix
2
3package host
4
5import (
6 "bytes"
7 "errors"
8 "go/ast"
9 "go/parser"
10 "go/token"
11 "os"
12 "os/user"
13 "path/filepath"
14 "strings"
15 "testing"
16)
17
18func TestRootRegistryRefusesPlantedFilesBeforeSQLite(t *testing.T) {
19 u, err := user.Current()
20 if err != nil {
21 t.Fatal(err)
22 }
23 if u.Uid == "0" {
24 t.Skip("this test simulates root repair with the current non-root service identity; real root credentials have separate coverage")
25 }
26 for _, name := range []string{"symlink", "dangling-symlink", "hardlink-wal", "hardlink-shm", "symlink-parent"} {
27 t.Run(name, func(t *testing.T) {
28 dir := t.TempDir()
29 victim := filepath.Join(dir, "victim")
30 data := filepath.Join(dir, "data")
31 if err := os.Mkdir(data, 0700); err != nil {
32 t.Fatal(err)
33 }
34 path := filepath.Join(data, "registry.db")
35 if name != "dangling-symlink" {
36 if err := os.WriteFile(victim, nil, 0640); err != nil {
37 t.Fatal(err)
38 }
39 }
40 switch name {
41 case "symlink", "dangling-symlink":
42 err = os.Symlink(victim, path)
43 case "hardlink-wal":
44 err = os.Link(victim, path+"-wal")
45 case "hardlink-shm":
46 err = os.Link(victim, path+"-shm")
47 case "symlink-parent":
48 alias := filepath.Join(dir, "alias")
49 err = os.Symlink(data, alias)
50 path = filepath.Join(alias, "registry.db")
51 }
52 if err != nil {
53 t.Fatal(err)
54 }
55 before, beforeErr := os.Stat(victim)
56 logged := captureLog(t)
57 reg, openErr := openRegistryConverging(0, u.Username, path)
58 if reg != nil {
59 reg.Close()
60 t.Fatal("unsafe registry reached SQLite")
61 }
62 if openErr == nil {
63 t.Fatal("unsafe registry accepted")
64 }
65 if !strings.Contains(strings.ToLower(logged()), "alert") {
66 t.Errorf("refused file had no alert: %s", logged())
67 }
68 after, afterErr := os.Stat(victim)
69 if errors.Is(beforeErr, os.ErrNotExist) {
70 if !errors.Is(afterErr, os.ErrNotExist) {
71 t.Fatalf("dangling link target created: %v", afterErr)
72 }
73 } else {
74 if afterErr != nil {
75 t.Fatal(afterErr)
76 }
77 beforeUID, beforeGID, _ := fileOwner(before)
78 afterUID, afterGID, _ := fileOwner(after)
79 if beforeUID != afterUID || beforeGID != afterGID || before.Mode() != after.Mode() || before.ModTime() != after.ModTime() {
80 t.Fatal("victim ownership, mode or modification time changed")
81 }
82 contents, err := os.ReadFile(victim)
83 if err != nil || !bytes.Equal(contents, []byte{}) {
84 t.Fatalf("victim changed: %q, %v", contents, err)
85 }
86 }
87 for _, suffix := range []string{"-wal", "-shm", "-journal"} {
88 if _, err := os.Lstat(victim + suffix); !errors.Is(err, os.ErrNotExist) {
89 t.Fatalf("SQLite created victim sidecar %s: %v", suffix, err)
90 }
91 }
92 })
93 }
94}
95
96func TestPrivilegedRegistryEntryPointsUseServiceBoundary(t *testing.T) {
97 for _, tc := range []struct{ file, function, opener string }{
98 {"agent.go", "runAdoptWithEnsureSystemUnits", "withClaimRootRelease"},
99 {"agent.go", "runAdoptUnderRootRelease", "openServiceRegistry"},
100 {"systemapply.go", "openSystemRegistry", "openServiceRegistry"},
101 {"claimstop_run.go", "RunInstanceControl", "openServiceRegistry"},
102 {"claimstop_run.go", "instanceRegistryHostApp", "openServiceRegistryReadOnly"},
103 {"rootgeneration_rows.go", "productionGenerationRegistryInventory", "openServiceRegistryReadOnly"},
104 {"claimjournal_migration.go", "RunSystemJournalMigrate", "openServiceRegistry"},
105 } {
106 t.Run(tc.function, func(t *testing.T) {
107 f, err := parser.ParseFile(token.NewFileSet(), tc.file, nil, 0)
108 if err != nil {
109 t.Fatal(err)
110 }
111 found := false
112 for _, decl := range f.Decls {
113 fn, ok := decl.(*ast.FuncDecl)
114 if !ok || fn.Name.Name != tc.function {
115 continue
116 }
117 ast.Inspect(fn.Body, func(n ast.Node) bool {
118 call, ok := n.(*ast.CallExpr)
119 if !ok {
120 return true
121 }
122 if id, ok := call.Fun.(*ast.Ident); ok && id.Name == tc.opener {
123 found = true
124 }
125 if sel, ok := call.Fun.(*ast.SelectorExpr); ok && (sel.Sel.Name == "Open" || sel.Sel.Name == "OpenReadOnly") {
126 if id, ok := sel.X.(*ast.Ident); ok && id.Name == "registry" {
127 t.Error("privileged caller directly opens SQLite")
128 }
129 }
130 return true
131 })
132 }
133 if !found {
134 t.Fatal("privileged caller does not cross the service registry boundary")
135 }
136 })
137 }
138}
139
140func TestStagedExecutableRegistryLookupCannotOpenSQLite(t *testing.T) {
141 entries, err := os.ReadDir("../instancectl")
142 if err != nil {
143 t.Fatal(err)
144 }
145 for _, entry := range entries {
146 if !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
147 continue
148 }
149 f, err := parser.ParseFile(token.NewFileSet(), filepath.Join("../instancectl", entry.Name()), nil, parser.ImportsOnly)
150 if err != nil {
151 t.Fatal(err)
152 }
153 for _, imp := range f.Imports {
154 if imp.Path.Value == `"database/sql"` || strings.Contains(imp.Path.Value, "sqlite") {
155 t.Errorf("instancectl/%s imports SQLite instead of receiving the service registry view", entry.Name())
156 }
157 }
158 }
159}
160