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