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
7package syscall_test
8
9import (
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.
34func 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
51func 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
93func 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
104func 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
156func 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
184func 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
221func 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
275func 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
341func 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
389func 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
436func 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
480func 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
526func 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
578func TestPidFD(t *testing.T) {
579	if err := testPidFD(t, false); err != nil {
580		t.Fatal("can't start a process:", err)
581	}
582}
583
584func 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
593func 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
605type capHeader struct {
606	version uint32
607	pid     int32
608}
609
610type capData struct {
611	effective   uint32
612	permitted   uint32
613	inheritable uint32
614}
615
616const CAP_SYS_TIME = 25
617const CAP_SYSLOG = 34
618
619type caps struct {
620	hdr  capHeader
621	data [2]capData
622}
623
624func 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
640func TestAmbientCaps(t *testing.T) {
641	testAmbientCaps(t, false)
642}
643
644func TestAmbientCapsUserns(t *testing.T) {
645	b, err := os.ReadFile("/proc/sys/kernel/apparmor_restrict_unprivileged_userns")
646	if err == nil && strings.TrimSpace(string(b)) == "1" {
647		t.Skip("AppArmor restriction for unprivileged user namespaces is enabled")
648	}
649	testAmbientCaps(t, true)
650}
651
652func testAmbientCaps(t *testing.T, userns bool) {
653	if os.Getenv("GO_WANT_HELPER_PROCESS") == "1" {
654		caps, err := getCaps()
655		if err != nil {
656			fmt.Fprintln(os.Stderr, err)
657			os.Exit(2)
658		}
659		if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 {
660			fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask")
661			os.Exit(2)
662		}
663		if caps.data[1].effective&(1<<uint(CAP_SYSLOG&31)) == 0 {
664			fmt.Fprintln(os.Stderr, "CAP_SYSLOG unexpectedly not in the effective capability mask")
665			os.Exit(2)
666		}
667		os.Exit(0)
668	}
669
670	// skip on android, due to lack of lookup support
671	if runtime.GOOS == "android" {
672		t.Skip("skipping test on android; see Issue 27327")
673	}
674
675	u, err := user.Lookup("nobody")
676	if err != nil {
677		t.Fatal(err)
678	}
679	uid, err := strconv.ParseInt(u.Uid, 0, 32)
680	if err != nil {
681		t.Fatal(err)
682	}
683	gid, err := strconv.ParseInt(u.Gid, 0, 32)
684	if err != nil {
685		t.Fatal(err)
686	}
687
688	// Copy the test binary to a temporary location which is readable by nobody.
689	f, err := os.CreateTemp("", "gotest")
690	if err != nil {
691		t.Fatal(err)
692	}
693	t.Cleanup(func() {
694		f.Close()
695		os.Remove(f.Name())
696	})
697
698	testenv.MustHaveExec(t)
699	exe, err := os.Executable()
700	if err != nil {
701		t.Fatal(err)
702	}
703
704	e, err := os.Open(exe)
705	if err != nil {
706		t.Fatal(err)
707	}
708	defer e.Close()
709	if _, err := io.Copy(f, e); err != nil {
710		t.Fatal(err)
711	}
712	if err := f.Chmod(0755); err != nil {
713		t.Fatal(err)
714	}
715	if err := f.Close(); err != nil {
716		t.Fatal(err)
717	}
718
719	cmd := testenv.Command(t, f.Name(), "-test.run=^"+t.Name()+"$")
720	cmd.Env = append(cmd.Environ(), "GO_WANT_HELPER_PROCESS=1")
721	cmd.Stdout = os.Stdout
722	cmd.Stderr = os.Stderr
723	cmd.SysProcAttr = &syscall.SysProcAttr{
724		Credential: &syscall.Credential{
725			Uid: uint32(uid),
726			Gid: uint32(gid),
727		},
728		AmbientCaps: []uintptr{CAP_SYS_TIME, CAP_SYSLOG},
729	}
730	if userns {
731		cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER
732		const nobody = 65534
733		uid := os.Getuid()
734		gid := os.Getgid()
735		cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{
736			ContainerID: int(nobody),
737			HostID:      uid,
738			Size:        int(1),
739		}}
740		cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{
741			ContainerID: int(nobody),
742			HostID:      gid,
743			Size:        int(1),
744		}}
745
746		// Set credentials to run as user and group nobody.
747		cmd.SysProcAttr.Credential = &syscall.Credential{
748			Uid: nobody,
749			Gid: nobody,
750		}
751	}
752	if err := cmd.Run(); err != nil {
753		if testenv.SyscallIsNotSupported(err) {
754			t.Skipf("skipping: %v: %v", cmd, err)
755		}
756		t.Fatal(err.Error())
757	}
758}
759