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