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 vs := []struct {
520 call string
521 fn func() error
522 filter, expect string
523 }{
524 {call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
525 {call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
526
527 {call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
528 {call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
529
530 {call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
531 {call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
532
533 {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"},
534 {call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
535 {call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
536
537 {call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
538 {call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
539 {call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
540
541 {call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
542 {call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
543 {call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
544
545 {call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
546 {call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
547 {call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
548
549 {call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
550 {call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
551 {call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
552 }
553
554 for i, v := range vs {
555
556 c := make(chan struct{})
557 go killAThread(c)
558 close(c)
559
560 if err := v.fn(); err != nil {
561 t.Errorf("[%d] %q failed: %v", i, v.call, err)
562 continue
563 }
564 if err := compareStatus(v.filter, v.expect); err != nil {
565 t.Errorf("[%d] %q comparison: %v", i, v.call, err)
566 }
567 }
568 }
569
570
571
572 func TestAllThreadsSyscallError(t *testing.T) {
573
574
575 r1, r2, err := syscall.AllThreadsSyscall(syscall.SYS_CAPGET, 0, 0, 0)
576 if err == syscall.ENOTSUP {
577 t.Skip("AllThreadsSyscall disabled with cgo")
578 }
579 if err != syscall.EFAULT {
580 t.Errorf("AllThreadSyscall(SYS_CAPGET) got %d, %d, %v, want err %v", r1, r2, err, syscall.EFAULT)
581 }
582 }
583
584
585
586
587 func TestAllThreadsSyscallBlockedSyscall(t *testing.T) {
588 if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
589 t.Skip("AllThreadsSyscall disabled with cgo")
590 }
591
592 rd, wr, err := os.Pipe()
593 if err != nil {
594 t.Fatalf("unable to obtain a pipe: %v", err)
595 }
596
597
598 var wg sync.WaitGroup
599 ready := make(chan bool)
600 wg.Add(1)
601 go func() {
602 data := make([]byte, 1)
603
604
605
606
607 ready <- true
608
609
610
611 n, err := syscall.Read(int(rd.Fd()), data)
612 if !(n == 0 && err == nil) {
613 t.Errorf("expected read to return 0, got %d, %s", n, err)
614 }
615
616
617
618 rd.Close()
619 wg.Done()
620 }()
621 <-ready
622
623
624
625 pid := syscall.Getpid()
626 for i := 0; i < 100; i++ {
627 if id, _, e := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0); e != 0 {
628 t.Errorf("[%d] getpid failed: %v", i, e)
629 } else if int(id) != pid {
630 t.Errorf("[%d] getpid got=%d, want=%d", i, id, pid)
631 }
632
633
634 runtime.Gosched()
635 }
636 wr.Close()
637 wg.Wait()
638 }
639
View as plain text