Source file
src/syscall/syscall_linux_test.go
Documentation: syscall
1
2
3
4
5 package syscall_test
6
7 import (
8 "bufio"
9 "fmt"
10 "io"
11 "io/fs"
12 "os"
13 "os/exec"
14 "os/signal"
15 "path/filepath"
16 "runtime"
17 "sort"
18 "strconv"
19 "strings"
20 "syscall"
21 "testing"
22 "time"
23 "unsafe"
24 )
25
26
27
28 func chtmpdir(t *testing.T) func() {
29 oldwd, err := os.Getwd()
30 if err != nil {
31 t.Fatalf("chtmpdir: %v", err)
32 }
33 d, err := os.MkdirTemp("", "test")
34 if err != nil {
35 t.Fatalf("chtmpdir: %v", err)
36 }
37 if err := os.Chdir(d); err != nil {
38 t.Fatalf("chtmpdir: %v", err)
39 }
40 return func() {
41 if err := os.Chdir(oldwd); err != nil {
42 t.Fatalf("chtmpdir: %v", err)
43 }
44 os.RemoveAll(d)
45 }
46 }
47
48 func touch(t *testing.T, name string) {
49 f, err := os.Create(name)
50 if err != nil {
51 t.Fatal(err)
52 }
53 if err := f.Close(); err != nil {
54 t.Fatal(err)
55 }
56 }
57
58 const (
59 _AT_SYMLINK_NOFOLLOW = 0x100
60 _AT_FDCWD = -0x64
61 _AT_EACCESS = 0x200
62 _F_OK = 0
63 _R_OK = 4
64 )
65
66 func TestFaccessat(t *testing.T) {
67 defer chtmpdir(t)()
68 touch(t, "file1")
69
70 err := syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 0)
71 if err != nil {
72 t.Errorf("Faccessat: unexpected error: %v", err)
73 }
74
75 err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 2)
76 if err != syscall.EINVAL {
77 t.Errorf("Faccessat: unexpected error: %v, want EINVAL", err)
78 }
79
80 err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_EACCESS)
81 if err != nil {
82 t.Errorf("Faccessat: unexpected error: %v", err)
83 }
84
85 err = os.Symlink("file1", "symlink1")
86 if err != nil {
87 t.Fatal(err)
88 }
89
90 err = syscall.Faccessat(_AT_FDCWD, "symlink1", _R_OK, _AT_SYMLINK_NOFOLLOW)
91 if err != nil {
92 t.Errorf("Faccessat SYMLINK_NOFOLLOW: unexpected error %v", err)
93 }
94
95
96
97
98
99
100 err = syscall.Fchmodat(_AT_FDCWD, "file1", 0, 0)
101 if err != nil {
102 t.Errorf("Fchmodat: unexpected error %v", err)
103 }
104
105 err = syscall.Faccessat(_AT_FDCWD, "file1", _F_OK, _AT_SYMLINK_NOFOLLOW)
106 if err != nil {
107 t.Errorf("Faccessat: unexpected error: %v", err)
108 }
109
110 err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_SYMLINK_NOFOLLOW)
111 if err != syscall.EACCES {
112 if syscall.Getuid() != 0 {
113 t.Errorf("Faccessat: unexpected error: %v, want EACCES", err)
114 }
115 }
116 }
117
118 func TestFchmodat(t *testing.T) {
119 defer chtmpdir(t)()
120
121 touch(t, "file1")
122 os.Symlink("file1", "symlink1")
123
124 err := syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, 0)
125 if err != nil {
126 t.Fatalf("Fchmodat: unexpected error: %v", err)
127 }
128
129 fi, err := os.Stat("file1")
130 if err != nil {
131 t.Fatal(err)
132 }
133
134 if fi.Mode() != 0444 {
135 t.Errorf("Fchmodat: failed to change mode: expected %v, got %v", 0444, fi.Mode())
136 }
137
138 err = syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, _AT_SYMLINK_NOFOLLOW)
139 if err != syscall.EOPNOTSUPP {
140 t.Fatalf("Fchmodat: unexpected error: %v, expected EOPNOTSUPP", err)
141 }
142 }
143
144 func TestMain(m *testing.M) {
145 if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
146 deathSignalParent()
147 } else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
148 deathSignalChild()
149 } else if os.Getenv("GO_SYSCALL_NOERROR") == "1" {
150 syscallNoError()
151 }
152
153 os.Exit(m.Run())
154 }
155
156 func TestLinuxDeathSignal(t *testing.T) {
157 if os.Getuid() != 0 {
158 t.Skip("skipping root only test")
159 }
160
161
162
163 tempDir, err := os.MkdirTemp("", "TestDeathSignal")
164 if err != nil {
165 t.Fatalf("cannot create temporary directory: %v", err)
166 }
167 defer os.RemoveAll(tempDir)
168 os.Chmod(tempDir, 0755)
169
170 tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
171
172 src, err := os.Open(os.Args[0])
173 if err != nil {
174 t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
175 }
176 defer src.Close()
177
178 dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
179 if err != nil {
180 t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
181 }
182 if _, err := io.Copy(dst, src); err != nil {
183 t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
184 }
185 err = dst.Close()
186 if err != nil {
187 t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
188 }
189
190 cmd := exec.Command(tmpBinary)
191 cmd.Env = append(os.Environ(), "GO_DEATHSIG_PARENT=1")
192 chldStdin, err := cmd.StdinPipe()
193 if err != nil {
194 t.Fatalf("failed to create new stdin pipe: %v", err)
195 }
196 chldStdout, err := cmd.StdoutPipe()
197 if err != nil {
198 t.Fatalf("failed to create new stdout pipe: %v", err)
199 }
200 cmd.Stderr = os.Stderr
201
202 err = cmd.Start()
203 defer cmd.Wait()
204 if err != nil {
205 t.Fatalf("failed to start first child process: %v", err)
206 }
207
208 chldPipe := bufio.NewReader(chldStdout)
209
210 if got, err := chldPipe.ReadString('\n'); got == "start\n" {
211 syscall.Kill(cmd.Process.Pid, syscall.SIGTERM)
212
213 go func() {
214 time.Sleep(5 * time.Second)
215 chldStdin.Close()
216 }()
217
218 want := "ok\n"
219 if got, err = chldPipe.ReadString('\n'); got != want {
220 t.Fatalf("expected %q, received %q, %v", want, got, err)
221 }
222 } else {
223 t.Fatalf("did not receive start from child, received %q, %v", got, err)
224 }
225 }
226
227 func deathSignalParent() {
228 cmd := exec.Command(os.Args[0])
229 cmd.Env = append(os.Environ(),
230 "GO_DEATHSIG_PARENT=",
231 "GO_DEATHSIG_CHILD=1",
232 )
233 cmd.Stdin = os.Stdin
234 cmd.Stdout = os.Stdout
235 attrs := syscall.SysProcAttr{
236 Pdeathsig: syscall.SIGUSR1,
237
238
239 Credential: &syscall.Credential{Uid: 99, Gid: 99},
240 }
241 cmd.SysProcAttr = &attrs
242
243 err := cmd.Start()
244 if err != nil {
245 fmt.Fprintf(os.Stderr, "death signal parent error: %v\n", err)
246 os.Exit(1)
247 }
248 cmd.Wait()
249 os.Exit(0)
250 }
251
252 func deathSignalChild() {
253 c := make(chan os.Signal, 1)
254 signal.Notify(c, syscall.SIGUSR1)
255 go func() {
256 <-c
257 fmt.Println("ok")
258 os.Exit(0)
259 }()
260 fmt.Println("start")
261
262 buf := make([]byte, 32)
263 os.Stdin.Read(buf)
264
265
266 fmt.Println("not ok")
267 os.Exit(1)
268 }
269
270 func TestParseNetlinkMessage(t *testing.T) {
271 for i, b := range [][]byte{
272 {103, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 5, 8, 0, 3,
273 0, 8, 0, 6, 0, 0, 0, 0, 1, 63, 0, 10, 0, 69, 16, 0, 59, 39, 82, 64, 0, 64, 6, 21, 89, 127, 0, 0,
274 1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86,
275 53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10,
276 },
277 {106, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 3, 8, 0, 3,
278 0, 8, 0, 6, 0, 0, 0, 0, 1, 66, 0, 10, 0, 69, 0, 0, 62, 230, 255, 64, 0, 64, 6, 85, 184, 127, 0, 0,
279 1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0,
280 0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10,
281 },
282 {102, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 2, 11, 0, 1, 0, 0, 0, 0, 1, 8, 0, 3, 0,
283 8, 0, 6, 0, 0, 0, 0, 1, 62, 0, 10, 0, 69, 0, 0, 58, 231, 2, 64, 0, 64, 6, 85, 185, 127, 0, 0, 1, 127,
284 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 86, 250, 60, 192, 97, 128, 24, 1, 86, 104, 64, 0, 0, 1, 1, 8,
285 10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10,
286 },
287 } {
288 m, err := syscall.ParseNetlinkMessage(b)
289 if err != syscall.EINVAL {
290 t.Errorf("#%d: got %v; want EINVAL", i, err)
291 }
292 if m != nil {
293 t.Errorf("#%d: got %v; want nil", i, m)
294 }
295 }
296 }
297
298 func TestSyscallNoError(t *testing.T) {
299
300
301
302 if unsafe.Sizeof(uintptr(0)) != 4 {
303 t.Skip("skipping on non-32bit architecture")
304 }
305
306
307
308
309
310 if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
311 t.Skipf("skipping on %s", runtime.GOARCH)
312 }
313
314 if os.Getuid() != 0 {
315 t.Skip("skipping root only test")
316 }
317
318 if runtime.GOOS == "android" {
319 t.Skip("skipping on rooted android, see issue 27364")
320 }
321
322
323
324 tempDir, err := os.MkdirTemp("", "TestSyscallNoError")
325 if err != nil {
326 t.Fatalf("cannot create temporary directory: %v", err)
327 }
328 defer os.RemoveAll(tempDir)
329 os.Chmod(tempDir, 0755)
330
331 tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
332
333 src, err := os.Open(os.Args[0])
334 if err != nil {
335 t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
336 }
337 defer src.Close()
338
339 dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
340 if err != nil {
341 t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
342 }
343 if _, err := io.Copy(dst, src); err != nil {
344 t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
345 }
346 err = dst.Close()
347 if err != nil {
348 t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
349 }
350
351 uid := uint32(0xfffffffe)
352 err = os.Chown(tmpBinary, int(uid), -1)
353 if err != nil {
354 t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err)
355 }
356
357 err = os.Chmod(tmpBinary, 0755|fs.ModeSetuid)
358 if err != nil {
359 t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err)
360 }
361
362 cmd := exec.Command(tmpBinary)
363 cmd.Env = append(os.Environ(), "GO_SYSCALL_NOERROR=1")
364
365 out, err := cmd.CombinedOutput()
366 if err != nil {
367 t.Fatalf("failed to start first child process: %v", err)
368 }
369
370 got := strings.TrimSpace(string(out))
371 want := strconv.FormatUint(uint64(uid)+1, 10) + " / " +
372 strconv.FormatUint(uint64(-uid), 10) + " / " +
373 strconv.FormatUint(uint64(uid), 10)
374 if got != want {
375 if filesystemIsNoSUID(tmpBinary) {
376 t.Skip("skipping test when temp dir is mounted nosuid")
377 }
378
379 t.Errorf("expected %s,\ngot %s", want, got)
380 }
381 }
382
383
384
385 func filesystemIsNoSUID(path string) bool {
386 var st syscall.Statfs_t
387 if syscall.Statfs(path, &st) != nil {
388 return false
389 }
390 return st.Flags&syscall.MS_NOSUID != 0
391 }
392
393 func syscallNoError() {
394
395
396 euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
397 euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
398
399 fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
400 os.Exit(0)
401 }
402
403
404 const (
405 PR_GET_KEEPCAPS uintptr = 7
406 PR_SET_KEEPCAPS = 8
407 )
408
409
410
411
412 func TestAllThreadsSyscall(t *testing.T) {
413 if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
414 t.Skip("AllThreadsSyscall disabled with cgo")
415 }
416
417 fns := []struct {
418 label string
419 fn func(uintptr) error
420 }{
421 {
422 label: "prctl<3-args>",
423 fn: func(v uintptr) error {
424 _, _, e := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0)
425 if e != 0 {
426 return e
427 }
428 return nil
429 },
430 },
431 {
432 label: "prctl<6-args>",
433 fn: func(v uintptr) error {
434 _, _, e := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0, 0, 0, 0)
435 if e != 0 {
436 return e
437 }
438 return nil
439 },
440 },
441 }
442
443 waiter := func(q <-chan uintptr, r chan<- uintptr, once bool) {
444 for x := range q {
445 runtime.LockOSThread()
446 v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0)
447 if e != 0 {
448 t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) failed: %v", syscall.Gettid(), e)
449 } else if x != v {
450 t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) mismatch: got=%d want=%d", syscall.Gettid(), v, x)
451 }
452 r <- v
453 if once {
454 break
455 }
456 runtime.UnlockOSThread()
457 }
458 }
459
460
461 const launches = 11
462 question := make(chan uintptr)
463 response := make(chan uintptr)
464 defer close(question)
465
466 routines := 0
467 for i, v := range fns {
468 for j := 0; j < launches; j++ {
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483 once := routines%5 == 4
484 go waiter(question, response, once)
485
486
487
488
489
490
491
492 routines++
493
494
495
496
497
498 want := uintptr(j & 1)
499
500
501 if err := v.fn(want); err != nil {
502 t.Errorf("[%d,%d] %s(PR_SET_KEEPCAPS, %d, ...): %v", i, j, v.label, j&1, err)
503 }
504
505
506
507
508 for k := 0; k < routines; k++ {
509 question <- want
510 }
511
512
513
514
515 for k := 0; k < routines; k++ {
516 if got := <-response; got != want {
517 t.Errorf("[%d,%d,%d] waiter result got=%d, want=%d", i, j, k, got, want)
518 }
519 }
520
521
522
523 runtime.Gosched()
524
525 if once {
526
527 routines--
528 }
529
530
531
532 if v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0); e != 0 {
533 t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) failed: %v", i, j, e)
534 } else if v != want {
535 t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) gave wrong value: got=%v, want=1", i, j, v)
536 }
537 }
538 }
539 }
540
541
542
543 func compareStatus(filter, expect string) error {
544 expected := filter + expect
545 pid := syscall.Getpid()
546 fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
547 if err != nil {
548 return fmt.Errorf("unable to find %d tasks: %v", pid, err)
549 }
550 expectedProc := fmt.Sprintf("Pid:\t%d", pid)
551 foundAThread := false
552 for _, f := range fs {
553 tf := fmt.Sprintf("/proc/%s/status", f.Name())
554 d, err := os.ReadFile(tf)
555 if err != nil {
556
557
558
559
560
561
562
563 continue
564 }
565 lines := strings.Split(string(d), "\n")
566 for _, line := range lines {
567
568 line = strings.TrimSpace(line)
569 if strings.HasPrefix(line, "Pid:\t") {
570
571
572
573
574
575
576
577
578
579 if line != expectedProc {
580 break
581 }
582
583
584
585 }
586 if strings.HasPrefix(line, filter) {
587 if line == expected {
588 foundAThread = true
589 break
590 }
591 if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") {
592
593
594 a := strings.Split(line[8:], " ")
595 sort.Strings(a)
596 got := strings.Join(a, " ")
597 if got == expected[8:] {
598 foundAThread = true
599 break
600 }
601
602 }
603 return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
604 }
605 }
606 }
607 if !foundAThread {
608 return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
609 }
610 return nil
611 }
612
613
614
615 func killAThread(c <-chan struct{}) {
616 runtime.LockOSThread()
617 <-c
618 return
619 }
620
621
622
623
624
625
626
627
628
629
630
631 func TestSetuidEtc(t *testing.T) {
632 if syscall.Getuid() != 0 {
633 t.Skip("skipping root only test")
634 }
635 vs := []struct {
636 call string
637 fn func() error
638 filter, expect string
639 }{
640 {call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
641 {call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
642
643 {call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
644 {call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
645
646 {call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
647 {call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
648
649 {call: "Setgroups([]int{0,1,2,3})", fn: func() error { return syscall.Setgroups([]int{0, 1, 2, 3}) }, filter: "Groups:", expect: "\t0 1 2 3"},
650 {call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
651 {call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
652
653 {call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
654 {call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
655 {call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
656
657 {call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
658 {call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
659 {call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
660
661 {call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
662 {call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
663 {call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
664
665 {call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
666 {call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
667 {call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
668 }
669
670 for i, v := range vs {
671
672 c := make(chan struct{})
673 go killAThread(c)
674 close(c)
675
676 if err := v.fn(); err != nil {
677 t.Errorf("[%d] %q failed: %v", i, v.call, err)
678 continue
679 }
680 if err := compareStatus(v.filter, v.expect); err != nil {
681 t.Errorf("[%d] %q comparison: %v", i, v.call, err)
682 }
683 }
684 }
685
View as plain text