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 "os"
7 "path/filepath"
8 "testing"
9
10 "github.com/carlosframework/platform/internal/hostbox"
11 "github.com/carlosframework/platform/internal/rootstate"
12 "golang.org/x/sys/unix"
13)
14
15func TestPlan14GenerationPrivateJournalRootRunner(t *testing.T) {
16 dir := generationRootRunnerDir(t)
17 private := filepath.Join(dir, "private")
18 if err := os.Mkdir(private, 0700); err != nil {
19 t.Fatal(err)
20 }
21 path := filepath.Join(private, "journals.db")
22 journal, err := hostbox.OpenLaunchJournal(path)
23 if err != nil {
24 t.Fatal(err)
25 }
26 t.Cleanup(func() { journal.Close() })
27 if _, err := hostbox.NewJournalService(journal); err != nil {
28 t.Fatal(err)
29 }
30 check := func(t *testing.T, refuse bool) {
31 t.Helper()
32 reader, err := openGenerationLaunchInventory(path)
33 if reader != nil {
34 defer reader.Close()
35 }
36 if (err != nil) != refuse {
37 t.Fatalf("private journal reader: refuse=%v error=%v", refuse, err)
38 }
39 if err == nil {
40 if _, err := reader.GenerationPayloads(t.Context()); err != nil {
41 t.Fatal(err)
42 }
43 if _, err := reader.GenerationLaunches(t.Context()); err != nil {
44 t.Fatal(err)
45 }
46 }
47 }
48 if f, err := rootstate.OpenTrustedFile(path); err == nil {
49 f.Close()
50 t.Fatal("public-file opener unexpectedly accepted private ancestry")
51 }
52 check(t, false)
53 for _, suffix := range []string{"", "-wal", "-shm"} {
54 for _, attack := range []string{"writable", "service-owned", "symlink", "hardlink", "fifo", "directory"} {
55 t.Run("journal"+suffix+"/"+attack, func(t *testing.T) {
56 leaf := path + suffix
57 info, err := os.Stat(leaf)
58 if err != nil {
59 t.Fatal(err)
60 }
61 must := func(err error) {
62 t.Helper()
63 if err != nil {
64 t.Fatal(err)
65 }
66 }
67 switch attack {
68 case "writable":
69 must(os.Chmod(leaf, 0666))
70 defer func() { must(os.Chmod(leaf, info.Mode().Perm())) }()
71 case "service-owned":
72 must(os.Chown(leaf, 65534, 65534))
73 defer func() { must(os.Chown(leaf, 0, 0)) }()
74 case "hardlink":
75 must(os.Link(leaf, leaf+".alias"))
76 defer func() { must(os.Remove(leaf + ".alias")) }()
77 default:
78 must(os.Rename(leaf, leaf+".saved"))
79 defer func() {
80 must(os.Remove(leaf))
81 must(os.Rename(leaf+".saved", leaf))
82 }()
83 switch attack {
84 case "symlink":
85 must(os.Symlink(filepath.Base(leaf)+".saved", leaf))
86 case "fifo":
87 must(unix.Mkfifo(leaf, 0600))
88 case "directory":
89 must(os.Mkdir(leaf, 0700))
90 }
91 }
92 check(t, true)
93 })
94 check(t, false)
95 }
96 }
97 for _, attack := range []string{"writable", "service-owned", "symlink"} {
98 t.Run("ancestor/"+attack, func(t *testing.T) {
99 must := func(err error) {
100 t.Helper()
101 if err != nil {
102 t.Fatal(err)
103 }
104 }
105 switch attack {
106 case "writable":
107 must(os.Chmod(private, 0777))
108 defer func() { must(os.Chmod(private, 0700)) }()
109 case "service-owned":
110 must(os.Chown(private, 65534, 65534))
111 defer func() { must(os.Chown(private, 0, 0)) }()
112 case "symlink":
113 must(os.Rename(private, private+".saved"))
114 must(os.Symlink(private+".saved", private))
115 defer func() {
116 must(os.Remove(private))
117 must(os.Rename(private+".saved", private))
118 }()
119 }
120 check(t, true)
121 })
122 check(t, false)
123 }
124 if err := journal.Close(); err != nil {
125 t.Fatal(err)
126 }
127 check(t, false)
128}
129