github.com/stingnevermore/go@v0.0.0-20180120041312-3810f5bfed72/src/syscall/exec_linux_test.go (about) 1 // Copyright 2015 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // +build linux 6 7 package syscall_test 8 9 import ( 10 "flag" 11 "fmt" 12 "internal/testenv" 13 "io" 14 "io/ioutil" 15 "os" 16 "os/exec" 17 "os/user" 18 "path/filepath" 19 "strconv" 20 "strings" 21 "syscall" 22 "testing" 23 "unsafe" 24 ) 25 26 func isDocker() bool { 27 _, err := os.Stat("/.dockerenv") 28 return err == nil 29 } 30 31 func isLXC() bool { 32 return os.Getenv("container") == "lxc" 33 } 34 35 func skipInContainer(t *testing.T) { 36 if isDocker() { 37 t.Skip("skip this test in Docker container") 38 } 39 if isLXC() { 40 t.Skip("skip this test in LXC container") 41 } 42 } 43 44 // Check if we are in a chroot by checking if the inode of / is 45 // different from 2 (there is no better test available to non-root on 46 // linux). 47 func isChrooted(t *testing.T) bool { 48 root, err := os.Stat("/") 49 if err != nil { 50 t.Fatalf("cannot stat /: %v", err) 51 } 52 return root.Sys().(*syscall.Stat_t).Ino != 2 53 } 54 55 func checkUserNS(t *testing.T) { 56 skipInContainer(t) 57 if _, err := os.Stat("/proc/self/ns/user"); err != nil { 58 if os.IsNotExist(err) { 59 t.Skip("kernel doesn't support user namespaces") 60 } 61 if os.IsPermission(err) { 62 t.Skip("unable to test user namespaces due to permissions") 63 } 64 t.Fatalf("Failed to stat /proc/self/ns/user: %v", err) 65 } 66 if isChrooted(t) { 67 // create_user_ns in the kernel (see 68 // https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/tree/kernel/user_namespace.c) 69 // forbids the creation of user namespaces when chrooted. 70 t.Skip("cannot create user namespaces when chrooted") 71 } 72 // On some systems, there is a sysctl setting. 73 if os.Getuid() != 0 { 74 data, errRead := ioutil.ReadFile("/proc/sys/kernel/unprivileged_userns_clone") 75 if errRead == nil && data[0] == '0' { 76 t.Skip("kernel prohibits user namespace in unprivileged process") 77 } 78 } 79 // On Centos 7 make sure they set the kernel parameter user_namespace=1 80 // See issue 16283 and 20796. 81 if _, err := os.Stat("/sys/module/user_namespace/parameters/enable"); err == nil { 82 buf, _ := ioutil.ReadFile("/sys/module/user_namespace/parameters/enabled") 83 if !strings.HasPrefix(string(buf), "Y") { 84 t.Skip("kernel doesn't support user namespaces") 85 } 86 } 87 // When running under the Go continuous build, skip tests for 88 // now when under Kubernetes. (where things are root but not quite) 89 // Both of these are our own environment variables. 90 // See Issue 12815. 91 if os.Getenv("GO_BUILDER_NAME") != "" && os.Getenv("IN_KUBERNETES") == "1" { 92 t.Skip("skipping test on Kubernetes-based builders; see Issue 12815") 93 } 94 } 95 96 func whoamiCmd(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd { 97 checkUserNS(t) 98 cmd := exec.Command("whoami") 99 cmd.SysProcAttr = &syscall.SysProcAttr{ 100 Cloneflags: syscall.CLONE_NEWUSER, 101 UidMappings: []syscall.SysProcIDMap{ 102 {ContainerID: 0, HostID: uid, Size: 1}, 103 }, 104 GidMappings: []syscall.SysProcIDMap{ 105 {ContainerID: 0, HostID: gid, Size: 1}, 106 }, 107 GidMappingsEnableSetgroups: setgroups, 108 } 109 return cmd 110 } 111 112 func testNEWUSERRemap(t *testing.T, uid, gid int, setgroups bool) { 113 cmd := whoamiCmd(t, uid, gid, setgroups) 114 out, err := cmd.CombinedOutput() 115 if err != nil { 116 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 117 } 118 sout := strings.TrimSpace(string(out)) 119 want := "root" 120 if sout != want { 121 t.Fatalf("whoami = %q; want %q", out, want) 122 } 123 } 124 125 func TestCloneNEWUSERAndRemapRootDisableSetgroups(t *testing.T) { 126 if os.Getuid() != 0 { 127 t.Skip("skipping root only test") 128 } 129 testNEWUSERRemap(t, 0, 0, false) 130 } 131 132 func TestCloneNEWUSERAndRemapRootEnableSetgroups(t *testing.T) { 133 if os.Getuid() != 0 { 134 t.Skip("skipping root only test") 135 } 136 testNEWUSERRemap(t, 0, 0, true) 137 } 138 139 func TestCloneNEWUSERAndRemapNoRootDisableSetgroups(t *testing.T) { 140 if os.Getuid() == 0 { 141 t.Skip("skipping unprivileged user only test") 142 } 143 testNEWUSERRemap(t, os.Getuid(), os.Getgid(), false) 144 } 145 146 func TestCloneNEWUSERAndRemapNoRootSetgroupsEnableSetgroups(t *testing.T) { 147 if os.Getuid() == 0 { 148 t.Skip("skipping unprivileged user only test") 149 } 150 cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), true) 151 err := cmd.Run() 152 if err == nil { 153 t.Skip("probably old kernel without security fix") 154 } 155 if !os.IsPermission(err) { 156 t.Fatalf("Unprivileged gid_map rewriting with GidMappingsEnableSetgroups must fail") 157 } 158 } 159 160 func TestEmptyCredGroupsDisableSetgroups(t *testing.T) { 161 cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), false) 162 cmd.SysProcAttr.Credential = &syscall.Credential{} 163 if err := cmd.Run(); err != nil { 164 t.Fatal(err) 165 } 166 } 167 168 func TestUnshare(t *testing.T) { 169 skipInContainer(t) 170 // Make sure we are running as root so we have permissions to use unshare 171 // and create a network namespace. 172 if os.Getuid() != 0 { 173 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace") 174 } 175 176 // When running under the Go continuous build, skip tests for 177 // now when under Kubernetes. (where things are root but not quite) 178 // Both of these are our own environment variables. 179 // See Issue 12815. 180 if os.Getenv("GO_BUILDER_NAME") != "" && os.Getenv("IN_KUBERNETES") == "1" { 181 t.Skip("skipping test on Kubernetes-based builders; see Issue 12815") 182 } 183 184 path := "/proc/net/dev" 185 if _, err := os.Stat(path); err != nil { 186 if os.IsNotExist(err) { 187 t.Skip("kernel doesn't support proc filesystem") 188 } 189 if os.IsPermission(err) { 190 t.Skip("unable to test proc filesystem due to permissions") 191 } 192 t.Fatal(err) 193 } 194 if _, err := os.Stat("/proc/self/ns/net"); err != nil { 195 if os.IsNotExist(err) { 196 t.Skip("kernel doesn't support net namespace") 197 } 198 t.Fatal(err) 199 } 200 201 orig, err := ioutil.ReadFile(path) 202 if err != nil { 203 t.Fatal(err) 204 } 205 origLines := strings.Split(strings.TrimSpace(string(orig)), "\n") 206 207 cmd := exec.Command("cat", path) 208 cmd.SysProcAttr = &syscall.SysProcAttr{ 209 Unshareflags: syscall.CLONE_NEWNET, 210 } 211 out, err := cmd.CombinedOutput() 212 if err != nil { 213 if strings.Contains(err.Error(), "operation not permitted") { 214 // Issue 17206: despite all the checks above, 215 // this still reportedly fails for some users. 216 // (older kernels?). Just skip. 217 t.Skip("skipping due to permission error") 218 } 219 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 220 } 221 222 // Check there is only the local network interface 223 sout := strings.TrimSpace(string(out)) 224 if !strings.Contains(sout, "lo:") { 225 t.Fatalf("Expected lo network interface to exist, got %s", sout) 226 } 227 228 lines := strings.Split(sout, "\n") 229 if len(lines) >= len(origLines) { 230 t.Fatalf("Got %d lines of output, want <%d", len(lines), len(origLines)) 231 } 232 } 233 234 func TestGroupCleanup(t *testing.T) { 235 if os.Getuid() != 0 { 236 t.Skip("we need root for credential") 237 } 238 cmd := exec.Command("id") 239 cmd.SysProcAttr = &syscall.SysProcAttr{ 240 Credential: &syscall.Credential{ 241 Uid: 0, 242 Gid: 0, 243 }, 244 } 245 out, err := cmd.CombinedOutput() 246 if err != nil { 247 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 248 } 249 strOut := strings.TrimSpace(string(out)) 250 expected := "uid=0(root) gid=0(root)" 251 // Just check prefix because some distros reportedly output a 252 // context parameter; see https://golang.org/issue/16224. 253 // Alpine does not output groups; see https://golang.org/issue/19938. 254 if !strings.HasPrefix(strOut, expected) { 255 t.Errorf("id command output: %q, expected prefix: %q", strOut, expected) 256 } 257 } 258 259 func TestGroupCleanupUserNamespace(t *testing.T) { 260 if os.Getuid() != 0 { 261 t.Skip("we need root for credential") 262 } 263 checkUserNS(t) 264 cmd := exec.Command("id") 265 uid, gid := os.Getuid(), os.Getgid() 266 cmd.SysProcAttr = &syscall.SysProcAttr{ 267 Cloneflags: syscall.CLONE_NEWUSER, 268 Credential: &syscall.Credential{ 269 Uid: uint32(uid), 270 Gid: uint32(gid), 271 }, 272 UidMappings: []syscall.SysProcIDMap{ 273 {ContainerID: 0, HostID: uid, Size: 1}, 274 }, 275 GidMappings: []syscall.SysProcIDMap{ 276 {ContainerID: 0, HostID: gid, Size: 1}, 277 }, 278 } 279 out, err := cmd.CombinedOutput() 280 if err != nil { 281 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 282 } 283 strOut := strings.TrimSpace(string(out)) 284 285 // Strings we've seen in the wild. 286 expected := []string{ 287 "uid=0(root) gid=0(root) groups=0(root)", 288 "uid=0(root) gid=0(root) groups=0(root),65534(nobody)", 289 "uid=0(root) gid=0(root) groups=0(root),65534(nogroup)", 290 "uid=0(root) gid=0(root) groups=0(root),65534", 291 "uid=0(root) gid=0(root) groups=0(root),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody),65534(nobody)", // Alpine; see https://golang.org/issue/19938 292 } 293 for _, e := range expected { 294 if strOut == e { 295 return 296 } 297 } 298 t.Errorf("id command output: %q, expected one of %q", strOut, expected) 299 } 300 301 // TestUnshareHelperProcess isn't a real test. It's used as a helper process 302 // for TestUnshareMountNameSpace. 303 func TestUnshareMountNameSpaceHelper(*testing.T) { 304 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 305 return 306 } 307 defer os.Exit(0) 308 if err := syscall.Mount("none", flag.Args()[0], "proc", 0, ""); err != nil { 309 fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %v", os.Args, err) 310 os.Exit(2) 311 } 312 } 313 314 // Test for Issue 38471: unshare fails because systemd has forced / to be shared 315 func TestUnshareMountNameSpace(t *testing.T) { 316 skipInContainer(t) 317 // Make sure we are running as root so we have permissions to use unshare 318 // and create a network namespace. 319 if os.Getuid() != 0 { 320 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace") 321 } 322 323 // When running under the Go continuous build, skip tests for 324 // now when under Kubernetes. (where things are root but not quite) 325 // Both of these are our own environment variables. 326 // See Issue 12815. 327 if os.Getenv("GO_BUILDER_NAME") != "" && os.Getenv("IN_KUBERNETES") == "1" { 328 t.Skip("skipping test on Kubernetes-based builders; see Issue 12815") 329 } 330 331 d, err := ioutil.TempDir("", "unshare") 332 if err != nil { 333 t.Fatalf("tempdir: %v", err) 334 } 335 336 cmd := exec.Command(os.Args[0], "-test.run=TestUnshareMountNameSpaceHelper", d) 337 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 338 cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS} 339 340 o, err := cmd.CombinedOutput() 341 if err != nil { 342 if strings.Contains(err.Error(), ": permission denied") { 343 t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err) 344 } 345 t.Fatalf("unshare failed: %s, %v", o, err) 346 } 347 348 // How do we tell if the namespace was really unshared? It turns out 349 // to be simple: just try to remove the directory. If it's still mounted 350 // on the rm will fail with EBUSY. Then we have some cleanup to do: 351 // we must unmount it, then try to remove it again. 352 353 if err := os.Remove(d); err != nil { 354 t.Errorf("rmdir failed on %v: %v", d, err) 355 if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil { 356 t.Errorf("Can't unmount %v: %v", d, err) 357 } 358 if err := os.Remove(d); err != nil { 359 t.Errorf("rmdir after unmount failed on %v: %v", d, err) 360 } 361 } 362 } 363 364 // Test for Issue 20103: unshare fails when chroot is used 365 func TestUnshareMountNameSpaceChroot(t *testing.T) { 366 skipInContainer(t) 367 // Make sure we are running as root so we have permissions to use unshare 368 // and create a network namespace. 369 if os.Getuid() != 0 { 370 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace") 371 } 372 373 // When running under the Go continuous build, skip tests for 374 // now when under Kubernetes. (where things are root but not quite) 375 // Both of these are our own environment variables. 376 // See Issue 12815. 377 if os.Getenv("GO_BUILDER_NAME") != "" && os.Getenv("IN_KUBERNETES") == "1" { 378 t.Skip("skipping test on Kubernetes-based builders; see Issue 12815") 379 } 380 381 d, err := ioutil.TempDir("", "unshare") 382 if err != nil { 383 t.Fatalf("tempdir: %v", err) 384 } 385 386 // Since we are doing a chroot, we need the binary there, 387 // and it must be statically linked. 388 x := filepath.Join(d, "syscall.test") 389 cmd := exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall") 390 cmd.Env = append(os.Environ(), "CGO_ENABLED=0") 391 if o, err := cmd.CombinedOutput(); err != nil { 392 t.Fatalf("Build of syscall in chroot failed, output %v, err %v", o, err) 393 } 394 395 cmd = exec.Command("/syscall.test", "-test.run=TestUnshareMountNameSpaceHelper", "/") 396 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 397 cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS} 398 399 o, err := cmd.CombinedOutput() 400 if err != nil { 401 if strings.Contains(err.Error(), ": permission denied") { 402 t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err) 403 } 404 t.Fatalf("unshare failed: %s, %v", o, err) 405 } 406 407 // How do we tell if the namespace was really unshared? It turns out 408 // to be simple: just try to remove the executable. If it's still mounted 409 // on, the rm will fail. Then we have some cleanup to do: 410 // we must force unmount it, then try to remove it again. 411 412 if err := os.Remove(x); err != nil { 413 t.Errorf("rm failed on %v: %v", x, err) 414 if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil { 415 t.Fatalf("Can't unmount %v: %v", d, err) 416 } 417 if err := os.Remove(x); err != nil { 418 t.Fatalf("rm failed on %v: %v", x, err) 419 } 420 } 421 422 if err := os.Remove(d); err != nil { 423 t.Errorf("rmdir failed on %v: %v", d, err) 424 } 425 } 426 427 type capHeader struct { 428 version uint32 429 pid int 430 } 431 432 type capData struct { 433 effective uint32 434 permitted uint32 435 inheritable uint32 436 } 437 438 const CAP_SYS_TIME = 25 439 440 type caps struct { 441 hdr capHeader 442 data [2]capData 443 } 444 445 func getCaps() (caps, error) { 446 var c caps 447 448 // Get capability version 449 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 { 450 return c, fmt.Errorf("SYS_CAPGET: %v", errno) 451 } 452 453 // Get current capabilities 454 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 { 455 return c, fmt.Errorf("SYS_CAPGET: %v", errno) 456 } 457 458 return c, nil 459 } 460 461 func mustSupportAmbientCaps(t *testing.T) { 462 var uname syscall.Utsname 463 if err := syscall.Uname(&uname); err != nil { 464 t.Fatalf("Uname: %v", err) 465 } 466 var buf [65]byte 467 for i, b := range uname.Release { 468 buf[i] = byte(b) 469 } 470 ver := string(buf[:]) 471 if i := strings.Index(ver, "\x00"); i != -1 { 472 ver = ver[:i] 473 } 474 if strings.HasPrefix(ver, "2.") || 475 strings.HasPrefix(ver, "3.") || 476 strings.HasPrefix(ver, "4.1.") || 477 strings.HasPrefix(ver, "4.2.") { 478 t.Skipf("kernel version %q predates required 4.3; skipping test", ver) 479 } 480 } 481 482 // TestAmbientCapsHelper isn't a real test. It's used as a helper process for 483 // TestAmbientCaps. 484 func TestAmbientCapsHelper(*testing.T) { 485 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { 486 return 487 } 488 defer os.Exit(0) 489 490 caps, err := getCaps() 491 if err != nil { 492 fmt.Fprintln(os.Stderr, err) 493 os.Exit(2) 494 } 495 if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 { 496 fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask") 497 os.Exit(2) 498 } 499 } 500 501 func TestAmbientCaps(t *testing.T) { 502 skipInContainer(t) 503 // Make sure we are running as root so we have permissions to use unshare 504 // and create a network namespace. 505 if os.Getuid() != 0 { 506 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace") 507 } 508 mustSupportAmbientCaps(t) 509 510 // When running under the Go continuous build, skip tests for 511 // now when under Kubernetes. (where things are root but not quite) 512 // Both of these are our own environment variables. 513 // See Issue 12815. 514 if os.Getenv("GO_BUILDER_NAME") != "" && os.Getenv("IN_KUBERNETES") == "1" { 515 t.Skip("skipping test on Kubernetes-based builders; see Issue 12815") 516 } 517 518 caps, err := getCaps() 519 if err != nil { 520 t.Fatal(err) 521 } 522 523 // Add CAP_SYS_TIME to the permitted and inheritable capability mask, 524 // otherwise we will not be able to add it to the ambient capability mask. 525 caps.data[0].permitted |= 1 << uint(CAP_SYS_TIME) 526 caps.data[0].inheritable |= 1 << uint(CAP_SYS_TIME) 527 528 if _, _, errno := syscall.Syscall(syscall.SYS_CAPSET, uintptr(unsafe.Pointer(&caps.hdr)), uintptr(unsafe.Pointer(&caps.data[0])), 0); errno != 0 { 529 t.Fatalf("SYS_CAPSET: %v", errno) 530 } 531 532 u, err := user.Lookup("nobody") 533 if err != nil { 534 t.Fatal(err) 535 } 536 uid, err := strconv.ParseInt(u.Uid, 0, 32) 537 if err != nil { 538 t.Fatal(err) 539 } 540 gid, err := strconv.ParseInt(u.Gid, 0, 32) 541 if err != nil { 542 t.Fatal(err) 543 } 544 545 // Copy the test binary to a temporary location which is readable by nobody. 546 f, err := ioutil.TempFile("", "gotest") 547 if err != nil { 548 t.Fatal(err) 549 } 550 defer os.Remove(f.Name()) 551 defer f.Close() 552 e, err := os.Open(os.Args[0]) 553 if err != nil { 554 t.Fatal(err) 555 } 556 defer e.Close() 557 if _, err := io.Copy(f, e); err != nil { 558 t.Fatal(err) 559 } 560 if err := f.Chmod(0755); err != nil { 561 t.Fatal(err) 562 } 563 if err := f.Close(); err != nil { 564 t.Fatal(err) 565 } 566 567 cmd := exec.Command(f.Name(), "-test.run=TestAmbientCapsHelper") 568 cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"} 569 cmd.Stdout = os.Stdout 570 cmd.Stderr = os.Stderr 571 cmd.SysProcAttr = &syscall.SysProcAttr{ 572 Credential: &syscall.Credential{ 573 Uid: uint32(uid), 574 Gid: uint32(gid), 575 }, 576 AmbientCaps: []uintptr{CAP_SYS_TIME}, 577 } 578 if err := cmd.Run(); err != nil { 579 t.Fatal(err.Error()) 580 } 581 }