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