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 instanceexec
4
5import (
6 "errors"
7 "net"
8 "os"
9 "path/filepath"
10 "runtime"
11 "syscall"
12 "testing"
13 "time"
14)
15
16// The probe refuses readiness for each property the hardening adds (#214
17// items 1-3, #178), naming the line.
18func TestProbeFailsOnEachMissingHardening(t *testing.T) {
19 cases := map[string]func(t *testing.T, e *probeEnv){
20 "opt-own-build": func(t *testing.T, e *probeEnv) {
21 if err := os.MkdirAll(filepath.Join(e.optRoot, "versions"), 0o755); err != nil {
22 t.Fatal(err)
23 }
24 },
25 "dns": func(t *testing.T, e *probeEnv) {
26 e.sendUDP = func(string, string) error { return syscall.EPERM }
27 },
28 "loopback-v4": func(t *testing.T, e *probeEnv) {
29 e.sendUDP = func(_, addr string) error {
30 if addr == "127.0.0.1:9" || addr == "10.0.0.2:53" {
31 return nil
32 }
33 return syscall.EPERM
34 }
35 },
36 "private-v4": func(t *testing.T, e *probeEnv) {
37 e.sendUDP = func(_, addr string) error {
38 if addr == "172.31.255.254:9" || addr == "10.0.0.2:53" {
39 return nil
40 }
41 return syscall.EPERM
42 }
43 },
44 "private-v6": func(t *testing.T, e *probeEnv) {
45 e.sendUDP = func(_, addr string) error {
46 if addr == "[fd00::1]:9" || addr == "10.0.0.2:53" {
47 return nil
48 }
49 return syscall.EPERM
50 }
51 },
52 "ipc-private": func(t *testing.T, e *probeEnv) {
53 e.ipcInode = func() (uint64, error) { return initIPCInode, nil }
54 },
55 "abstract-unix": func(t *testing.T, e *probeEnv) {
56 e.abstractScoped = func() error { return errAbstractReached }
57 },
58 "cpu-limits": func(t *testing.T, e *probeEnv) {
59 cg, _ := e.selfCgroup()
60 if err := os.WriteFile(filepath.Join(cg, "cpu.max"), []byte("max 100000\n"), 0o644); err != nil {
61 t.Fatal(err)
62 }
63 },
64 "io-weight": func(t *testing.T, e *probeEnv) {
65 cg, _ := e.selfCgroup()
66 if err := os.WriteFile(filepath.Join(cg, "io.weight"), []byte("default 100\n"), 0o644); err != nil {
67 t.Fatal(err)
68 }
69 },
70 }
71 for check, breakIt := range cases {
72 t.Run(check, func(t *testing.T) {
73 e := goodEnv(t)
74 breakIt(t, &e)
75 r := runChecks(e)
76 if r.Ready || verdict(r, check) != "fail" {
77 t.Errorf("%s broken yet ready=%v verdict=%s", check, r.Ready, verdict(r, check))
78 }
79 })
80 }
81}
82
83// A host mqueue visible in the namespace fails ipc-private even with a
84// private namespace inode.
85func TestProbeFailsOnAVisibleHostMqueue(t *testing.T) {
86 e := goodEnv(t)
87 if err := os.WriteFile(filepath.Join(e.devMqueue, "q"), nil, 0o600); err != nil {
88 t.Fatal(err)
89 }
90 if r := runChecks(e); r.Ready || verdict(r, "ipc-private") != "fail" {
91 t.Errorf("host mqueue visible yet ready=%v", r.Ready)
92 }
93}
94
95// A loopback resolver other than resolved's stub is refused by policy,
96// and the probe names it rather than passing on a reachable address.
97func TestProbeRefusesALoopbackResolver(t *testing.T) {
98 e := goodEnv(t)
99 if err := os.WriteFile(e.resolvConf, []byte("nameserver 127.0.0.1\n"), 0o644); err != nil {
100 t.Fatal(err)
101 }
102 e.sendUDP = func(string, string) error { return syscall.EPERM }
103 if r := runChecks(e); r.Ready || verdict(r, "dns") != "fail" {
104 t.Errorf("loopback resolver yet ready=%v", r.Ready)
105 }
106}
107
108// Unverified, not failed: no IPv6 on the box, and IO weights with no
109// controller to enforce them.
110func TestProbeUnverifiedLinesDoNotBlock(t *testing.T) {
111 e := goodEnv(t)
112 e.sendUDP = func(network, addr string) error {
113 if addr == "10.0.0.2:53" {
114 return nil
115 }
116 if network == "udp6" {
117 return syscall.ENETUNREACH
118 }
119 return syscall.EPERM
120 }
121 e.ioEnforced = func(string) bool { return false }
122 r := runChecks(e)
123 if !r.Ready {
124 t.Fatalf("not ready: %+v", r.Lines)
125 }
126 for _, c := range []string{"loopback-v6", "private-v6", "io-weight"} {
127 if v := verdict(r, c); v != "unverified" {
128 t.Errorf("%s = %s, want unverified", c, v)
129 }
130 }
131}
132
133// The real Landlock scope on this kernel (#178): a scoped thread cannot
134// connect to an abstract name bound outside its domain, while the
135// unscoped process still can. Skipped where the kernel lacks ABI 6.
136func TestScopeAbstractUnixRefusesAnOutsideName(t *testing.T) {
137 name := "@carlos-scope-test-" + randomName()
138 ln, err := net.Listen("unix", name)
139 if err != nil {
140 t.Skipf("abstract listen: %v", err)
141 }
142 defer ln.Close()
143 go func() {
144 for {
145 c, err := ln.Accept()
146 if err != nil {
147 return
148 }
149 c.Close()
150 }
151 }()
152 res := make(chan error, 1)
153 go func() {
154 runtime.LockOSThread() // never unlocked: the thread dies scoped
155 if err := ScopeAbstractUnix(); err != nil {
156 res <- err
157 return
158 }
159 c, err := net.Dial("unix", name)
160 if err == nil {
161 c.Close()
162 res <- errAbstractReached
163 return
164 }
165 res <- unwrapErrno(err)
166 }()
167 err = <-res
168 if err != nil && !errors.Is(err, syscall.EPERM) && !errors.Is(err, errAbstractReached) {
169 t.Skipf("no Landlock scope here: %v", err)
170 }
171 if !errors.Is(err, syscall.EPERM) {
172 t.Fatalf("scoped connect = %v, want EPERM", err)
173 }
174 c, err := net.Dial("unix", name)
175 if err != nil {
176 t.Fatalf("unscoped connect after the scoped thread: %v", err)
177 }
178 c.Close()
179 if err := realAbstractCheck(); err != nil {
180 t.Errorf("realAbstractCheck = %v", err)
181 }
182}
183
184// An allowed resolver must open only a resolver (#214 item 3, review F1):
185// one that answers on a non-DNS port fails resolver-ports. One that is an
186// address of this box is the control service's refusal (resolver-local).
187func TestProbeResolverPortsLine(t *testing.T) {
188 e := goodEnv(t)
189 if r := runChecks(e); verdict(r, "resolver-ports") != "ok" {
190 t.Fatalf("good fixture: resolver-ports = %s", verdict(r, "resolver-ports"))
191 }
192 e.connectTCP = func(addr string, _ time.Duration) error {
193 if addr == "10.0.0.2:22" {
194 return nil
195 }
196 return syscall.ETIMEDOUT
197 }
198 if r := runChecks(e); r.Ready || verdict(r, "resolver-ports") != "fail" {
199 t.Errorf("resolver answering on 22 yet ready=%v", r.Ready)
200 }
201}
202
203// resolved's stub is never a resolver an instance may use: the namespace
204// should have replaced it with the upstream list, and if it did not, dns
205// fails with the reason.
206func TestProbeRefusesTheResolvedStub(t *testing.T) {
207 e := goodEnv(t)
208 if err := os.WriteFile(e.resolvConf, []byte("nameserver 127.0.0.53\noptions edns0 trust-ad\n"), 0o644); err != nil {
209 t.Fatal(err)
210 }
211 e.sendUDP = func(string, string) error { return nil }
212 if r := runChecks(e); r.Ready || verdict(r, "dns") != "fail" {
213 t.Errorf("stub resolver yet ready=%v", r.Ready)
214 }
215}
216