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