Source file
src/syscall/exec_linux_test.go
1
2
3
4
5
6
7 package syscall_test
8
9 import (
10 "bytes"
11 "errors"
12 "flag"
13 "fmt"
14 "internal/testenv"
15 "io"
16 "os"
17 "os/exec"
18 "os/user"
19 "path"
20 "path/filepath"
21 "runtime"
22 "strconv"
23 "strings"
24 "syscall"
25 "testing"
26 "unsafe"
27 )
28
29 func isDocker() bool {
30 _, err := os.Stat("/.dockerenv")
31 return err == nil
32 }
33
34 func isLXC() bool {
35 return os.Getenv("container") == "lxc"
36 }
37
38 func skipInContainer(t *testing.T) {
39
40
41
42
43
44
45
46
47 if isDocker() {
48 t.Skip("skip this test in Docker container")
49 }
50 if isLXC() {
51 t.Skip("skip this test in LXC container")
52 }
53 }
54
55 func skipNoUserNamespaces(t *testing.T) {
56 if _, err := os.Stat("/proc/self/ns/user"); err != nil {
57 if os.IsNotExist(err) {
58 t.Skip("kernel doesn't support user namespaces")
59 }
60 if os.IsPermission(err) {
61 t.Skip("unable to test user namespaces due to permissions")
62 }
63 t.Fatalf("Failed to stat /proc/self/ns/user: %v", err)
64 }
65 }
66
67 func skipUnprivilegedUserClone(t *testing.T) {
68
69
70 data, errRead := os.ReadFile("/proc/sys/kernel/unprivileged_userns_clone")
71 if os.IsNotExist(errRead) {
72
73 return
74 }
75 if errRead != nil || len(data) < 1 || data[0] == '0' {
76 t.Skip("kernel prohibits user namespace in unprivileged process")
77 }
78 }
79
80
81
82
83 func isChrooted(t *testing.T) bool {
84 root, err := os.Stat("/")
85 if err != nil {
86 t.Fatalf("cannot stat /: %v", err)
87 }
88 return root.Sys().(*syscall.Stat_t).Ino != 2
89 }
90
91 func checkUserNS(t *testing.T) {
92 skipInContainer(t)
93 skipNoUserNamespaces(t)
94 if isChrooted(t) {
95
96
97
98 t.Skip("cannot create user namespaces when chrooted")
99 }
100
101 if os.Getuid() != 0 {
102 skipUnprivilegedUserClone(t)
103 }
104
105
106 if _, err := os.Stat("/sys/module/user_namespace/parameters/enable"); err == nil {
107 buf, _ := os.ReadFile("/sys/module/user_namespace/parameters/enabled")
108 if !strings.HasPrefix(string(buf), "Y") {
109 t.Skip("kernel doesn't support user namespaces")
110 }
111 }
112
113
114 if _, err := os.Stat("/proc/sys/user/max_user_namespaces"); err == nil {
115 buf, errRead := os.ReadFile("/proc/sys/user/max_user_namespaces")
116 if errRead == nil && buf[0] == '0' {
117 t.Skip("kernel doesn't support user namespaces")
118 }
119 }
120 }
121
122 func whoamiCmd(t *testing.T, uid, gid int, setgroups bool) *exec.Cmd {
123 checkUserNS(t)
124 cmd := exec.Command("whoami")
125 cmd.SysProcAttr = &syscall.SysProcAttr{
126 Cloneflags: syscall.CLONE_NEWUSER,
127 UidMappings: []syscall.SysProcIDMap{
128 {ContainerID: 0, HostID: uid, Size: 1},
129 },
130 GidMappings: []syscall.SysProcIDMap{
131 {ContainerID: 0, HostID: gid, Size: 1},
132 },
133 GidMappingsEnableSetgroups: setgroups,
134 }
135 return cmd
136 }
137
138 func testNEWUSERRemap(t *testing.T, uid, gid int, setgroups bool) {
139 cmd := whoamiCmd(t, uid, gid, setgroups)
140 out, err := cmd.CombinedOutput()
141 if err != nil {
142 t.Fatalf("Cmd failed with err %v, output: %s", err, out)
143 }
144 sout := strings.TrimSpace(string(out))
145 want := "root"
146 if sout != want {
147 t.Fatalf("whoami = %q; want %q", out, want)
148 }
149 }
150
151 func TestCloneNEWUSERAndRemapRootDisableSetgroups(t *testing.T) {
152 if os.Getuid() != 0 {
153 t.Skip("skipping root only test")
154 }
155 testNEWUSERRemap(t, 0, 0, false)
156 }
157
158 func TestCloneNEWUSERAndRemapRootEnableSetgroups(t *testing.T) {
159 if os.Getuid() != 0 {
160 t.Skip("skipping root only test")
161 }
162 testNEWUSERRemap(t, 0, 0, true)
163 }
164
165 func TestCloneNEWUSERAndRemapNoRootDisableSetgroups(t *testing.T) {
166 if os.Getuid() == 0 {
167 t.Skip("skipping unprivileged user only test")
168 }
169 testNEWUSERRemap(t, os.Getuid(), os.Getgid(), false)
170 }
171
172 func TestCloneNEWUSERAndRemapNoRootSetgroupsEnableSetgroups(t *testing.T) {
173 if os.Getuid() == 0 {
174 t.Skip("skipping unprivileged user only test")
175 }
176 cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), true)
177 err := cmd.Run()
178 if err == nil {
179 t.Skip("probably old kernel without security fix")
180 }
181 if !os.IsPermission(err) {
182 t.Fatalf("Unprivileged gid_map rewriting with GidMappingsEnableSetgroups must fail")
183 }
184 }
185
186 func TestEmptyCredGroupsDisableSetgroups(t *testing.T) {
187 cmd := whoamiCmd(t, os.Getuid(), os.Getgid(), false)
188 cmd.SysProcAttr.Credential = &syscall.Credential{}
189 if err := cmd.Run(); err != nil {
190 t.Fatal(err)
191 }
192 }
193
194 func TestUnshare(t *testing.T) {
195 skipInContainer(t)
196
197
198 if os.Getuid() != 0 {
199 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
200 }
201
202 path := "/proc/net/dev"
203 if _, err := os.Stat(path); err != nil {
204 if os.IsNotExist(err) {
205 t.Skip("kernel doesn't support proc filesystem")
206 }
207 if os.IsPermission(err) {
208 t.Skip("unable to test proc filesystem due to permissions")
209 }
210 t.Fatal(err)
211 }
212 if _, err := os.Stat("/proc/self/ns/net"); err != nil {
213 if os.IsNotExist(err) {
214 t.Skip("kernel doesn't support net namespace")
215 }
216 t.Fatal(err)
217 }
218
219 orig, err := os.ReadFile(path)
220 if err != nil {
221 t.Fatal(err)
222 }
223 origLines := strings.Split(strings.TrimSpace(string(orig)), "\n")
224
225 cmd := exec.Command("cat", path)
226 cmd.SysProcAttr = &syscall.SysProcAttr{
227 Unshareflags: syscall.CLONE_NEWNET,
228 }
229 out, err := cmd.CombinedOutput()
230 if err != nil {
231 if strings.Contains(err.Error(), "operation not permitted") {
232
233
234
235 t.Skip("skipping due to permission error")
236 }
237 t.Fatalf("Cmd failed with err %v, output: %s", err, out)
238 }
239
240
241 sout := strings.TrimSpace(string(out))
242 if !strings.Contains(sout, "lo:") {
243 t.Fatalf("Expected lo network interface to exist, got %s", sout)
244 }
245
246 lines := strings.Split(sout, "\n")
247 if len(lines) >= len(origLines) {
248 t.Fatalf("Got %d lines of output, want <%d", len(lines), len(origLines))
249 }
250 }
251
252 func TestGroupCleanup(t *testing.T) {
253 if os.Getuid() != 0 {
254 t.Skip("we need root for credential")
255 }
256 cmd := exec.Command("id")
257 cmd.SysProcAttr = &syscall.SysProcAttr{
258 Credential: &syscall.Credential{
259 Uid: 0,
260 Gid: 0,
261 },
262 }
263 out, err := cmd.CombinedOutput()
264 if err != nil {
265 t.Fatalf("Cmd failed with err %v, output: %s", err, out)
266 }
267 strOut := strings.TrimSpace(string(out))
268 t.Logf("id: %s", strOut)
269
270 expected := "uid=0(root) gid=0(root)"
271
272
273
274 if !strings.HasPrefix(strOut, expected) {
275 t.Errorf("expected prefix: %q", expected)
276 }
277 }
278
279 func TestGroupCleanupUserNamespace(t *testing.T) {
280 if os.Getuid() != 0 {
281 t.Skip("we need root for credential")
282 }
283 checkUserNS(t)
284 cmd := exec.Command("id")
285 uid, gid := os.Getuid(), os.Getgid()
286 cmd.SysProcAttr = &syscall.SysProcAttr{
287 Cloneflags: syscall.CLONE_NEWUSER,
288 Credential: &syscall.Credential{
289 Uid: uint32(uid),
290 Gid: uint32(gid),
291 },
292 UidMappings: []syscall.SysProcIDMap{
293 {ContainerID: 0, HostID: uid, Size: 1},
294 },
295 GidMappings: []syscall.SysProcIDMap{
296 {ContainerID: 0, HostID: gid, Size: 1},
297 },
298 }
299 out, err := cmd.CombinedOutput()
300 if err != nil {
301 t.Fatalf("Cmd failed with err %v, output: %s", err, out)
302 }
303 strOut := strings.TrimSpace(string(out))
304 t.Logf("id: %s", strOut)
305
306
307
308 expected := "uid=0(root) gid=0(root) groups=0(root)"
309 if !strings.HasPrefix(strOut, expected) {
310 t.Errorf("expected prefix: %q", expected)
311 }
312 }
313
314
315
316 func TestUnshareMountNameSpaceHelper(*testing.T) {
317 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
318 return
319 }
320 defer os.Exit(0)
321 if err := syscall.Mount("none", flag.Args()[0], "proc", 0, ""); err != nil {
322 fmt.Fprintf(os.Stderr, "unshare: mount %v failed: %v", os.Args, err)
323 os.Exit(2)
324 }
325 }
326
327
328 func TestUnshareMountNameSpace(t *testing.T) {
329 skipInContainer(t)
330
331
332 if os.Getuid() != 0 {
333 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
334 }
335
336 d, err := os.MkdirTemp("", "unshare")
337 if err != nil {
338 t.Fatalf("tempdir: %v", err)
339 }
340
341 cmd := exec.Command(os.Args[0], "-test.run=TestUnshareMountNameSpaceHelper", d)
342 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
343 cmd.SysProcAttr = &syscall.SysProcAttr{Unshareflags: syscall.CLONE_NEWNS}
344
345 o, err := cmd.CombinedOutput()
346 if err != nil {
347 if strings.Contains(err.Error(), ": permission denied") {
348 t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err)
349 }
350 t.Fatalf("unshare failed: %s, %v", o, err)
351 }
352
353
354
355
356
357
358 if err := os.Remove(d); err != nil {
359 t.Errorf("rmdir failed on %v: %v", d, err)
360 if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil {
361 t.Errorf("Can't unmount %v: %v", d, err)
362 }
363 if err := os.Remove(d); err != nil {
364 t.Errorf("rmdir after unmount failed on %v: %v", d, err)
365 }
366 }
367 }
368
369
370 func TestUnshareMountNameSpaceChroot(t *testing.T) {
371 skipInContainer(t)
372
373
374 if os.Getuid() != 0 {
375 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
376 }
377
378 d, err := os.MkdirTemp("", "unshare")
379 if err != nil {
380 t.Fatalf("tempdir: %v", err)
381 }
382
383
384
385 x := filepath.Join(d, "syscall.test")
386 cmd := exec.Command(testenv.GoToolPath(t), "test", "-c", "-o", x, "syscall")
387 cmd.Env = append(os.Environ(), "CGO_ENABLED=0")
388 if o, err := cmd.CombinedOutput(); err != nil {
389 t.Fatalf("Build of syscall in chroot failed, output %v, err %v", o, err)
390 }
391
392 cmd = exec.Command("/syscall.test", "-test.run=TestUnshareMountNameSpaceHelper", "/")
393 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
394 cmd.SysProcAttr = &syscall.SysProcAttr{Chroot: d, Unshareflags: syscall.CLONE_NEWNS}
395
396 o, err := cmd.CombinedOutput()
397 if err != nil {
398 if strings.Contains(err.Error(), ": permission denied") {
399 t.Skipf("Skipping test (golang.org/issue/19698); unshare failed due to permissions: %s, %v", o, err)
400 }
401 t.Fatalf("unshare failed: %s, %v", o, err)
402 }
403
404
405
406
407
408
409 if err := os.Remove(x); err != nil {
410 t.Errorf("rm failed on %v: %v", x, err)
411 if err := syscall.Unmount(d, syscall.MNT_FORCE); err != nil {
412 t.Fatalf("Can't unmount %v: %v", d, err)
413 }
414 if err := os.Remove(x); err != nil {
415 t.Fatalf("rm failed on %v: %v", x, err)
416 }
417 }
418
419 if err := os.Remove(d); err != nil {
420 t.Errorf("rmdir failed on %v: %v", d, err)
421 }
422 }
423
424 func TestUnshareUidGidMappingHelper(*testing.T) {
425 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
426 return
427 }
428 defer os.Exit(0)
429 if err := syscall.Chroot(os.TempDir()); err != nil {
430 fmt.Fprintln(os.Stderr, err)
431 os.Exit(2)
432 }
433 }
434
435
436 func TestUnshareUidGidMapping(t *testing.T) {
437 if os.Getuid() == 0 {
438 t.Skip("test exercises unprivileged user namespace, fails with privileges")
439 }
440 checkUserNS(t)
441 cmd := exec.Command(os.Args[0], "-test.run=TestUnshareUidGidMappingHelper")
442 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
443 cmd.SysProcAttr = &syscall.SysProcAttr{
444 Unshareflags: syscall.CLONE_NEWNS | syscall.CLONE_NEWUSER,
445 GidMappingsEnableSetgroups: false,
446 UidMappings: []syscall.SysProcIDMap{
447 {
448 ContainerID: 0,
449 HostID: syscall.Getuid(),
450 Size: 1,
451 },
452 },
453 GidMappings: []syscall.SysProcIDMap{
454 {
455 ContainerID: 0,
456 HostID: syscall.Getgid(),
457 Size: 1,
458 },
459 },
460 }
461 out, err := cmd.CombinedOutput()
462 if err != nil {
463 t.Fatalf("Cmd failed with err %v, output: %s", err, out)
464 }
465 }
466
467 func prepareCgroupFD(t *testing.T) (int, string) {
468 t.Helper()
469
470 const O_PATH = 0x200000
471
472
473 const prefix = "/sys/fs/cgroup"
474 selfCg, err := os.ReadFile("/proc/self/cgroup")
475 if err != nil {
476 if os.IsNotExist(err) || os.IsPermission(err) {
477 t.Skip(err)
478 }
479 t.Fatal(err)
480 }
481
482
483
484
485 if bytes.Count(selfCg, []byte("\n")) > 1 {
486 t.Skip("cgroup v2 not available")
487 }
488 cg := bytes.TrimPrefix(selfCg, []byte("0::"))
489 if len(cg) == len(selfCg) {
490 t.Skipf("cgroup v2 not available (/proc/self/cgroup contents: %q)", selfCg)
491 }
492
493
494 _, err = syscall.ForkExec("non-existent binary", nil, &syscall.ProcAttr{
495 Sys: &syscall.SysProcAttr{
496 UseCgroupFD: true,
497 CgroupFD: -1,
498 },
499 })
500
501 if err == syscall.ENOSYS || err == syscall.EPERM {
502 t.Skipf("clone3 with CLONE_INTO_CGROUP not available: %v", err)
503 }
504
505
506 subCgroup, err := os.MkdirTemp(prefix+string(bytes.TrimSpace(cg)), "subcg-")
507 if err != nil {
508
509
510 if os.IsNotExist(err) || os.IsPermission(err) || errors.Is(err, syscall.EROFS) {
511 t.Skip(err)
512 }
513 t.Fatal(err)
514 }
515 t.Cleanup(func() { syscall.Rmdir(subCgroup) })
516
517 cgroupFD, err := syscall.Open(subCgroup, O_PATH, 0)
518 if err != nil {
519 t.Fatal(&os.PathError{Op: "open", Path: subCgroup, Err: err})
520 }
521 t.Cleanup(func() { syscall.Close(cgroupFD) })
522
523 return cgroupFD, "/" + path.Base(subCgroup)
524 }
525
526 func TestUseCgroupFD(t *testing.T) {
527 fd, suffix := prepareCgroupFD(t)
528
529 cmd := exec.Command(os.Args[0], "-test.run=TestUseCgroupFDHelper")
530 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
531 cmd.SysProcAttr = &syscall.SysProcAttr{
532 UseCgroupFD: true,
533 CgroupFD: fd,
534 }
535 out, err := cmd.CombinedOutput()
536 if err != nil {
537 t.Fatalf("Cmd failed with err %v, output: %s", err, out)
538 }
539
540 if !bytes.HasSuffix(bytes.TrimSpace(out), []byte(suffix)) {
541 t.Fatalf("got: %q, want: a line that ends with %q", out, suffix)
542 }
543 }
544
545 func TestUseCgroupFDHelper(*testing.T) {
546 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
547 return
548 }
549 defer os.Exit(0)
550
551 selfCg, err := os.ReadFile("/proc/self/cgroup")
552 if err != nil {
553 fmt.Fprintln(os.Stderr, err)
554 os.Exit(2)
555 }
556 fmt.Print(string(selfCg))
557 }
558
559 type capHeader struct {
560 version uint32
561 pid int32
562 }
563
564 type capData struct {
565 effective uint32
566 permitted uint32
567 inheritable uint32
568 }
569
570 const CAP_SYS_TIME = 25
571 const CAP_SYSLOG = 34
572
573 type caps struct {
574 hdr capHeader
575 data [2]capData
576 }
577
578 func getCaps() (caps, error) {
579 var c caps
580
581
582 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(nil)), 0); errno != 0 {
583 return c, fmt.Errorf("SYS_CAPGET: %v", errno)
584 }
585
586
587 if _, _, errno := syscall.Syscall(syscall.SYS_CAPGET, uintptr(unsafe.Pointer(&c.hdr)), uintptr(unsafe.Pointer(&c.data[0])), 0); errno != 0 {
588 return c, fmt.Errorf("SYS_CAPGET: %v", errno)
589 }
590
591 return c, nil
592 }
593
594 func mustSupportAmbientCaps(t *testing.T) {
595 var uname syscall.Utsname
596 if err := syscall.Uname(&uname); err != nil {
597 t.Fatalf("Uname: %v", err)
598 }
599 var buf [65]byte
600 for i, b := range uname.Release {
601 buf[i] = byte(b)
602 }
603 ver := string(buf[:])
604 ver, _, _ = strings.Cut(ver, "\x00")
605 if strings.HasPrefix(ver, "2.") ||
606 strings.HasPrefix(ver, "3.") ||
607 strings.HasPrefix(ver, "4.1.") ||
608 strings.HasPrefix(ver, "4.2.") {
609 t.Skipf("kernel version %q predates required 4.3; skipping test", ver)
610 }
611 }
612
613
614
615 func TestAmbientCapsHelper(*testing.T) {
616 if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
617 return
618 }
619 defer os.Exit(0)
620
621 caps, err := getCaps()
622 if err != nil {
623 fmt.Fprintln(os.Stderr, err)
624 os.Exit(2)
625 }
626 if caps.data[0].effective&(1<<uint(CAP_SYS_TIME)) == 0 {
627 fmt.Fprintln(os.Stderr, "CAP_SYS_TIME unexpectedly not in the effective capability mask")
628 os.Exit(2)
629 }
630 if caps.data[1].effective&(1<<uint(CAP_SYSLOG&31)) == 0 {
631 fmt.Fprintln(os.Stderr, "CAP_SYSLOG unexpectedly not in the effective capability mask")
632 os.Exit(2)
633 }
634 }
635
636 func TestAmbientCaps(t *testing.T) {
637
638
639 if os.Getuid() != 0 {
640 t.Skip("kernel prohibits unshare in unprivileged process, unless using user namespace")
641 }
642
643 testAmbientCaps(t, false)
644 }
645
646 func TestAmbientCapsUserns(t *testing.T) {
647 checkUserNS(t)
648 testAmbientCaps(t, true)
649 }
650
651 func testAmbientCaps(t *testing.T, userns bool) {
652 skipInContainer(t)
653 mustSupportAmbientCaps(t)
654
655 skipUnprivilegedUserClone(t)
656
657
658 if runtime.GOOS == "android" {
659 t.Skip("skipping test on android; see Issue 27327")
660 }
661
662 u, err := user.Lookup("nobody")
663 if err != nil {
664 t.Fatal(err)
665 }
666 uid, err := strconv.ParseInt(u.Uid, 0, 32)
667 if err != nil {
668 t.Fatal(err)
669 }
670 gid, err := strconv.ParseInt(u.Gid, 0, 32)
671 if err != nil {
672 t.Fatal(err)
673 }
674
675
676 f, err := os.CreateTemp("", "gotest")
677 if err != nil {
678 t.Fatal(err)
679 }
680 defer os.Remove(f.Name())
681 defer f.Close()
682 e, err := os.Open(os.Args[0])
683 if err != nil {
684 t.Fatal(err)
685 }
686 defer e.Close()
687 if _, err := io.Copy(f, e); err != nil {
688 t.Fatal(err)
689 }
690 if err := f.Chmod(0755); err != nil {
691 t.Fatal(err)
692 }
693 if err := f.Close(); err != nil {
694 t.Fatal(err)
695 }
696
697 cmd := exec.Command(f.Name(), "-test.run=TestAmbientCapsHelper")
698 cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1")
699 cmd.Stdout = os.Stdout
700 cmd.Stderr = os.Stderr
701 cmd.SysProcAttr = &syscall.SysProcAttr{
702 Credential: &syscall.Credential{
703 Uid: uint32(uid),
704 Gid: uint32(gid),
705 },
706 AmbientCaps: []uintptr{CAP_SYS_TIME, CAP_SYSLOG},
707 }
708 if userns {
709 cmd.SysProcAttr.Cloneflags = syscall.CLONE_NEWUSER
710 const nobody = 65534
711 uid := os.Getuid()
712 gid := os.Getgid()
713 cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{{
714 ContainerID: int(nobody),
715 HostID: int(uid),
716 Size: int(1),
717 }}
718 cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{{
719 ContainerID: int(nobody),
720 HostID: int(gid),
721 Size: int(1),
722 }}
723
724
725 cmd.SysProcAttr.Credential = &syscall.Credential{
726 Uid: nobody,
727 Gid: nobody,
728 }
729 }
730 if err := cmd.Run(); err != nil {
731 t.Fatal(err.Error())
732 }
733 }
734
View as plain text