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