github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 //go:build linux 6 7 package syscall_test 8 9 import ( 10 "bytes" 11 "errors" 12 "flag" 13 "fmt" 14 "internal/platform" 15 "internal/syscall/unix" 16 "internal/testenv" 17 "io" 18 "os" 19 "os/exec" 20 "os/user" 21 "path" 22 "path/filepath" 23 "runtime" 24 "strconv" 25 "strings" 26 "syscall" 27 "testing" 28 "time" 29 "unsafe" 30 ) 31 32 // whoamiNEWUSER returns a command that runs "whoami" with CLONE_NEWUSER, 33 // mapping uid and gid 0 to the actual uid and gid of the test. 34 func whoamiNEWUSER(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd { 35 t.Helper() 36 testenv.MustHaveExecPath(t, "whoami") 37 cmd := testenv.Command(t, "whoami") 38 cmd.SysProcAttr = &syscall.SysProcAttr{ 39 Cloneflags: syscall.CLONE_NEWUSER, 40 UidMappings: []syscall.SysProcIDMap{ 41 {ContainerID: 0, HostID: uid, Size: 1}, 42 }, 43 GidMappings: []syscall.SysProcIDMap{ 44 {ContainerID: 0, HostID: gid, Size: 1}, 45 }, 46 GidMappingsEnableSetgroups: setgroups, 47 } 48 return cmd 49 } 50 51 func TestCloneNEWUSERAndRemap(t *testing.T) { 52 for _, setgroups := range []bool{false, true} { 53 setgroups := setgroups 54 t.Run(fmt.Sprintf("setgroups=%v", setgroups), func(t *testing.T) { 55 uid := os.Getuid() 56 gid := os.Getgid() 57 58 cmd := whoamiNEWUSER(t, uid, gid, setgroups) 59 out, err := cmd.CombinedOutput() 60 t.Logf("%v: %v", cmd, err) 61 62 if uid != 0 && setgroups { 63 t.Logf("as non-root, expected permission error due to unprivileged gid_map") 64 if !os.IsPermission(err) { 65 if err == nil { 66 t.Skipf("unexpected success: probably old kernel without security fix?") 67 } 68 if testenv.SyscallIsNotSupported(err) { 69 t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported") 70 } 71 t.Fatalf("got non-permission error") // Already logged above. 72 } 73 return 74 } 75 76 if err != nil { 77 if testenv.SyscallIsNotSupported(err) { 78 // May be inside a container that disallows CLONE_NEWUSER. 79 t.Skipf("skipping: CLONE_NEWUSER appears to be unsupported") 80 } 81 t.Fatalf("unexpected command failure; output:\n%s", out) 82 } 83 84 sout := strings.TrimSpace(string(out)) 85 want := "root" 86 if sout != want { 87 t.Fatalf("whoami = %q; want %q", out, want) 88 } 89 }) 90 } 91 } 92 93 func TestEmptyCredGroupsDisableSetgroups(t *testing.T) { 94 cmd := whoamiNEWUSER(t, os.Getuid(), os.Getgid(), false) 95 cmd.SysProcAttr.Credential = &syscall.Credential{} 96 if err := cmd.Run(); err != nil { 97 if testenv.SyscallIsNotSupported(err) { 98 t.Skipf("skipping: %v: %v", cmd, err) 99 } 100 t.Fatal(err) 101 } 102 } 103 104 func TestUnshare(t *testing.T) { 105 path := "/proc/net/dev" 106 if _, err := os.Stat(path); err != nil { 107 if os.IsNotExist(err) { 108 t.Skip("kernel doesn't support proc filesystem") 109 } 110 if os.IsPermission(err) { 111 t.Skip("unable to test proc filesystem due to permissions") 112 } 113 t.Fatal(err) 114 } 115 116 b, err := os.ReadFile(path) 117 if err != nil { 118 t.Fatal(err) 119 } 120 orig := strings.TrimSpace(string(b)) 121 if strings.Contains(orig, "lo:") && strings.Count(orig, ":") == 1 { 122 // This test expects there to be at least 1 more network interface 123 // in addition to the local network interface, so that it can tell 124 // that unshare worked. 125 t.Skip("not enough network interfaces to test unshare with") 126 } 127 128 cmd := testenv.Command(t, "cat", path) 129 cmd.SysProcAttr = &syscall.SysProcAttr{ 130 Unshareflags: syscall.CLONE_NEWNET, 131 } 132 out, err := cmd.CombinedOutput() 133 if err != nil { 134 if testenv.SyscallIsNotSupported(err) { 135 // CLONE_NEWNET does not appear to be supported. 136 t.Skipf("skipping due to permission error: %v", err) 137 } 138 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 139 } 140 141 // Check there is only the local network interface. 142 sout := strings.TrimSpace(string(out)) 143 if !strings.Contains(sout, "lo:") { 144 t.Fatalf("Expected lo network interface to exist, got %s", sout) 145 } 146 147 origLines := strings.Split(orig, "\n") 148 lines := strings.Split(sout, "\n") 149 if len(lines) >= len(origLines) { 150 t.Logf("%s before unshare:\n%s", path, orig) 151 t.Logf("%s after unshare:\n%s", path, sout) 152 t.Fatalf("Got %d lines of output, want < %d", len(lines), len(origLines)) 153 } 154 } 155 156 func TestGroupCleanup(t *testing.T) { 157 testenv.MustHaveExecPath(t, "id") 158 cmd := testenv.Command(t, "id") 159 cmd.SysProcAttr = &syscall.SysProcAttr{ 160 Credential: &syscall.Credential{ 161 Uid: 0, 162 Gid: 0, 163 }, 164 } 165 out, err := cmd.CombinedOutput() 166 if err != nil { 167 if testenv.SyscallIsNotSupported(err) { 168 t.Skipf("skipping: %v: %v", cmd, err) 169 } 170 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 171 } 172 strOut := strings.TrimSpace(string(out)) 173 t.Logf("id: %s", strOut) 174 175 expected := "uid=0(root) gid=0(root)" 176 // Just check prefix because some distros reportedly output a 177 // context parameter; see https://golang.org/issue/16224. 178 // Alpine does not output groups; see https://golang.org/issue/19938. 179 if !strings.HasPrefix(strOut, expected) { 180 t.Errorf("expected prefix: %q", expected) 181 } 182 } 183 184 func TestGroupCleanupUserNamespace(t *testing.T) { 185 testenv.MustHaveExecPath(t, "id") 186 cmd := testenv.Command(t, "id") 187 uid, gid := os.Getuid(), os.Getgid() 188 cmd.SysProcAttr = &syscall.SysProcAttr{ 189 Cloneflags: syscall.CLONE_NEWUSER, 190 Credential: &syscall.Credential{ 191 Uid: uint32(uid), 192 Gid: uint32(gid), 193 }, 194 UidMappings: []syscall.SysProcIDMap{ 195 {ContainerID: 0, HostID: uid, Size: 1}, 196 }, 197 GidMappings: []syscall.SysProcIDMap{ 198 {ContainerID: 0, HostID: gid, Size: 1}, 199 }, 200 } 201 out, err := cmd.CombinedOutput() 202 if err != nil { 203 if testenv.SyscallIsNotSupported(err) { 204 t.Skipf("skipping: %v: %v", cmd, err) 205 } 206 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 207 } 208 strOut := strings.TrimSpace(string(out)) 209 t.Logf("id: %s", strOut) 210 211 // As in TestGroupCleanup, just check prefix. 212 // The actual groups and contexts seem to vary from one distro to the next. 213 expected := "uid=0(root) gid=0(root) groups=0(root)" 214 if !strings.HasPrefix(strOut, expected) { 215 t.Errorf("expected prefix: %q", expected) 216 } 217 } 218 219 // Test for https://go.dev/issue/19661: unshare fails because systemd 220 // has forced / to be shared 221 func TestUnshareMountNameSpace(t *testing.T) { 222 const mountNotSupported = "mount is not supported: " // Output prefix indicating a test skip. 223 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 224 dir := flag.Args()[0] 225 err := syscall.Mount("none", dir, "proc", 0, "") 226 if testenv.SyscallIsNotSupported(err) { 227 fmt.Print(mountNotSupported, err) 228 } else if err != nil { 229 fmt.Fprintf(os.Stderr, "unshare: mount %s: %v\n", dir, err) 230 os.Exit(2) 231 } 232 os.Exit(0) 233 } 234 235 testenv.MustHaveExec(t) 236 exe, err := os.Executable() 237 if err != nil { 238 t.Fatal(err) 239 } 240 241 d := t.TempDir() 242 t.Cleanup(func() { 243 // If the subprocess fails to unshare the parent directory, force-unmount it 244 // so that the test can clean it up. 245 if _, err := os.Stat(d); err == nil { 246 syscall.Unmount(d, syscall.MNT_FORCE) 247 } 248 }) 249 cmd := testenv.Command(t, exe, "-test.run=^TestUnshareMountNameSpace$", d) 250 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 251 cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS} 252 253 out, err := cmd.CombinedOutput() 254 if err != nil { 255 if testenv.SyscallIsNotSupported(err) { 256 t.Skipf("skipping: could not start process with CLONE_NEWNS: %v", err) 257 } 258 t.Fatalf("unshare failed: %v\n%s", err, out) 259 } else if len(out) != 0 { 260 if bytes.HasPrefix(out, []byte(mountNotSupported)) { 261 t.Skipf("skipping: helper process reported %s", out) 262 } 263 t.Fatalf("unexpected output from helper process: %s", out) 264 } 265 266 // How do we tell if the namespace was really unshared? It turns out 267 // to be simple: just try to remove the directory. If it's still mounted 268 // on the rm will fail with EBUSY. 269 if err := os.Remove(d); err != nil { 270 t.Errorf("rmdir failed on %v: %v", d, err) 271 } 272 } 273 274 // Test for Issue 20103: unshare fails when chroot is used 275 func TestUnshareMountNameSpaceChroot(t *testing.T) { 276 const mountNotSupported = "mount is not supported: " // Output prefix indicating a test skip. 277 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 278 dir := flag.Args()[0] 279 err := syscall.Mount("none", dir, "proc", 0, "") 280 if testenv.SyscallIsNotSupported(err) { 281 fmt.Print(mountNotSupported, err) 282 } else if err != nil { 283 fmt.Fprintf(os.Stderr, "unshare: mount %s: %v\n", dir, err) 284 os.Exit(2) 285 } 286 os.Exit(0) 287 } 288 289 d := t.TempDir() 290 291 // Since we are doing a chroot, we need the binary there, 292 // and it must be statically linked. 293 testenv.MustHaveGoBuild(t) 294 if platform.MustLinkExternal(runtime.GOOS, runtime.GOARCH, false) { 295 t.Skipf("skipping: can't build static binary because %s/%s requires external linking", runtime.GOOS, runtime.GOARCH) 296 } 297 x := filepath.Join(d, "syscall.test") 298 t.Cleanup(func() { 299 // If the subprocess fails to unshare the parent directory, force-unmount it 300 // so that the test can clean it up. 301 if _, err := os.Stat(d); err == nil { 302 syscall.Unmount(d, syscall.MNT_FORCE) 303 } 304 }) 305 306 cmd := testenv.Command(t, testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall") 307 cmd.Env = append(cmd.Environ(), "CGO_ENABLED=0") 308 if o, err := cmd.CombinedOutput(); err != nil { 309 t.Fatalf("%v: %v\n%s", cmd, err, o) 310 } 311 312 cmd = testenv.Command(t, "/syscall.test", "-test.run=^TestUnshareMountNameSpaceChroot$", "/") 313 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 314 cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS} 315 316 out, err := cmd.CombinedOutput() 317 if err != nil { 318 if testenv.SyscallIsNotSupported(err) { 319 t.Skipf("skipping: could not start process with CLONE_NEWNS and Chroot %q: %v", d, err) 320 } 321 t.Fatalf("unshare failed: %v\n%s", err, out) 322 } else if len(out) != 0 { 323 if bytes.HasPrefix(out, []byte(mountNotSupported)) { 324 t.Skipf("skipping: helper process reported %s", out) 325 } 326 t.Fatalf("unexpected output from helper process: %s", out) 327 } 328 329 // How do we tell if the namespace was really unshared? It turns out 330 // to be simple: just try to remove the executable. If it's still mounted 331 // on, the rm will fail. 332 if err := os.Remove(x); err != nil { 333 t.Errorf("rm failed on %v: %v", x, err) 334 } 335 if err := os.Remove(d); err != nil { 336 t.Errorf("rmdir failed on %v: %v", d, err) 337 } 338 } 339 340 // Test for Issue 29789: unshare fails when uid/gid mapping is specified 341 func TestUnshareUidGidMapping(t *testing.T) { 342 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 343 defer os.Exit(0) 344 if err := syscall.Chroot(os.TempDir()); err != nil { 345 fmt.Fprintln(os.Stderr, err) 346 os.Exit(2) 347 } 348 } 349 350 if os.Getuid() == 0 { 351 t.Skip("test exercises unprivileged user namespace, fails with privileges") 352 } 353 354 testenv.MustHaveExec(t) 355 exe, err := os.Executable() 356 if err != nil { 357 t.Fatal(err) 358 } 359 360 cmd := testenv.Command(t, exe, "-test.run=^TestUnshareUidGidMapping$") 361 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 362 cmd.SysProcAttr = &syscall.SysProcAttr{ 363 Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER, 364 GidMappingsEnableSetgroups: false, 365 UidMappings: []syscall.SysProcIDMap{ 366 { 367 ContainerID: 0, 368 HostID: syscall.Getuid(), 369 Size: 1, 370 }, 371 }, 372 GidMappings: []syscall.SysProcIDMap{ 373 { 374 ContainerID: 0, 375 HostID: syscall.Getgid(), 376 Size: 1, 377 }, 378 }, 379 } 380 out, err := cmd.CombinedOutput() 381 if err != nil { 382 if testenv.SyscallIsNotSupported(err) { 383 t.Skipf("skipping: could not start process with CLONE_NEWNS and CLONE_NEWUSER: %v", err) 384 } 385 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 386 } 387 } 388 389 func prepareCgroupFD(t *testing.T) (int, string) { 390 t.Helper() 391 392 const O_PATH = 0x200000 // Same for all architectures, but for some reason not defined in syscall for 386||amd64. 393 394 // Requires cgroup v2. 395 const prefix = "/sys/fs/cgroup" 396 selfCg, err := os.ReadFile("/proc/self/cgroup") 397 if err != nil { 398 if os.IsNotExist(err) || os.IsPermission(err) { 399 t.Skip(err) 400 } 401 t.Fatal(err) 402 } 403 404 // Expect a single line like this: 405 // 0::/user.slice/user-1000.slice/user@1000.service/app.slice/vte-spawn-891992a2-efbb-4f28-aedb-b24f9e706770.scope 406 // Otherwise it's either cgroup v1 or a hybrid hierarchy. 407 if bytes.Count(selfCg, []byte("\n")) > 1 { 408 t.Skip("cgroup v2 not available") 409 } 410 cg := bytes.TrimPrefix(selfCg, []byte("0::")) 411 if len(cg) == len(selfCg) { // No prefix found. 412 t.Skipf("cgroup v2 not available (/proc/self/cgroup contents: %q)", selfCg) 413 } 414 415 // Need an ability to create a sub-cgroup. 416 subCgroup, err := os.MkdirTemp(prefix+string(bytes.TrimSpace(cg)), "subcg-") 417 if err != nil { 418 // ErrPermission or EROFS (#57262) when running in an unprivileged container. 419 // ErrNotExist when cgroupfs is not mounted in chroot/schroot. 420 if os.IsNotExist(err) || testenv.SyscallIsNotSupported(err) { 421 t.Skipf("skipping: %v", err) 422 } 423 t.Fatal(err) 424 } 425 t.Cleanup(func() { syscall.Rmdir(subCgroup) }) 426 427 cgroupFD, err := syscall.Open(subCgroup, O_PATH, 0) 428 if err != nil { 429 t.Fatal(&os.PathError{Op: "open", Path: subCgroup, Err: err}) 430 } 431 t.Cleanup(func() { syscall.Close(cgroupFD) }) 432 433 return cgroupFD, "/" + path.Base(subCgroup) 434 } 435 436 func TestUseCgroupFD(t *testing.T) { 437 testenv.MustHaveExec(t) 438 439 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 440 // Read and print own cgroup path. 441 selfCg, err := os.ReadFile("/proc/self/cgroup") 442 if err != nil { 443 fmt.Fprintln(os.Stderr, err) 444 os.Exit(2) 445 } 446 fmt.Print(string(selfCg)) 447 os.Exit(0) 448 } 449 450 exe, err := os.Executable() 451 if err != nil { 452 t.Fatal(err) 453 } 454 455 fd, suffix := prepareCgroupFD(t) 456 457 cmd := testenv.Command(t, exe, "-test.run=^TestUseCgroupFD$") 458 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 459 cmd.SysProcAttr = &syscall.SysProcAttr{ 460 UseCgroupFD: true, 461 CgroupFD: fd, 462 } 463 out, err := cmd.CombinedOutput() 464 if err != nil { 465 if testenv.SyscallIsNotSupported(err) && !errors.Is(err, syscall.EINVAL) { 466 // Can be one of: 467 // - clone3 not supported (old kernel); 468 // - clone3 not allowed (by e.g. seccomp); 469 // - lack of CAP_SYS_ADMIN. 470 t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err) 471 } 472 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 473 } 474 // NB: this wouldn't work with cgroupns. 475 if !bytes.HasSuffix(bytes.TrimSpace(out), []byte(suffix)) { 476 t.Fatalf("got: %q, want: a line that ends with %q", out, suffix) 477 } 478 } 479 480 func TestCloneTimeNamespace(t *testing.T) { 481 testenv.MustHaveExec(t) 482 483 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 484 timens, err := os.Readlink("/proc/self/ns/time") 485 if err != nil { 486 fmt.Fprintln(os.Stderr, err) 487 os.Exit(2) 488 } 489 fmt.Print(string(timens)) 490 os.Exit(0) 491 } 492 493 exe, err := os.Executable() 494 if err != nil { 495 t.Fatal(err) 496 } 497 498 cmd := testenv.Command(t, exe, "-test.run=^TestCloneTimeNamespace$") 499 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 500 cmd.SysProcAttr = &syscall.SysProcAttr{ 501 Cloneflags: syscall.CLONE_NEWTIME, 502 } 503 out, err := cmd.CombinedOutput() 504 if err != nil { 505 if testenv.SyscallIsNotSupported(err) { 506 // CLONE_NEWTIME does not appear to be supported. 507 t.Skipf("skipping, CLONE_NEWTIME not supported: %v", err) 508 } 509 t.Fatalf("Cmd failed with err %v, output: %s", err, out) 510 } 511 512 // Inode number of the time namespaces should be different. 513 // Based on https://man7.org/linux/man-pages/man7/time_namespaces.7.html#EXAMPLES 514 timens, err := os.Readlink("/proc/self/ns/time") 515 if err != nil { 516 t.Fatal(err) 517 } 518 519 parentTimeNS := timens 520 childTimeNS := string(out) 521 if childTimeNS == parentTimeNS { 522 t.Fatalf("expected child time namespace to be different from parent time namespace: %s", parentTimeNS) 523 } 524 } 525 526 func testPidFD(t *testing.T, userns bool) error { 527 testenv.MustHaveExec(t) 528 529 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 530 // Child: wait for a signal. 531 time.Sleep(time.Hour) 532 } 533 534 exe, err := os.Executable() 535 if err != nil { 536 t.Fatal(err) 537 } 538 539 var pidfd int 540 cmd := testenv.Command(t, exe, "-test.run=^TestPidFD$") 541 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 542 cmd.SysProcAttr = &syscall.SysProcAttr{ 543 PidFD: &pidfd, 544 } 545 if userns { 546 cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER 547 } 548 if err := cmd.Start(); err != nil { 549 return err 550 } 551 defer func() { 552 cmd.Process.Kill() 553 cmd.Wait() 554 }() 555 t.Log("got pidfd:", pidfd) 556 // If pidfd is not supported by the kernel, -1 is returned. 557 if pidfd == -1 { 558 t.Skip("pidfd not supported") 559 } 560 defer syscall.Close(pidfd) 561 562 // Use pidfd to send a signal to the child. 563 sig := syscall.SIGINT 564 if err := unix.PidFDSendSignal(uintptr(pidfd), sig); err != nil { 565 if err != syscall.EINVAL && testenv.SyscallIsNotSupported(err) { 566 t.Skip("pidfd_send_signal syscall not supported:", err) 567 } 568 t.Fatal("pidfd_send_signal syscall failed:", err) 569 } 570 // Check if the child received our signal. 571 err = cmd.Wait() 572 if cmd.ProcessState == nil || cmd.ProcessState.Sys().(syscall.WaitStatus).Signal() != sig { 573 t.Fatal("unexpected child error:", err) 574 } 575 return nil 576 } 577 578 func TestPidFD(t *testing.T) { 579 if err := testPidFD(t, false); err != nil { 580 t.Fatal("can't start a process:", err) 581 } 582 } 583 584 func TestPidFDWithUserNS(t *testing.T) { 585 if err := testPidFD(t, true); err != nil { 586 if testenv.SyscallIsNotSupported(err) { 587 t.Skip("userns not supported:", err) 588 } 589 t.Fatal("can't start a process:", err) 590 } 591 } 592 593 func TestPidFDClone3(t *testing.T) { 594 *syscall.ForceClone3 = true 595 defer func() { *syscall.ForceClone3 = false }() 596 597 if err := testPidFD(t, false); err != nil { 598 if testenv.SyscallIsNotSupported(err) { 599 t.Skip("clone3 not supported:", err) 600 } 601 t.Fatal("can't start a process:", err) 602 } 603 } 604 605 type capHeader struct { 606 version uint32 607 pid int32 608 } 609 610 type capData struct { 611 effective uint32 612 permitted uint32 613 inheritable uint32 614 } 615 616 const CAP_SYS_TIME = 25 617 const CAP_SYSLOG = 34 618 619 type caps struct { 620 hdr capHeader 621 data [2]capData 622 } 623 624 func getCaps() (caps, error) { 625 var c caps 626 627 // Get capability version 628 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 { 629 return c, fmt.Errorf("SYS_CAPGET: %v", errno) 630 } 631 632 // Get current capabilities 633 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 { 634 return c, fmt.Errorf("SYS_CAPGET: %v", errno) 635 } 636 637 return c, nil 638 } 639 640 func TestAmbientCaps(t *testing.T) { 641 testAmbientCaps(t, false) 642 } 643 644 func TestAmbientCapsUserns(t *testing.T) { 645 testAmbientCaps(t, true) 646 } 647 648 func testAmbientCaps(t *testing.T, userns bool) { 649 if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" { 650 caps, err := getCaps() 651 if err != nil { 652 fmt.Fprintln(os.Stderr, err) 653 os.Exit(2) 654 } 655 if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 { 656 fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask") 657 os.Exit(2) 658 } 659 if caps.data[1].effective&(1<<uint(CAP_SYSLOG&31)) == 0 { 660 fmt.Fprintln(os.Stderr, "CAP_SYSLOG unexpectedly not in the effective capability mask") 661 os.Exit(2) 662 } 663 os.Exit(0) 664 } 665 666 // skip on android, due to lack of lookup support 667 if runtime.GOOS == "android" { 668 t.Skip("skipping test on android; see Issue 27327") 669 } 670 671 u, err := user.Lookup("nobody") 672 if err != nil { 673 t.Fatal(err) 674 } 675 uid, err := strconv.ParseInt(u.Uid, 0, 32) 676 if err != nil { 677 t.Fatal(err) 678 } 679 gid, err := strconv.ParseInt(u.Gid, 0, 32) 680 if err != nil { 681 t.Fatal(err) 682 } 683 684 // Copy the test binary to a temporary location which is readable by nobody. 685 f, err := os.CreateTemp("", "gotest") 686 if err != nil { 687 t.Fatal(err) 688 } 689 t.Cleanup(func() { 690 f.Close() 691 os.Remove(f.Name()) 692 }) 693 694 testenv.MustHaveExec(t) 695 exe, err := os.Executable() 696 if err != nil { 697 t.Fatal(err) 698 } 699 700 e, err := os.Open(exe) 701 if err != nil { 702 t.Fatal(err) 703 } 704 defer e.Close() 705 if _, err := io.Copy(f, e); err != nil { 706 t.Fatal(err) 707 } 708 if err := f.Chmod(0755); err != nil { 709 t.Fatal(err) 710 } 711 if err := f.Close(); err != nil { 712 t.Fatal(err) 713 } 714 715 cmd := testenv.Command(t, f.Name(), "-test.run=^"+t.Name()+"$") 716 cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1") 717 cmd.Stdout = os.Stdout 718 cmd.Stderr = os.Stderr 719 cmd.SysProcAttr = &syscall.SysProcAttr{ 720 Credential: &syscall.Credential{ 721 Uid: uint32(uid), 722 Gid: uint32(gid), 723 }, 724 AmbientCaps: []uintptr{CAP_SYS_TIME, CAP_SYSLOG}, 725 } 726 if userns { 727 cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER 728 const nobody = 65534 729 uid := os.Getuid() 730 gid := os.Getgid() 731 cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{ 732 ContainerID: int(nobody), 733 HostID: uid, 734 Size: int(1), 735 }} 736 cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{ 737 ContainerID: int(nobody), 738 HostID: gid, 739 Size: int(1), 740 }} 741 742 // Set credentials to run as user and group nobody. 743 cmd.SysProcAttr.Credential = &syscall.Credential{ 744 Uid: nobody, 745 Gid: nobody, 746 } 747 } 748 if err := cmd.Run(); err != nil { 749 if testenv.SyscallIsNotSupported(err) { 750 t.Skipf("skipping: %v: %v", cmd, err) 751 } 752 t.Fatal(err.Error()) 753 } 754 }