github.com/SagerNet/gvisor@v0.0.0-20210707092255-7731c139d75c/runsc/specutils/seccomp/seccomp_test.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package seccomp 16 17 import ( 18 "fmt" 19 "testing" 20 21 specs "github.com/opencontainers/runtime-spec/specs-go" 22 "golang.org/x/sys/unix" 23 "github.com/SagerNet/gvisor/pkg/abi/linux" 24 "github.com/SagerNet/gvisor/pkg/bpf" 25 "github.com/SagerNet/gvisor/pkg/hostarch" 26 "github.com/SagerNet/gvisor/pkg/marshal" 27 ) 28 29 // asInput converts a linux.SeccompData to a bpf.Input. 30 func asInput(d *linux.SeccompData) bpf.Input { 31 return bpf.InputBytes{marshal.Marshal(d), hostarch.ByteOrder} 32 } 33 34 // testInput creates an Input struct with given seccomp input values. 35 func testInput(arch uint32, syscallName string, args *[6]uint64) bpf.Input { 36 syscallNo, err := lookupSyscallNo(arch, syscallName) 37 if err != nil { 38 // Assume tests set valid syscall names. 39 panic(err) 40 } 41 42 if args == nil { 43 argArray := [6]uint64{0, 0, 0, 0, 0, 0} 44 args = &argArray 45 } 46 47 data := linux.SeccompData{ 48 Nr: int32(syscallNo), 49 Arch: arch, 50 Args: *args, 51 } 52 53 return asInput(&data) 54 } 55 56 // testCase holds a seccomp test case. 57 type testCase struct { 58 name string 59 config specs.LinuxSeccomp 60 input bpf.Input 61 expected uint32 62 } 63 64 var ( 65 // seccompTests is a list of speccomp test cases. 66 seccompTests = []testCase{ 67 { 68 name: "default_allow", 69 config: specs.LinuxSeccomp{ 70 DefaultAction: specs.ActAllow, 71 }, 72 input: testInput(nativeArchAuditNo, "read", nil), 73 expected: uint32(allowAction), 74 }, 75 { 76 name: "default_deny", 77 config: specs.LinuxSeccomp{ 78 DefaultAction: specs.ActErrno, 79 }, 80 input: testInput(nativeArchAuditNo, "read", nil), 81 expected: uint32(errnoAction), 82 }, 83 { 84 name: "deny_arch", 85 config: specs.LinuxSeccomp{ 86 DefaultAction: specs.ActAllow, 87 Syscalls: []specs.LinuxSyscall{ 88 { 89 Names: []string{ 90 "getcwd", 91 }, 92 Action: specs.ActErrno, 93 }, 94 }, 95 }, 96 // Syscall matches but the arch is AUDIT_ARCH_X86 so the return 97 // value is the bad arch action. 98 input: asInput(&linux.SeccompData{Nr: 183, Arch: 0x40000003}), // 99 expected: uint32(killThreadAction), 100 }, 101 { 102 name: "match_name_errno", 103 config: specs.LinuxSeccomp{ 104 DefaultAction: specs.ActAllow, 105 Syscalls: []specs.LinuxSyscall{ 106 { 107 Names: []string{ 108 "getcwd", 109 "chmod", 110 }, 111 Action: specs.ActErrno, 112 }, 113 { 114 Names: []string{ 115 "write", 116 }, 117 Action: specs.ActTrace, 118 }, 119 }, 120 }, 121 input: testInput(nativeArchAuditNo, "getcwd", nil), 122 expected: uint32(errnoAction), 123 }, 124 { 125 name: "match_name_trace", 126 config: specs.LinuxSeccomp{ 127 DefaultAction: specs.ActAllow, 128 Syscalls: []specs.LinuxSyscall{ 129 { 130 Names: []string{ 131 "getcwd", 132 "chmod", 133 }, 134 Action: specs.ActErrno, 135 }, 136 { 137 Names: []string{ 138 "write", 139 }, 140 Action: specs.ActTrace, 141 }, 142 }, 143 }, 144 input: testInput(nativeArchAuditNo, "write", nil), 145 expected: uint32(traceAction), 146 }, 147 { 148 name: "no_match_name_allow", 149 config: specs.LinuxSeccomp{ 150 DefaultAction: specs.ActAllow, 151 Syscalls: []specs.LinuxSyscall{ 152 { 153 Names: []string{ 154 "getcwd", 155 "chmod", 156 }, 157 Action: specs.ActErrno, 158 }, 159 { 160 Names: []string{ 161 "write", 162 }, 163 Action: specs.ActTrace, 164 }, 165 }, 166 }, 167 input: testInput(nativeArchAuditNo, "openat", nil), 168 expected: uint32(allowAction), 169 }, 170 { 171 name: "simple_match_args", 172 config: specs.LinuxSeccomp{ 173 DefaultAction: specs.ActAllow, 174 Syscalls: []specs.LinuxSyscall{ 175 { 176 Names: []string{ 177 "clone", 178 }, 179 Args: []specs.LinuxSeccompArg{ 180 { 181 Index: 0, 182 Value: unix.CLONE_FS, 183 Op: specs.OpEqualTo, 184 }, 185 }, 186 Action: specs.ActErrno, 187 }, 188 }, 189 }, 190 input: testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS}), 191 expected: uint32(errnoAction), 192 }, 193 { 194 name: "match_args_or", 195 config: specs.LinuxSeccomp{ 196 DefaultAction: specs.ActAllow, 197 Syscalls: []specs.LinuxSyscall{ 198 { 199 Names: []string{ 200 "clone", 201 }, 202 Args: []specs.LinuxSeccompArg{ 203 { 204 Index: 0, 205 Value: unix.CLONE_FS, 206 Op: specs.OpEqualTo, 207 }, 208 { 209 Index: 0, 210 Value: unix.CLONE_VM, 211 Op: specs.OpEqualTo, 212 }, 213 }, 214 Action: specs.ActErrno, 215 }, 216 }, 217 }, 218 input: testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS}), 219 expected: uint32(errnoAction), 220 }, 221 { 222 name: "match_args_and", 223 config: specs.LinuxSeccomp{ 224 DefaultAction: specs.ActAllow, 225 Syscalls: []specs.LinuxSyscall{ 226 { 227 Names: []string{ 228 "getsockopt", 229 }, 230 Args: []specs.LinuxSeccompArg{ 231 { 232 Index: 1, 233 Value: unix.SOL_SOCKET, 234 Op: specs.OpEqualTo, 235 }, 236 { 237 Index: 2, 238 Value: unix.SO_PEERCRED, 239 Op: specs.OpEqualTo, 240 }, 241 }, 242 Action: specs.ActErrno, 243 }, 244 }, 245 }, 246 input: testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, unix.SOL_SOCKET, unix.SO_PEERCRED}), 247 expected: uint32(errnoAction), 248 }, 249 { 250 name: "no_match_args_and", 251 config: specs.LinuxSeccomp{ 252 DefaultAction: specs.ActAllow, 253 Syscalls: []specs.LinuxSyscall{ 254 { 255 Names: []string{ 256 "getsockopt", 257 }, 258 Args: []specs.LinuxSeccompArg{ 259 { 260 Index: 1, 261 Value: unix.SOL_SOCKET, 262 Op: specs.OpEqualTo, 263 }, 264 { 265 Index: 2, 266 Value: unix.SO_PEERCRED, 267 Op: specs.OpEqualTo, 268 }, 269 }, 270 Action: specs.ActErrno, 271 }, 272 }, 273 }, 274 input: testInput(nativeArchAuditNo, "getsockopt", &[6]uint64{0, unix.SOL_SOCKET}), 275 expected: uint32(allowAction), 276 }, 277 { 278 name: "Simple args (no match)", 279 config: specs.LinuxSeccomp{ 280 DefaultAction: specs.ActAllow, 281 Syscalls: []specs.LinuxSyscall{ 282 { 283 Names: []string{ 284 "clone", 285 }, 286 Args: []specs.LinuxSeccompArg{ 287 { 288 Index: 0, 289 Value: unix.CLONE_FS, 290 Op: specs.OpEqualTo, 291 }, 292 }, 293 Action: specs.ActErrno, 294 }, 295 }, 296 }, 297 input: testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_VM}), 298 expected: uint32(allowAction), 299 }, 300 { 301 name: "OpMaskedEqual (match)", 302 config: specs.LinuxSeccomp{ 303 DefaultAction: specs.ActAllow, 304 Syscalls: []specs.LinuxSyscall{ 305 { 306 Names: []string{ 307 "clone", 308 }, 309 Args: []specs.LinuxSeccompArg{ 310 { 311 Index: 0, 312 Value: unix.CLONE_FS, 313 ValueTwo: unix.CLONE_FS, 314 Op: specs.OpMaskedEqual, 315 }, 316 }, 317 Action: specs.ActErrno, 318 }, 319 }, 320 }, 321 input: testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS | unix.CLONE_VM}), 322 expected: uint32(errnoAction), 323 }, 324 { 325 name: "OpMaskedEqual (no match)", 326 config: specs.LinuxSeccomp{ 327 DefaultAction: specs.ActAllow, 328 Syscalls: []specs.LinuxSyscall{ 329 { 330 Names: []string{ 331 "clone", 332 }, 333 Args: []specs.LinuxSeccompArg{ 334 { 335 Index: 0, 336 Value: unix.CLONE_FS | unix.CLONE_VM, 337 ValueTwo: unix.CLONE_FS | unix.CLONE_VM, 338 Op: specs.OpMaskedEqual, 339 }, 340 }, 341 Action: specs.ActErrno, 342 }, 343 }, 344 }, 345 input: testInput(nativeArchAuditNo, "clone", &[6]uint64{unix.CLONE_FS}), 346 expected: uint32(allowAction), 347 }, 348 { 349 name: "OpMaskedEqual (clone)", 350 config: specs.LinuxSeccomp{ 351 DefaultAction: specs.ActErrno, 352 Syscalls: []specs.LinuxSyscall{ 353 { 354 Names: []string{ 355 "clone", 356 }, 357 // This comes from the Docker default seccomp 358 // profile for clone. 359 Args: []specs.LinuxSeccompArg{ 360 { 361 Index: 0, 362 Value: 0x7e020000, 363 ValueTwo: 0x0, 364 Op: specs.OpMaskedEqual, 365 }, 366 }, 367 Action: specs.ActAllow, 368 }, 369 }, 370 }, 371 input: testInput(nativeArchAuditNo, "clone", &[6]uint64{0x50f00}), 372 expected: uint32(allowAction), 373 }, 374 } 375 ) 376 377 // TestRunscSeccomp generates seccomp programs from OCI config and executes 378 // them using runsc's library, comparing against expected results. 379 func TestRunscSeccomp(t *testing.T) { 380 for _, tc := range seccompTests { 381 t.Run(tc.name, func(t *testing.T) { 382 runscProgram, err := BuildProgram(&tc.config) 383 if err != nil { 384 t.Fatalf("generating runsc BPF: %v", err) 385 } 386 387 if err := checkProgram(runscProgram, tc.input, tc.expected); err != nil { 388 t.Fatalf("running runsc BPF: %v", err) 389 } 390 }) 391 } 392 } 393 394 // checkProgram runs the given program over the given input and checks the 395 // result against the expected output. 396 func checkProgram(p bpf.Program, in bpf.Input, expected uint32) error { 397 result, err := bpf.Exec(p, in) 398 if err != nil { 399 return err 400 } 401 402 if result != expected { 403 // Include a decoded version of the program in output for debugging purposes. 404 decoded, _ := bpf.DecodeProgram(p) 405 return fmt.Errorf("Unexpected result: got: %d, expected: %d\nBPF Program\n%s", result, expected, decoded) 406 } 407 408 return nil 409 }