github.com/rigado/snapd@v2.42.5-go-mod+incompatible/cmd/snap-seccomp/main_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2017 Canonical Ltd 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License version 3 as 8 * published by the Free Software Foundation. 9 * 10 * This program is distributed in the hope that it will be useful, 11 * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 * GNU General Public License for more details. 14 * 15 * You should have received a copy of the GNU General Public License 16 * along with this program. If not, see <http://www.gnu.org/licenses/>. 17 * 18 */ 19 20 package main_test 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "math/rand" 26 "os" 27 "os/exec" 28 "path/filepath" 29 "strconv" 30 "strings" 31 "testing" 32 33 . "gopkg.in/check.v1" 34 35 "github.com/mvo5/libseccomp-golang" 36 37 "github.com/snapcore/snapd/arch" 38 main "github.com/snapcore/snapd/cmd/snap-seccomp" 39 "github.com/snapcore/snapd/osutil" 40 "github.com/snapcore/snapd/release" 41 "github.com/snapcore/snapd/testutil" 42 ) 43 44 // Hook up check.v1 into the "go test" runner 45 func Test(t *testing.T) { TestingT(t) } 46 47 type snapSeccompSuite struct { 48 seccompBpfLoader string 49 seccompSyscallRunner string 50 canCheckCompatArch bool 51 } 52 53 var _ = Suite(&snapSeccompSuite{}) 54 55 const ( 56 Deny = iota 57 Allow 58 ) 59 60 var seccompBpfLoaderContent = []byte(` 61 #include <fcntl.h> 62 #include <inttypes.h> 63 #include <stdint.h> 64 #include <stdio.h> 65 #include <stdlib.h> 66 #include <string.h> 67 #include <sys/prctl.h> 68 #include <unistd.h> 69 70 #include <linux/filter.h> 71 #include <linux/seccomp.h> 72 73 #define MAX_BPF_SIZE 32 * 1024 74 75 int sc_apply_seccomp_bpf(const char* profile_path) 76 { 77 unsigned char bpf[MAX_BPF_SIZE + 1]; // account for EOF 78 FILE* fp; 79 fp = fopen(profile_path, "rb"); 80 if (fp == NULL) { 81 fprintf(stderr, "cannot read %s\n", profile_path); 82 return -1; 83 } 84 85 // set 'size' to 1; to get bytes transferred 86 size_t num_read = fread(bpf, 1, sizeof(bpf), fp); 87 88 if (ferror(fp) != 0) { 89 perror("fread()"); 90 return -1; 91 } else if (feof(fp) == 0) { 92 fprintf(stderr, "file too big\n"); 93 return -1; 94 } 95 fclose(fp); 96 97 struct sock_fprog prog = { 98 .len = num_read / sizeof(struct sock_filter), 99 .filter = (struct sock_filter*)bpf, 100 }; 101 102 // Set NNP to allow loading seccomp policy into the kernel without 103 // root 104 if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { 105 perror("prctl(PR_NO_NEW_PRIVS, 1, 0, 0, 0)"); 106 return -1; 107 } 108 109 if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { 110 perror("prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, ...) failed"); 111 return -1; 112 } 113 return 0; 114 } 115 116 int main(int argc, char* argv[]) 117 { 118 int rc = 0; 119 if (argc < 2) { 120 fprintf(stderr, "Usage: %s <bpf file> [prog ...]\n", argv[0]); 121 return 1; 122 } 123 124 rc = sc_apply_seccomp_bpf(argv[1]); 125 if (rc != 0) 126 return -rc; 127 128 execv(argv[2], (char* const*)&argv[2]); 129 perror("execv failed"); 130 return 1; 131 } 132 `) 133 134 var seccompSyscallRunnerContent = []byte(` 135 #define _GNU_SOURCE 136 #include <errno.h> 137 #include <stdlib.h> 138 #include <sys/syscall.h> 139 #include <unistd.h> 140 #include <inttypes.h> 141 int main(int argc, char** argv) 142 { 143 uint32_t l[7]; 144 int syscall_ret, ret = 0; 145 for (int i = 0; i < 7; i++) { 146 errno = 0; 147 l[i] = strtoll(argv[i + 1], NULL, 10); 148 // exit '11' let's us know strtoll failed 149 if (errno != 0) 150 syscall(SYS_exit, 11, 0, 0, 0, 0, 0); 151 } 152 // There might be architecture-specific requirements. see "man syscall" 153 // for details. 154 syscall_ret = syscall(l[0], l[1], l[2], l[3], l[4], l[5], l[6]); 155 // 911 is our mocked errno 156 if (syscall_ret < 0 && errno == 911) { 157 ret = 10; 158 } 159 syscall(SYS_exit, ret, 0, 0, 0, 0, 0); 160 return 0; 161 } 162 `) 163 164 func (s *snapSeccompSuite) SetUpSuite(c *C) { 165 main.MockErrnoOnDenial(911) 166 167 // build seccomp-load helper 168 s.seccompBpfLoader = filepath.Join(c.MkDir(), "seccomp_bpf_loader") 169 err := ioutil.WriteFile(s.seccompBpfLoader+".c", seccompBpfLoaderContent, 0644) 170 c.Assert(err, IsNil) 171 cmd := exec.Command("gcc", "-Werror", "-Wall", s.seccompBpfLoader+".c", "-o", s.seccompBpfLoader) 172 cmd.Stdout = os.Stdout 173 cmd.Stderr = os.Stderr 174 err = cmd.Run() 175 c.Assert(err, IsNil) 176 177 // build syscall-runner helper 178 s.seccompSyscallRunner = filepath.Join(c.MkDir(), "seccomp_syscall_runner") 179 err = ioutil.WriteFile(s.seccompSyscallRunner+".c", seccompSyscallRunnerContent, 0644) 180 c.Assert(err, IsNil) 181 182 cmd = exec.Command("gcc", "-std=c99", "-Werror", "-Wall", "-static", s.seccompSyscallRunner+".c", "-o", s.seccompSyscallRunner, "-Wl,-static", "-static-libgcc") 183 cmd.Stdout = os.Stdout 184 cmd.Stderr = os.Stderr 185 err = cmd.Run() 186 c.Assert(err, IsNil) 187 188 // Amazon Linux 2 is 64bit only and there is no multilib support 189 s.canCheckCompatArch = !release.DistroLike("amzn") 190 191 // Build 32bit runner on amd64 to test non-native syscall handling. 192 // Ideally we would build for ppc64el->powerpc and arm64->armhf but 193 // it seems tricky to find the right gcc-multilib for this. 194 if arch.DpkgArchitecture() == "amd64" && s.canCheckCompatArch { 195 cmd = exec.Command(cmd.Args[0], cmd.Args[1:]...) 196 cmd.Args = append(cmd.Args, "-m32") 197 for i, k := range cmd.Args { 198 if k == s.seccompSyscallRunner { 199 cmd.Args[i] = s.seccompSyscallRunner + ".m32" 200 } 201 } 202 if output, err := cmd.CombinedOutput(); err != nil { 203 fmt.Printf("cannot build multi-lib syscall runner: %v\n%s", err, output) 204 } 205 } 206 } 207 208 // Runs the policy through the kernel: 209 // 1. runs main.Compile() 210 // 2. the program in seccompBpfLoaderContent with the output file as an 211 // argument 212 // 3. the program in seccompBpfLoaderContent loads the output file BPF into 213 // the kernel and executes the program in seccompBpfRunnerContent with the 214 // syscall and arguments specified by the test 215 // 216 // In this manner, in addition to verifying policy syntax we are able to 217 // unit test the resulting bpf in several ways. 218 // 219 // Full testing of applied policy is done elsewhere via spread tests. 220 // 221 // Note that we skip testing prctl(PR_SET_ENDIAN) - it causes havoc when 222 // it is run. We will also need to skip: fadvise64_64, 223 // ftruncate64, posix_fadvise, pread64, pwrite64, readahead, 224 // sync_file_range, and truncate64. 225 // Once we start using those. See `man syscall` 226 func (s *snapSeccompSuite) runBpf(c *C, seccompWhitelist, bpfInput string, expected int) { 227 // Common syscalls we need to allow for a minimal statically linked 228 // c program. 229 // 230 // If we compile a test program for each test we can get away with 231 // a even smaller set of syscalls: execve,exit essentially. But it 232 // means a much longer test run (30s vs 2s). Commit d288d89 contains 233 // the code for this. 234 common := ` 235 execve 236 uname 237 brk 238 arch_prctl 239 readlink 240 access 241 sysinfo 242 exit 243 # i386 244 set_thread_area 245 # armhf 246 set_tls 247 # arm64 248 readlinkat 249 faccessat 250 # i386 from amd64 251 restart_syscall 252 ` 253 bpfPath := filepath.Join(c.MkDir(), "bpf") 254 err := main.Compile([]byte(common+seccompWhitelist), bpfPath) 255 c.Assert(err, IsNil) 256 257 // default syscall runner 258 syscallRunner := s.seccompSyscallRunner 259 260 // syscallName;arch;arg1,arg2... 261 l := strings.Split(bpfInput, ";") 262 syscallName := l[0] 263 syscallArch := "native" 264 if len(l) > 1 { 265 syscallArch = l[1] 266 } 267 268 syscallNr, err := seccomp.GetSyscallFromName(syscallName) 269 c.Assert(err, IsNil) 270 271 // Check if we want to test non-native architecture 272 // handling. Doing this via the in-kernel tests is tricky as 273 // we need a kernel that can run the architecture and a 274 // compiler that can produce the required binaries. Currently 275 // we only test amd64 running i386 here. 276 if syscallArch != "native" { 277 syscallNr, err = seccomp.GetSyscallFromNameByArch(syscallName, main.DpkgArchToScmpArch(syscallArch)) 278 c.Assert(err, IsNil) 279 280 switch syscallArch { 281 case "amd64": 282 // default syscallRunner 283 case "i386": 284 syscallRunner = s.seccompSyscallRunner + ".m32" 285 default: 286 c.Errorf("unexpected non-native arch: %s", syscallArch) 287 } 288 } 289 switch { 290 case syscallNr == -101: 291 // "socket" 292 // see libseccomp: _s390x_sock_demux(), _x86_sock_demux() 293 // the -101 is translated to 359 (socket) 294 syscallNr = 359 295 case syscallNr == -10165: 296 // "mknod" on arm64 is not available at all on arm64 297 // only "mknodat" but libseccomp will not generate a 298 // "mknodat" whitelist, it geneates a whitelist with 299 // syscall -10165 (!?!) so we cannot test this. 300 c.Skip("skipping mknod tests on arm64") 301 case syscallNr < 0: 302 c.Errorf("failed to resolve %v: %v", l[0], syscallNr) 303 return 304 } 305 306 var syscallRunnerArgs [7]string 307 syscallRunnerArgs[0] = strconv.FormatInt(int64(syscallNr), 10) 308 if len(l) > 2 { 309 args := strings.Split(l[2], ",") 310 for i := range args { 311 // init with random number argument 312 syscallArg := (uint64)(rand.Uint32()) 313 // override if the test specifies a specific number; 314 // this must match main.go:readNumber() 315 if nr, ok := main.SeccompResolver[args[i]]; ok { 316 syscallArg = nr 317 } else if nr, err := strconv.ParseUint(args[i], 10, 32); err == nil { 318 syscallArg = nr 319 } else if nr, err := strconv.ParseInt(args[i], 10, 32); err == nil { 320 syscallArg = uint64(uint32(nr)) 321 } 322 syscallRunnerArgs[i+1] = strconv.FormatUint(syscallArg, 10) 323 } 324 } 325 326 cmd := exec.Command(s.seccompBpfLoader, bpfPath, syscallRunner, syscallRunnerArgs[0], syscallRunnerArgs[1], syscallRunnerArgs[2], syscallRunnerArgs[3], syscallRunnerArgs[4], syscallRunnerArgs[5], syscallRunnerArgs[6]) 327 cmd.Stdin = os.Stdin 328 cmd.Stdout = os.Stdout 329 cmd.Stderr = os.Stderr 330 err = cmd.Run() 331 switch expected { 332 case Allow: 333 if err != nil { 334 c.Fatalf("unexpected error for %q (failed to run %q)", seccompWhitelist, err) 335 } 336 case Deny: 337 if err == nil { 338 c.Fatalf("unexpected success for %q %q (ran but should have failed)", seccompWhitelist, bpfInput) 339 } 340 default: 341 c.Fatalf("unknown expected result %v", expected) 342 } 343 } 344 345 func (s *snapSeccompSuite) TestUnrestricted(c *C) { 346 inp := "@unrestricted\n" 347 outPath := filepath.Join(c.MkDir(), "bpf") 348 err := main.Compile([]byte(inp), outPath) 349 c.Assert(err, IsNil) 350 351 c.Check(outPath, testutil.FileEquals, inp) 352 } 353 354 // TestCompile iterates over a range of textual seccomp whitelist rules and 355 // mocked kernel syscall input. For each rule, the test consists of compiling 356 // the rule into a bpf program and then running that program on a virtual bpf 357 // machine and comparing the bpf machine output to the specified expected 358 // output and seccomp operation. Eg: 359 // {"<rule>", "<mocked kernel input>", <seccomp result>} 360 // 361 // Eg to test that the rule 'read >=2' is allowed with 'read(2)' and 'read(3)' 362 // and denied with 'read(1)' and 'read(0)', add the following tests: 363 // {"read >=2", "read;native;2", Allow}, 364 // {"read >=2", "read;native;3", Allow}, 365 // {"read >=2", "read;native;1", main.SeccompRetKill}, 366 // {"read >=2", "read;native;0", main.SeccompRetKill}, 367 func (s *snapSeccompSuite) TestCompile(c *C) { 368 369 for _, t := range []struct { 370 seccompWhitelist string 371 bpfInput string 372 expected int 373 }{ 374 // special 375 {"@complain", "execve", Allow}, 376 377 // trivial allow 378 {"read", "read", Allow}, 379 {"read\nwrite\nexecve\n", "write", Allow}, 380 381 // trivial denial 382 {"read", "ioctl", Deny}, 383 384 // test argument filtering syntax, we currently support: 385 // >=, <=, !, <, >, | 386 // modifiers. 387 388 // reads >= 2 are ok 389 {"read >=2", "read;native;2", Allow}, 390 {"read >=2", "read;native;3", Allow}, 391 // but not reads < 2, those get killed 392 {"read >=2", "read;native;1", Deny}, 393 {"read >=2", "read;native;0", Deny}, 394 395 // reads <= 2 are ok 396 {"read <=2", "read;native;0", Allow}, 397 {"read <=2", "read;native;1", Allow}, 398 {"read <=2", "read;native;2", Allow}, 399 // but not reads >2, those get killed 400 {"read <=2", "read;native;3", Deny}, 401 {"read <=2", "read;native;4", Deny}, 402 403 // reads that are not 2 are ok 404 {"read !2", "read;native;1", Allow}, 405 {"read !2", "read;native;3", Allow}, 406 // but not 2, this gets killed 407 {"read !2", "read;native;2", Deny}, 408 409 // reads > 2 are ok 410 {"read >2", "read;native;4", Allow}, 411 {"read >2", "read;native;3", Allow}, 412 // but not reads <= 2, those get killed 413 {"read >2", "read;native;2", Deny}, 414 {"read >2", "read;native;1", Deny}, 415 416 // reads < 2 are ok 417 {"read <2", "read;native;0", Allow}, 418 {"read <2", "read;native;1", Allow}, 419 // but not reads >= 2, those get killed 420 {"read <2", "read;native;2", Deny}, 421 {"read <2", "read;native;3", Deny}, 422 423 // FIXME: test maskedEqual better 424 {"read |1", "read;native;1", Allow}, 425 {"read |1", "read;native;2", Deny}, 426 427 // exact match, reads == 2 are ok 428 {"read 2", "read;native;2", Allow}, 429 // but not those != 2 430 {"read 2", "read;native;3", Deny}, 431 {"read 2", "read;native;1", Deny}, 432 433 // test actual syscalls and their expected usage 434 {"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", Allow}, 435 {"ioctl - TIOCSTI", "ioctl;native;-,99", Deny}, 436 {"ioctl - !TIOCSTI", "ioctl;native;-,TIOCSTI", Deny}, 437 438 // test_bad_seccomp_filter_args_clone 439 {"setns - CLONE_NEWNET", "setns;native;-,99", Deny}, 440 {"setns - CLONE_NEWNET", "setns;native;-,CLONE_NEWNET", Allow}, 441 442 // test_bad_seccomp_filter_args_mknod 443 {"mknod - |S_IFIFO", "mknod;native;-,S_IFIFO", Allow}, 444 {"mknod - |S_IFIFO", "mknod;native;-,99", Deny}, 445 446 // test_bad_seccomp_filter_args_prctl 447 {"prctl PR_CAP_AMBIENT_RAISE", "prctl;native;PR_CAP_AMBIENT_RAISE", Allow}, 448 {"prctl PR_CAP_AMBIENT_RAISE", "prctl;native;99", Deny}, 449 450 // test_bad_seccomp_filter_args_prio 451 {"setpriority PRIO_PROCESS 0 >=0", "setpriority;native;PRIO_PROCESS,0,19", Allow}, 452 {"setpriority PRIO_PROCESS 0 >=0", "setpriority;native;99", Deny}, 453 454 // test_bad_seccomp_filter_args_quotactl 455 {"quotactl Q_GETQUOTA", "quotactl;native;Q_GETQUOTA", Allow}, 456 {"quotactl Q_GETQUOTA", "quotactl;native;99", Deny}, 457 458 // test_bad_seccomp_filter_args_termios 459 {"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", Allow}, 460 {"ioctl - TIOCSTI", "ioctl;native;-,99", Deny}, 461 462 // u:root g:root 463 {"fchown - u:root g:root", "fchown;native;-,0,0", Allow}, 464 {"fchown - u:root g:root", "fchown;native;-,99,0", Deny}, 465 {"chown - u:root g:root", "chown;native;-,0,0", Allow}, 466 {"chown - u:root g:root", "chown;native;-,99,0", Deny}, 467 468 // u:root -1 469 {"chown - u:root -1", "chown;native;-,0,-1", Allow}, 470 {"chown - u:root -1", "chown;native;-,99,-1", Deny}, 471 {"chown - -1 u:root", "chown;native;-,-1,0", Allow}, 472 {"chown - -1 u:root", "chown;native;-,99,0", Deny}, 473 {"chown - -1 -1", "chown;native;-,-1,-1", Allow}, 474 {"chown - -1 -1", "chown;native;-,99,-1", Deny}, 475 } { 476 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 477 } 478 } 479 480 // TestCompileSocket runs in a separate tests so that only this part 481 // can be skipped when "socketcall()" is used instead of "socket()". 482 // 483 // Some architectures (i386, s390x) use the "socketcall" syscall instead 484 // of "socket". This is the case on Ubuntu 14.04, 17.04, 17.10 485 func (s *snapSeccompSuite) TestCompileSocket(c *C) { 486 if release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04" { 487 c.Skip("14.04/i386 uses socketcall which cannot be tested here") 488 } 489 490 for _, t := range []struct { 491 seccompWhitelist string 492 bpfInput string 493 expected int 494 }{ 495 496 // test_bad_seccomp_filter_args_socket 497 {"socket AF_UNIX", "socket;native;AF_UNIX", Allow}, 498 {"socket AF_UNIX", "socket;native;99", Deny}, 499 {"socket - SOCK_STREAM", "socket;native;-,SOCK_STREAM", Allow}, 500 {"socket - SOCK_STREAM", "socket;native;-,99", Deny}, 501 {"socket AF_CONN", "socket;native;AF_CONN", Allow}, 502 {"socket AF_CONN", "socket;native;99", Deny}, 503 } { 504 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 505 } 506 507 } 508 509 func (s *snapSeccompSuite) TestCompileBadInput(c *C) { 510 for _, t := range []struct { 511 inp string 512 errMsg string 513 }{ 514 // test_bad_seccomp_filter_args_clone (various typos in input) 515 {"setns - CLONE_NEWNE", `cannot parse line: cannot parse token "CLONE_NEWNE" \(line "setns - CLONE_NEWNE"\)`}, 516 {"setns - CLONE_NEWNETT", `cannot parse line: cannot parse token "CLONE_NEWNETT" \(line "setns - CLONE_NEWNETT"\)`}, 517 {"setns - CL0NE_NEWNET", `cannot parse line: cannot parse token "CL0NE_NEWNET" \(line "setns - CL0NE_NEWNET"\)`}, 518 519 // test_bad_seccomp_filter_args_mknod (various typos in input) 520 {"mknod - |S_IFIF", `cannot parse line: cannot parse token "S_IFIF" \(line "mknod - |S_IFIF"\)`}, 521 {"mknod - |S_IFIFOO", `cannot parse line: cannot parse token "S_IFIFOO" \(line "mknod - |S_IFIFOO"\)`}, 522 {"mknod - |S_!FIFO", `cannot parse line: cannot parse token "S_IFIFO" \(line "mknod - |S_!FIFO"\)`}, 523 524 // test_bad_seccomp_filter_args_null 525 {"socket S\x00CK_STREAM", `cannot parse line: cannot parse token .*`}, 526 {"socket SOCK_STREAM\x00bad stuff", `cannot parse line: cannot parse token .*`}, 527 528 // test_bad_seccomp_filter_args 529 {"setpriority bar", `cannot parse line: cannot parse token "bar" .*`}, 530 {"setpriority -1", `cannot parse line: cannot parse token "-1" .*`}, 531 {"setpriority 0 - -1 0", `cannot parse line: cannot parse token "-1" .*`}, 532 {"setpriority --10", `cannot parse line: cannot parse token "--10" .*`}, 533 {"setpriority 0:10", `cannot parse line: cannot parse token "0:10" .*`}, 534 {"setpriority 0-10", `cannot parse line: cannot parse token "0-10" .*`}, 535 {"setpriority 0,1", `cannot parse line: cannot parse token "0,1" .*`}, 536 {"setpriority 0x0", `cannot parse line: cannot parse token "0x0" .*`}, 537 {"setpriority a1", `cannot parse line: cannot parse token "a1" .*`}, 538 {"setpriority 1a", `cannot parse line: cannot parse token "1a" .*`}, 539 {"setpriority 1-", `cannot parse line: cannot parse token "1-" .*`}, 540 {"setpriority 1\\ 2", `cannot parse line: cannot parse token "1\\\\" .*`}, 541 {"setpriority 1\\n2", `cannot parse line: cannot parse token "1\\\\n2" .*`}, 542 // 1 bigger than uint32 543 {"chown 0 4294967296", `cannot parse line: cannot parse token "4294967296" .*`}, 544 // 1 smaller than int32 545 {"chown - 0 -2147483649", `cannot parse line: cannot parse token "-2147483649" .*`}, 546 {"setpriority 999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999", `cannot parse line: cannot parse token "999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999" .*`}, 547 {"mbind - - - - - - 7", `cannot parse line: too many arguments specified for syscall 'mbind' in line.*`}, 548 {"mbind 1 2 3 4 5 6 7", `cannot parse line: too many arguments specified for syscall 'mbind' in line.*`}, 549 // test_bad_seccomp_filter_args_prctl 550 {"prctl PR_GET_SECCOM", `cannot parse line: cannot parse token "PR_GET_SECCOM" .*`}, 551 {"prctl PR_GET_SECCOMPP", `cannot parse line: cannot parse token "PR_GET_SECCOMPP" .*`}, 552 {"prctl PR_GET_SECC0MP", `cannot parse line: cannot parse token "PR_GET_SECC0MP" .*`}, 553 {"prctl PR_CAP_AMBIENT_RAIS", `cannot parse line: cannot parse token "PR_CAP_AMBIENT_RAIS" .*`}, 554 {"prctl PR_CAP_AMBIENT_RAISEE", `cannot parse line: cannot parse token "PR_CAP_AMBIENT_RAISEE" .*`}, 555 // test_bad_seccomp_filter_args_prio 556 {"setpriority PRIO_PROCES 0 >=0", `cannot parse line: cannot parse token "PRIO_PROCES" .*`}, 557 {"setpriority PRIO_PROCESSS 0 >=0", `cannot parse line: cannot parse token "PRIO_PROCESSS" .*`}, 558 {"setpriority PRIO_PR0CESS 0 >=0", `cannot parse line: cannot parse token "PRIO_PR0CESS" .*`}, 559 // test_bad_seccomp_filter_args_quotactl 560 {"quotactl Q_GETQUOT", `cannot parse line: cannot parse token "Q_GETQUOT" .*`}, 561 {"quotactl Q_GETQUOTAA", `cannot parse line: cannot parse token "Q_GETQUOTAA" .*`}, 562 {"quotactl Q_GETQU0TA", `cannot parse line: cannot parse token "Q_GETQU0TA" .*`}, 563 // test_bad_seccomp_filter_args_socket 564 {"socket AF_UNI", `cannot parse line: cannot parse token "AF_UNI" .*`}, 565 {"socket AF_UNIXX", `cannot parse line: cannot parse token "AF_UNIXX" .*`}, 566 {"socket AF_UN!X", `cannot parse line: cannot parse token "AF_UN!X" .*`}, 567 {"socket - SOCK_STREA", `cannot parse line: cannot parse token "SOCK_STREA" .*`}, 568 {"socket - SOCK_STREAMM", `cannot parse line: cannot parse token "SOCK_STREAMM" .*`}, 569 {"socket - NETLINK_ROUT", `cannot parse line: cannot parse token "NETLINK_ROUT" .*`}, 570 {"socket - NETLINK_ROUTEE", `cannot parse line: cannot parse token "NETLINK_ROUTEE" .*`}, 571 {"socket - NETLINK_R0UTE", `cannot parse line: cannot parse token "NETLINK_R0UTE" .*`}, 572 // test_bad_seccomp_filter_args_termios 573 {"ioctl - TIOCST", `cannot parse line: cannot parse token "TIOCST" .*`}, 574 {"ioctl - TIOCSTII", `cannot parse line: cannot parse token "TIOCSTII" .*`}, 575 {"ioctl - TIOCST1", `cannot parse line: cannot parse token "TIOCST1" .*`}, 576 // ensure missing numbers are caught 577 {"setpriority >", `cannot parse line: cannot parse token ">" .*`}, 578 {"setpriority >=", `cannot parse line: cannot parse token ">=" .*`}, 579 {"setpriority <", `cannot parse line: cannot parse token "<" .*`}, 580 {"setpriority <=", `cannot parse line: cannot parse token "<=" .*`}, 581 {"setpriority |", `cannot parse line: cannot parse token "|" .*`}, 582 {"setpriority !", `cannot parse line: cannot parse token "!" .*`}, 583 584 // u:<username> 585 {"setuid :root", `cannot parse line: cannot parse token ":root" .*`}, 586 {"setuid u:", `cannot parse line: cannot parse token "u:" \(line "setuid u:"\): "" must be a valid username`}, 587 {"setuid u:!", `cannot parse line: cannot parse token "u:!" \(line "setuid u:!"\): "!" must be a valid username`}, 588 {"setuid u:b@d|npu+", `cannot parse line: cannot parse token "u:b@d|npu+" \(line "setuid u:b@d|npu+"\): "b@d|npu+" must be a valid username`}, 589 {"setuid u:snap|bad", `cannot parse line: cannot parse token "u:snap|bad" \(line "setuid u:snap|bad"\): "snap|bad" must be a valid username`}, 590 {"setuid U:root", `cannot parse line: cannot parse token "U:root" .*`}, 591 {"setuid u:nonexistent", `cannot parse line: cannot parse token "u:nonexistent" \(line "setuid u:nonexistent"\): user: unknown user nonexistent`}, 592 // g:<groupname> 593 {"setgid g:", `cannot parse line: cannot parse token "g:" \(line "setgid g:"\): "" must be a valid group name`}, 594 {"setgid g:!", `cannot parse line: cannot parse token "g:!" \(line "setgid g:!"\): "!" must be a valid group name`}, 595 {"setgid g:b@d|npu+", `cannot parse line: cannot parse token "g:b@d|npu+" \(line "setgid g:b@d|npu+"\): "b@d|npu+" must be a valid group name`}, 596 {"setgid g:snap|bad", `cannot parse line: cannot parse token "g:snap|bad" \(line "setgid g:snap|bad"\): "snap|bad" must be a valid group name`}, 597 {"setgid G:root", `cannot parse line: cannot parse token "G:root" .*`}, 598 {"setgid g:nonexistent", `cannot parse line: cannot parse token "g:nonexistent" \(line "setgid g:nonexistent"\): group: unknown group nonexistent`}, 599 } { 600 outPath := filepath.Join(c.MkDir(), "bpf") 601 err := main.Compile([]byte(t.inp), outPath) 602 c.Check(err, ErrorMatches, t.errMsg, Commentf("%q errors in unexpected ways, got: %q expected %q", t.inp, err, t.errMsg)) 603 } 604 } 605 606 // ported from test_restrictions_working_args_socket 607 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsSocket(c *C) { 608 if release.ReleaseInfo.ID == "ubuntu" && release.ReleaseInfo.VersionID == "14.04" { 609 c.Skip("14.04/i386 uses socketcall which cannot be tested here") 610 } 611 612 for _, pre := range []string{"AF", "PF"} { 613 for _, i := range []string{"UNIX", "LOCAL", "INET", "INET6", "IPX", "NETLINK", "X25", "AX25", "ATMPVC", "APPLETALK", "PACKET", "ALG", "CAN", "BRIDGE", "NETROM", "ROSE", "NETBEUI", "SECURITY", "KEY", "ASH", "ECONET", "SNA", "IRDA", "PPPOX", "WANPIPE", "BLUETOOTH", "RDS", "LLC", "TIPC", "IUCV", "RXRPC", "ISDN", "PHONET", "IEEE802154", "CAIF", "NFC", "VSOCK", "MPLS", "IB"} { 614 seccompWhitelist := fmt.Sprintf("socket %s_%s", pre, i) 615 bpfInputGood := fmt.Sprintf("socket;native;%s_%s", pre, i) 616 bpfInputBad := "socket;native;99999" 617 s.runBpf(c, seccompWhitelist, bpfInputGood, Allow) 618 s.runBpf(c, seccompWhitelist, bpfInputBad, Deny) 619 620 for _, j := range []string{"SOCK_STREAM", "SOCK_DGRAM", "SOCK_SEQPACKET", "SOCK_RAW", "SOCK_RDM", "SOCK_PACKET"} { 621 seccompWhitelist := fmt.Sprintf("socket %s_%s %s", pre, i, j) 622 bpfInputGood := fmt.Sprintf("socket;native;%s_%s,%s", pre, i, j) 623 bpfInputBad := fmt.Sprintf("socket;native;%s_%s,9999", pre, i) 624 s.runBpf(c, seccompWhitelist, bpfInputGood, Allow) 625 s.runBpf(c, seccompWhitelist, bpfInputBad, Deny) 626 } 627 } 628 } 629 630 for _, i := range []string{"NETLINK_ROUTE", "NETLINK_USERSOCK", "NETLINK_FIREWALL", "NETLINK_SOCK_DIAG", "NETLINK_NFLOG", "NETLINK_XFRM", "NETLINK_SELINUX", "NETLINK_ISCSI", "NETLINK_AUDIT", "NETLINK_FIB_LOOKUP", "NETLINK_CONNECTOR", "NETLINK_NETFILTER", "NETLINK_IP6_FW", "NETLINK_DNRTMSG", "NETLINK_KOBJECT_UEVENT", "NETLINK_GENERIC", "NETLINK_SCSITRANSPORT", "NETLINK_ECRYPTFS", "NETLINK_RDMA", "NETLINK_CRYPTO", "NETLINK_INET_DIAG"} { 631 for _, j := range []string{"AF_NETLINK", "PF_NETLINK"} { 632 seccompWhitelist := fmt.Sprintf("socket %s - %s", j, i) 633 bpfInputGood := fmt.Sprintf("socket;native;%s,0,%s", j, i) 634 bpfInputBad := fmt.Sprintf("socket;native;%s,0,99", j) 635 s.runBpf(c, seccompWhitelist, bpfInputGood, Allow) 636 s.runBpf(c, seccompWhitelist, bpfInputBad, Deny) 637 } 638 } 639 } 640 641 // ported from test_restrictions_working_args_quotactl 642 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsQuotactl(c *C) { 643 for _, arg := range []string{"Q_QUOTAON", "Q_QUOTAOFF", "Q_GETQUOTA", "Q_SETQUOTA", "Q_GETINFO", "Q_SETINFO", "Q_GETFMT", "Q_SYNC", "Q_XQUOTAON", "Q_XQUOTAOFF", "Q_XGETQUOTA", "Q_XSETQLIM", "Q_XGETQSTAT", "Q_XQUOTARM"} { 644 // good input 645 seccompWhitelist := fmt.Sprintf("quotactl %s", arg) 646 bpfInputGood := fmt.Sprintf("quotactl;native;%s", arg) 647 s.runBpf(c, seccompWhitelist, bpfInputGood, Allow) 648 // bad input 649 for _, bad := range []string{"quotactl;native;99999", "read;native;"} { 650 s.runBpf(c, seccompWhitelist, bad, Deny) 651 } 652 } 653 } 654 655 // ported from test_restrictions_working_args_prctl 656 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsPrctl(c *C) { 657 for _, arg := range []string{"PR_CAP_AMBIENT", "PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL", "PR_CAPBSET_READ", "PR_CAPBSET_DROP", "PR_SET_CHILD_SUBREAPER", "PR_GET_CHILD_SUBREAPER", "PR_SET_DUMPABLE", "PR_GET_DUMPABLE", "PR_GET_ENDIAN", "PR_SET_FPEMU", "PR_GET_FPEMU", "PR_SET_FPEXC", "PR_GET_FPEXC", "PR_SET_KEEPCAPS", "PR_GET_KEEPCAPS", "PR_MCE_KILL", "PR_MCE_KILL_GET", "PR_SET_MM", "PR_SET_MM_START_CODE", "PR_SET_MM_END_CODE", "PR_SET_MM_START_DATA", "PR_SET_MM_END_DATA", "PR_SET_MM_START_STACK", "PR_SET_MM_START_BRK", "PR_SET_MM_BRK", "PR_SET_MM_ARG_START", "PR_SET_MM_ARG_END", "PR_SET_MM_ENV_START", "PR_SET_MM_ENV_END", "PR_SET_MM_AUXV", "PR_SET_MM_EXE_FILE", "PR_MPX_ENABLE_MANAGEMENT", "PR_MPX_DISABLE_MANAGEMENT", "PR_SET_NAME", "PR_GET_NAME", "PR_SET_NO_NEW_PRIVS", "PR_GET_NO_NEW_PRIVS", "PR_SET_PDEATHSIG", "PR_GET_PDEATHSIG", "PR_SET_PTRACER", "PR_SET_SECCOMP", "PR_GET_SECCOMP", "PR_SET_SECUREBITS", "PR_GET_SECUREBITS", "PR_SET_THP_DISABLE", "PR_TASK_PERF_EVENTS_DISABLE", "PR_TASK_PERF_EVENTS_ENABLE", "PR_GET_THP_DISABLE", "PR_GET_TID_ADDRESS", "PR_SET_TIMERSLACK", "PR_GET_TIMERSLACK", "PR_SET_TIMING", "PR_GET_TIMING", "PR_SET_TSC", "PR_GET_TSC", "PR_SET_UNALIGN", "PR_GET_UNALIGN"} { 658 // good input 659 seccompWhitelist := fmt.Sprintf("prctl %s", arg) 660 bpfInputGood := fmt.Sprintf("prctl;native;%s", arg) 661 s.runBpf(c, seccompWhitelist, bpfInputGood, Allow) 662 // bad input 663 for _, bad := range []string{"prctl;native;99999", "setpriority;native;"} { 664 s.runBpf(c, seccompWhitelist, bad, Deny) 665 } 666 667 if arg == "PR_CAP_AMBIENT" { 668 for _, j := range []string{"PR_CAP_AMBIENT_RAISE", "PR_CAP_AMBIENT_LOWER", "PR_CAP_AMBIENT_IS_SET", "PR_CAP_AMBIENT_CLEAR_ALL"} { 669 seccompWhitelist := fmt.Sprintf("prctl %s %s", arg, j) 670 bpfInputGood := fmt.Sprintf("prctl;native;%s,%s", arg, j) 671 s.runBpf(c, seccompWhitelist, bpfInputGood, Allow) 672 for _, bad := range []string{ 673 fmt.Sprintf("prctl;native;%s,99999", arg), 674 "setpriority;native;", 675 } { 676 s.runBpf(c, seccompWhitelist, bad, Deny) 677 } 678 } 679 } 680 } 681 } 682 683 // ported from test_restrictions_working_args_clone 684 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsClone(c *C) { 685 for _, t := range []struct { 686 seccompWhitelist string 687 bpfInput string 688 expected int 689 }{ 690 // good input 691 {"setns - CLONE_NEWIPC", "setns;native;-,CLONE_NEWIPC", Allow}, 692 {"setns - CLONE_NEWNET", "setns;native;-,CLONE_NEWNET", Allow}, 693 {"setns - CLONE_NEWNS", "setns;native;-,CLONE_NEWNS", Allow}, 694 {"setns - CLONE_NEWPID", "setns;native;-,CLONE_NEWPID", Allow}, 695 {"setns - CLONE_NEWUSER", "setns;native;-,CLONE_NEWUSER", Allow}, 696 {"setns - CLONE_NEWUTS", "setns;native;-,CLONE_NEWUTS", Allow}, 697 // bad input 698 {"setns - CLONE_NEWIPC", "setns;native;-,99", Deny}, 699 {"setns - CLONE_NEWNET", "setns;native;-,99", Deny}, 700 {"setns - CLONE_NEWNS", "setns;native;-,99", Deny}, 701 {"setns - CLONE_NEWPID", "setns;native;-,99", Deny}, 702 {"setns - CLONE_NEWUSER", "setns;native;-,99", Deny}, 703 {"setns - CLONE_NEWUTS", "setns;native;-,99", Deny}, 704 } { 705 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 706 } 707 } 708 709 // ported from test_restrictions_working_args_mknod 710 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsMknod(c *C) { 711 for _, t := range []struct { 712 seccompWhitelist string 713 bpfInput string 714 expected int 715 }{ 716 // good input 717 {"mknod - S_IFREG", "mknod;native;-,S_IFREG", Allow}, 718 {"mknod - S_IFCHR", "mknod;native;-,S_IFCHR", Allow}, 719 {"mknod - S_IFBLK", "mknod;native;-,S_IFBLK", Allow}, 720 {"mknod - S_IFIFO", "mknod;native;-,S_IFIFO", Allow}, 721 {"mknod - S_IFSOCK", "mknod;native;-,S_IFSOCK", Allow}, 722 // bad input 723 {"mknod - S_IFREG", "mknod;native;-,999", Deny}, 724 {"mknod - S_IFCHR", "mknod;native;-,999", Deny}, 725 {"mknod - S_IFBLK", "mknod;native;-,999", Deny}, 726 {"mknod - S_IFIFO", "mknod;native;-,999", Deny}, 727 {"mknod - S_IFSOCK", "mknod;native;-,999", Deny}, 728 } { 729 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 730 } 731 } 732 733 // ported from test_restrictions_working_args_prio 734 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsPrio(c *C) { 735 for _, t := range []struct { 736 seccompWhitelist string 737 bpfInput string 738 expected int 739 }{ 740 // good input 741 {"setpriority PRIO_PROCESS", "setpriority;native;PRIO_PROCESS", Allow}, 742 {"setpriority PRIO_PGRP", "setpriority;native;PRIO_PGRP", Allow}, 743 {"setpriority PRIO_USER", "setpriority;native;PRIO_USER", Allow}, 744 // bad input 745 {"setpriority PRIO_PROCESS", "setpriority;native;99", Deny}, 746 {"setpriority PRIO_PGRP", "setpriority;native;99", Deny}, 747 {"setpriority PRIO_USER", "setpriority;native;99", Deny}, 748 } { 749 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 750 } 751 } 752 753 // ported from test_restrictions_working_args_termios 754 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsTermios(c *C) { 755 for _, t := range []struct { 756 seccompWhitelist string 757 bpfInput string 758 expected int 759 }{ 760 // good input 761 {"ioctl - TIOCSTI", "ioctl;native;-,TIOCSTI", Allow}, 762 // bad input 763 {"ioctl - TIOCSTI", "quotactl;native;-,99", Deny}, 764 } { 765 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 766 } 767 } 768 769 func (s *snapSeccompSuite) TestRestrictionsWorkingArgsUidGid(c *C) { 770 // while 'root' user usually has uid 0, 'daemon' user uid may vary 771 // across distributions, best lookup the uid directly 772 daemonUid, err := osutil.FindUid("daemon") 773 774 if err != nil { 775 c.Skip("daemon user not available, perhaps we are in a buildroot jail") 776 } 777 778 for _, t := range []struct { 779 seccompWhitelist string 780 bpfInput string 781 expected int 782 }{ 783 // good input. 'root' is guaranteed to be '0' and 'daemon' uid 784 // was determined at runtime 785 {"setuid u:root", "setuid;native;0", Allow}, 786 {"setuid u:daemon", fmt.Sprintf("setuid;native;%v", daemonUid), Allow}, 787 {"setgid g:root", "setgid;native;0", Allow}, 788 {"setgid g:daemon", fmt.Sprintf("setgid;native;%v", daemonUid), Allow}, 789 // bad input 790 {"setuid u:root", "setuid;native;99", Deny}, 791 {"setuid u:daemon", "setuid;native;99", Deny}, 792 {"setgid g:root", "setgid;native;99", Deny}, 793 {"setgid g:daemon", "setgid;native;99", Deny}, 794 } { 795 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 796 } 797 } 798 799 func (s *snapSeccompSuite) TestCompatArchWorks(c *C) { 800 if !s.canCheckCompatArch { 801 c.Skip("multi-lib syscall runner not supported by this host") 802 } 803 for _, t := range []struct { 804 arch string 805 seccompWhitelist string 806 bpfInput string 807 expected int 808 }{ 809 // on amd64 we add compat i386 810 {"amd64", "read", "read;i386", Allow}, 811 {"amd64", "read", "read;amd64", Allow}, 812 {"amd64", "chown - 0 -1", "chown;i386;-,0,-1", Allow}, 813 {"amd64", "chown - 0 -1", "chown;amd64;-,0,-1", Allow}, 814 {"amd64", "chown - 0 -1", "chown;i386;-,99,-1", Deny}, 815 {"amd64", "chown - 0 -1", "chown;amd64;-,99,-1", Deny}, 816 {"amd64", "setresuid -1 -1 -1", "setresuid;i386;-1,-1,-1", Allow}, 817 {"amd64", "setresuid -1 -1 -1", "setresuid;amd64;-1,-1,-1", Allow}, 818 {"amd64", "setresuid -1 -1 -1", "setresuid;i386;-1,99,-1", Deny}, 819 {"amd64", "setresuid -1 -1 -1", "setresuid;amd64;-1,99,-1", Deny}, 820 } { 821 // It is tricky to mock the architecture here because 822 // seccomp is always adding the native arch to the seccomp 823 // filter and it will silently discard arches that have 824 // an endian mismatch: 825 // https://github.com/seccomp/libseccomp/issues/86 826 // 827 // This means we can not just 828 // main.MockArchDpkgArchitecture(t.arch) 829 // here because on endian mismatch the arch will *not* be 830 // added 831 if arch.DpkgArchitecture() == t.arch { 832 s.runBpf(c, t.seccompWhitelist, t.bpfInput, t.expected) 833 } 834 } 835 }