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 && rootrunner
2
3package host
4
5import (
6 "errors"
7 "maps"
8 "os"
9 "path/filepath"
10 "slices"
11 "syscall"
12 "testing"
13
14 "golang.org/x/sys/unix"
15)
16
17// TestRootFloorDirsUntrustedAncestry is fix round 2's witness as real root,
18// through production's prepareRootFloorDirs (the walk from /, owner root):
19// the floor repair checks every directory from / to guard/ before it makes
20// or changes anything. An ancestor symlink with the floor directory missing
21// behind it, and an ancestor owned by someone else holding a root-owned
22// floor directory (or none), each refuse with both trees as they were. A
23// floor directory of another owner under a trusted path refuses too. A
24// trusted path under umask 077 is repaired to 0755 root:root.
25func TestRootFloorDirsUntrustedAncestry(t *testing.T) {
26 if os.Geteuid() != 0 {
27 t.Fatal("designated runner must execute as root")
28 }
29 const nobody = 65534
30 mkopt := func(t *testing.T) (string, string) {
31 t.Helper()
32 scratch := generationRootRunnerDir(t)
33 opt := filepath.Join(scratch, "opt")
34 for _, dir := range []string{opt, filepath.Join(opt, "system"), systemGuardDir(opt)} {
35 if err := os.Mkdir(dir, 0o755); err != nil {
36 t.Fatal(err)
37 }
38 if err := os.Chmod(dir, 0o755); err != nil {
39 t.Fatal(err)
40 }
41 }
42 return scratch, opt
43 }
44 mkdir := func(t *testing.T, dir string, mode os.FileMode, uid int) {
45 t.Helper()
46 if err := os.Mkdir(dir, mode); err != nil {
47 t.Fatal(err)
48 }
49 if err := os.Chmod(dir, mode); err != nil {
50 t.Fatal(err)
51 }
52 if err := os.Lchown(dir, uid, uid); err != nil {
53 t.Fatal(err)
54 }
55 }
56 for name, c := range map[string]struct {
57 setup func(t *testing.T, scratch, opt string) []string // trees outside opt that must not change
58 want []error
59 }{
60 "symlinked guard, moves missing": {setup: func(t *testing.T, scratch, opt string) []string {
61 target := filepath.Join(scratch, "realguard")
62 mkdir(t, target, 0o755, 0)
63 if err := os.Remove(systemGuardDir(opt)); err != nil {
64 t.Fatal(err)
65 }
66 if err := os.Symlink(target, systemGuardDir(opt)); err != nil {
67 t.Fatal(err)
68 }
69 return []string{target}
70 }, want: []error{unix.ELOOP, unix.ENOTDIR}},
71 "nobody's system, root's moves 0700": {setup: func(t *testing.T, scratch, opt string) []string {
72 mkdir(t, rootFloorDir(opt), 0o700, 0)
73 if err := os.Lchown(filepath.Join(opt, "system"), nobody, nobody); err != nil {
74 t.Fatal(err)
75 }
76 return nil
77 }, want: []error{os.ErrPermission}},
78 "nobody's guard, moves missing": {setup: func(t *testing.T, scratch, opt string) []string {
79 if err := os.Lchown(systemGuardDir(opt), nobody, nobody); err != nil {
80 t.Fatal(err)
81 }
82 return nil
83 }, want: []error{os.ErrPermission}},
84 "nobody's moves 0700": {setup: func(t *testing.T, scratch, opt string) []string {
85 mkdir(t, rootFloorDir(opt), 0o700, nobody)
86 return nil
87 }, want: []error{os.ErrPermission}},
88 } {
89 t.Run(name, func(t *testing.T) {
90 scratch, opt := mkopt(t)
91 roots := append([]string{opt}, c.setup(t, scratch, opt)...)
92 before := floorTree(t, roots...)
93 err := prepareRootFloorDirs(opt)
94 if !slices.ContainsFunc(c.want, func(w error) bool { return errors.Is(err, w) }) {
95 t.Fatalf("prepareRootFloorDirs = %v, want a refusal with one of %v", err, c.want)
96 }
97 if after := floorTree(t, roots...); !maps.Equal(before, after) {
98 t.Fatalf("the refused repair changed the tree:\nbefore %v\nafter %v", before, after)
99 }
100 for _, root := range roots[1:] {
101 if _, err := os.Lstat(filepath.Join(root, "moves")); !errors.Is(err, os.ErrNotExist) {
102 t.Fatal("moves made through the symlink", err)
103 }
104 }
105 t.Logf("refused with both trees unchanged: %v", err)
106 })
107 }
108
109 // The trusted path, guard/moves left 0700 and guard/seeds missing, under
110 // the window shell's umask 077: both come out 0755 root:root, and a
111 // second run changes nothing.
112 t.Run("trusted, umask 077", func(t *testing.T) {
113 _, opt := mkopt(t)
114 mkdir(t, rootFloorDir(opt), 0o700, 0)
115 old := syscall.Umask(0o077)
116 err := prepareRootFloorDirs(opt)
117 syscall.Umask(old)
118 if err != nil {
119 t.Fatal(err)
120 }
121 for _, dir := range []string{rootFloorDir(opt), rootSeedsFloorDir(opt)} {
122 st, err := os.Lstat(dir)
123 if err != nil || !st.IsDir() || st.Mode().Perm() != 0o755 || st.Sys().(*syscall.Stat_t).Uid != 0 || st.Sys().(*syscall.Stat_t).Gid != 0 {
124 t.Fatalf("%s is not a 0755 root:root directory: %v %v", dir, st, err)
125 }
126 }
127 before := floorTree(t, opt)
128 if err := prepareRootFloorDirs(opt); err != nil {
129 t.Fatal("second run", err)
130 }
131 if after := floorTree(t, opt); !maps.Equal(before, after) {
132 t.Fatalf("the second run changed the tree:\nbefore %v\nafter %v", before, after)
133 }
134 })
135}
136