1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package syscall_test
6
7import (
8	"fmt"
9	"internal/testenv"
10	"io"
11	"io/fs"
12	"os"
13	"os/exec"
14	"path/filepath"
15	"runtime"
16	"slices"
17	"strconv"
18	"strings"
19	"sync"
20	"syscall"
21	"testing"
22	"unsafe"
23)
24
25// chtmpdir changes the working directory to a new temporary directory and
26// provides a cleanup function. Used when PWD is read-only.
27func chtmpdir(t *testing.T) func() {
28	oldwd, err := os.Getwd()
29	if err != nil {
30		t.Fatalf("chtmpdir: %v", err)
31	}
32	d, err := os.MkdirTemp("", "test")
33	if err != nil {
34		t.Fatalf("chtmpdir: %v", err)
35	}
36	if err := os.Chdir(d); err != nil {
37		t.Fatalf("chtmpdir: %v", err)
38	}
39	return func() {
40		if err := os.Chdir(oldwd); err != nil {
41			t.Fatalf("chtmpdir: %v", err)
42		}
43		os.RemoveAll(d)
44	}
45}
46
47func touch(t *testing.T, name string) {
48	f, err := os.Create(name)
49	if err != nil {
50		t.Fatal(err)
51	}
52	if err := f.Close(); err != nil {
53		t.Fatal(err)
54	}
55}
56
57const (
58	_AT_SYMLINK_NOFOLLOW = 0x100
59	_AT_FDCWD            = -0x64
60	_AT_EACCESS          = 0x200
61	_F_OK                = 0
62	_R_OK                = 4
63)
64
65func TestFaccessat(t *testing.T) {
66	defer chtmpdir(t)()
67	touch(t, "file1")
68
69	err := syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 0)
70	if err != nil {
71		t.Errorf("Faccessat: unexpected error: %v", err)
72	}
73
74	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, 2)
75	if err != syscall.EINVAL {
76		t.Errorf("Faccessat: unexpected error: %v, want EINVAL", err)
77	}
78
79	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_EACCESS)
80	if err != nil {
81		t.Errorf("Faccessat: unexpected error: %v", err)
82	}
83
84	err = os.Symlink("file1", "symlink1")
85	if err != nil {
86		t.Fatal(err)
87	}
88
89	err = syscall.Faccessat(_AT_FDCWD, "symlink1", _R_OK, _AT_SYMLINK_NOFOLLOW)
90	if err != nil {
91		t.Errorf("Faccessat SYMLINK_NOFOLLOW: unexpected error %v", err)
92	}
93
94	// We can't really test _AT_SYMLINK_NOFOLLOW, because there
95	// doesn't seem to be any way to change the mode of a symlink.
96	// We don't test _AT_EACCESS because such tests are only
97	// meaningful if run as root.
98
99	err = syscall.Fchmodat(_AT_FDCWD, "file1", 0, 0)
100	if err != nil {
101		t.Errorf("Fchmodat: unexpected error %v", err)
102	}
103
104	err = syscall.Faccessat(_AT_FDCWD, "file1", _F_OK, _AT_SYMLINK_NOFOLLOW)
105	if err != nil {
106		t.Errorf("Faccessat: unexpected error: %v", err)
107	}
108
109	err = syscall.Faccessat(_AT_FDCWD, "file1", _R_OK, _AT_SYMLINK_NOFOLLOW)
110	if err != syscall.EACCES {
111		if syscall.Getuid() != 0 {
112			t.Errorf("Faccessat: unexpected error: %v, want EACCES", err)
113		}
114	}
115}
116
117func TestFchmodat(t *testing.T) {
118	defer chtmpdir(t)()
119
120	touch(t, "file1")
121	os.Symlink("file1", "symlink1")
122
123	err := syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, 0)
124	if err != nil {
125		t.Fatalf("Fchmodat: unexpected error: %v", err)
126	}
127
128	fi, err := os.Stat("file1")
129	if err != nil {
130		t.Fatal(err)
131	}
132
133	if fi.Mode() != 0444 {
134		t.Errorf("Fchmodat: failed to change mode: expected %v, got %v", 0444, fi.Mode())
135	}
136
137	err = syscall.Fchmodat(_AT_FDCWD, "symlink1", 0444, _AT_SYMLINK_NOFOLLOW)
138	if err != syscall.EOPNOTSUPP {
139		t.Fatalf("Fchmodat: unexpected error: %v, expected EOPNOTSUPP", err)
140	}
141}
142
143func TestMain(m *testing.M) {
144	if os.Getenv("GO_DEATHSIG_PARENT") == "1" {
145		deathSignalParent()
146	} else if os.Getenv("GO_DEATHSIG_CHILD") == "1" {
147		deathSignalChild()
148	} else if os.Getenv("GO_SYSCALL_NOERROR") == "1" {
149		syscallNoError()
150	}
151
152	os.Exit(m.Run())
153}
154
155func TestParseNetlinkMessage(t *testing.T) {
156	for i, b := range [][]byte{
157		{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,
158			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,
159			1, 127, 0, 0, 1, 230, 228, 31, 144, 32, 186, 155, 211, 185, 151, 209, 179, 128, 24, 1, 86,
160			53, 119, 0, 0, 1, 1, 8, 10, 0, 17, 234, 12, 0, 17, 189, 126, 107, 106, 108, 107, 106, 13, 10,
161		},
162		{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,
163			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,
164			1, 127, 0, 0, 1, 237, 206, 31, 144, 73, 197, 128, 65, 250, 60, 192, 97, 128, 24, 1, 86, 253, 21, 0,
165			0, 1, 1, 8, 10, 0, 51, 106, 89, 0, 51, 102, 198, 108, 104, 106, 108, 107, 104, 108, 107, 104, 10,
166		},
167		{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,
168			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,
169			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,
170			10, 0, 52, 198, 200, 0, 51, 135, 232, 101, 115, 97, 103, 103, 10,
171		},
172	} {
173		m, err := syscall.ParseNetlinkMessage(b)
174		if err != syscall.EINVAL {
175			t.Errorf("#%d: got %v; want EINVAL", i, err)
176		}
177		if m != nil {
178			t.Errorf("#%d: got %v; want nil", i, m)
179		}
180	}
181}
182
183func TestSyscallNoError(t *testing.T) {
184	// On Linux there are currently no syscalls which don't fail and return
185	// a value larger than 0xfffffffffffff001 so we could test RawSyscall
186	// vs. RawSyscallNoError on 64bit architectures.
187	if unsafe.Sizeof(uintptr(0)) != 4 {
188		t.Skip("skipping on non-32bit architecture")
189	}
190
191	// See https://golang.org/issue/35422
192	// On MIPS, Linux returns whether the syscall had an error in a separate
193	// register (R7), not using a negative return value as on other
194	// architectures.
195	if runtime.GOARCH == "mips" || runtime.GOARCH == "mipsle" {
196		t.Skipf("skipping on %s", runtime.GOARCH)
197	}
198
199	if os.Getuid() != 0 {
200		t.Skip("skipping root only test")
201	}
202	if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
203		// The Go build system's swarming user is known not to be root.
204		// Unfortunately, it sometimes appears as root due the current
205		// implementation of a no-network check using 'unshare -n -r'.
206		// Since this test does need root to work, we need to skip it.
207		t.Skip("skipping root only test on a non-root builder")
208	}
209
210	if runtime.GOOS == "android" {
211		t.Skip("skipping on rooted android, see issue 27364")
212	}
213
214	// Copy the test binary to a location that a non-root user can read/execute
215	// after we drop privileges
216	tempDir, err := os.MkdirTemp("", "TestSyscallNoError")
217	if err != nil {
218		t.Fatalf("cannot create temporary directory: %v", err)
219	}
220	defer os.RemoveAll(tempDir)
221	os.Chmod(tempDir, 0755)
222
223	tmpBinary := filepath.Join(tempDir, filepath.Base(os.Args[0]))
224
225	src, err := os.Open(os.Args[0])
226	if err != nil {
227		t.Fatalf("cannot open binary %q, %v", os.Args[0], err)
228	}
229	defer src.Close()
230
231	dst, err := os.OpenFile(tmpBinary, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0755)
232	if err != nil {
233		t.Fatalf("cannot create temporary binary %q, %v", tmpBinary, err)
234	}
235	if _, err := io.Copy(dst, src); err != nil {
236		t.Fatalf("failed to copy test binary to %q, %v", tmpBinary, err)
237	}
238	err = dst.Close()
239	if err != nil {
240		t.Fatalf("failed to close test binary %q, %v", tmpBinary, err)
241	}
242
243	uid := uint32(0xfffffffe)
244	err = os.Chown(tmpBinary, int(uid), -1)
245	if err != nil {
246		t.Fatalf("failed to chown test binary %q, %v", tmpBinary, err)
247	}
248
249	err = os.Chmod(tmpBinary, 0755|fs.ModeSetuid)
250	if err != nil {
251		t.Fatalf("failed to set setuid bit on test binary %q, %v", tmpBinary, err)
252	}
253
254	cmd := exec.Command(tmpBinary)
255	cmd.Env = append(os.Environ(), "GO_SYSCALL_NOERROR=1")
256
257	out, err := cmd.CombinedOutput()
258	if err != nil {
259		t.Fatalf("failed to start first child process: %v", err)
260	}
261
262	got := strings.TrimSpace(string(out))
263	want := strconv.FormatUint(uint64(uid)+1, 10) + " / " +
264		strconv.FormatUint(uint64(-uid), 10) + " / " +
265		strconv.FormatUint(uint64(uid), 10)
266	if got != want {
267		if filesystemIsNoSUID(tmpBinary) {
268			t.Skip("skipping test when temp dir is mounted nosuid")
269		}
270		// formatted so the values are aligned for easier comparison
271		t.Errorf("expected %s,\ngot      %s", want, got)
272	}
273}
274
275// filesystemIsNoSUID reports whether the filesystem for the given
276// path is mounted nosuid.
277func filesystemIsNoSUID(path string) bool {
278	var st syscall.Statfs_t
279	if syscall.Statfs(path, &st) != nil {
280		return false
281	}
282	return st.Flags&syscall.MS_NOSUID != 0
283}
284
285func syscallNoError() {
286	// Test that the return value from SYS_GETEUID32 (which cannot fail)
287	// doesn't get treated as an error (see https://golang.org/issue/22924)
288	euid1, _, e := syscall.RawSyscall(syscall.Sys_GETEUID, 0, 0, 0)
289	euid2, _ := syscall.RawSyscallNoError(syscall.Sys_GETEUID, 0, 0, 0)
290
291	fmt.Println(uintptr(euid1), "/", int(e), "/", uintptr(euid2))
292	os.Exit(0)
293}
294
295// reference uapi/linux/prctl.h
296const (
297	PR_GET_KEEPCAPS uintptr = 7
298	PR_SET_KEEPCAPS         = 8
299)
300
301// TestAllThreadsSyscall tests that the go runtime can perform
302// syscalls that execute on all OSThreads - with which to support
303// POSIX semantics for security state changes.
304func TestAllThreadsSyscall(t *testing.T) {
305	if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
306		t.Skip("AllThreadsSyscall disabled with cgo")
307	}
308
309	fns := []struct {
310		label string
311		fn    func(uintptr) error
312	}{
313		{
314			label: "prctl<3-args>",
315			fn: func(v uintptr) error {
316				_, _, e := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0)
317				if e != 0 {
318					return e
319				}
320				return nil
321			},
322		},
323		{
324			label: "prctl<6-args>",
325			fn: func(v uintptr) error {
326				_, _, e := syscall.AllThreadsSyscall6(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, v, 0, 0, 0, 0)
327				if e != 0 {
328					return e
329				}
330				return nil
331			},
332		},
333	}
334
335	waiter := func(q <-chan uintptr, r chan<- uintptr, once bool) {
336		for x := range q {
337			runtime.LockOSThread()
338			v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0)
339			if e != 0 {
340				t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) failed: %v", syscall.Gettid(), e)
341			} else if x != v {
342				t.Errorf("tid=%d prctl(PR_GET_KEEPCAPS) mismatch: got=%d want=%d", syscall.Gettid(), v, x)
343			}
344			r <- v
345			if once {
346				break
347			}
348			runtime.UnlockOSThread()
349		}
350	}
351
352	// launches per fns member.
353	const launches = 11
354	question := make(chan uintptr)
355	response := make(chan uintptr)
356	defer close(question)
357
358	routines := 0
359	for i, v := range fns {
360		for j := 0; j < launches; j++ {
361			// Add another goroutine - the closest thing
362			// we can do to encourage more OS thread
363			// creation - while the test is running.  The
364			// actual thread creation may or may not be
365			// needed, based on the number of available
366			// unlocked OS threads at the time waiter
367			// calls runtime.LockOSThread(), but the goal
368			// of doing this every time through the loop
369			// is to race thread creation with v.fn(want)
370			// being executed. Via the once boolean we
371			// also encourage one in 5 waiters to return
372			// locked after participating in only one
373			// question response sequence. This allows the
374			// test to race thread destruction too.
375			once := routines%5 == 4
376			go waiter(question, response, once)
377
378			// Keep a count of how many goroutines are
379			// going to participate in the
380			// question/response test. This will count up
381			// towards 2*launches minus the count of
382			// routines that have been invoked with
383			// once=true.
384			routines++
385
386			// Decide what value we want to set the
387			// process-shared KEEPCAPS. Note, there is
388			// an explicit repeat of 0 when we change the
389			// variant of the syscall being used.
390			want := uintptr(j & 1)
391
392			// Invoke the AllThreadsSyscall* variant.
393			if err := v.fn(want); err != nil {
394				t.Errorf("[%d,%d] %s(PR_SET_KEEPCAPS, %d, ...): %v", i, j, v.label, j&1, err)
395			}
396
397			// At this point, we want all launched Go
398			// routines to confirm that they see the
399			// wanted value for KEEPCAPS.
400			for k := 0; k < routines; k++ {
401				question <- want
402			}
403
404			// At this point, we should have a large
405			// number of locked OS threads all wanting to
406			// reply.
407			for k := 0; k < routines; k++ {
408				if got := <-response; got != want {
409					t.Errorf("[%d,%d,%d] waiter result got=%d, want=%d", i, j, k, got, want)
410				}
411			}
412
413			// Provide an explicit opportunity for this Go
414			// routine to change Ms.
415			runtime.Gosched()
416
417			if once {
418				// One waiter routine will have exited.
419				routines--
420			}
421
422			// Whatever M we are now running on, confirm
423			// we see the wanted value too.
424			if v, _, e := syscall.Syscall(syscall.SYS_PRCTL, PR_GET_KEEPCAPS, 0, 0); e != 0 {
425				t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) failed: %v", i, j, e)
426			} else if v != want {
427				t.Errorf("[%d,%d] prctl(PR_GET_KEEPCAPS) gave wrong value: got=%v, want=1", i, j, v)
428			}
429		}
430	}
431}
432
433// compareStatus is used to confirm the contents of the thread
434// specific status files match expectations.
435func compareStatus(filter, expect string) error {
436	expected := filter + expect
437	pid := syscall.Getpid()
438	fs, err := os.ReadDir(fmt.Sprintf("/proc/%d/task", pid))
439	if err != nil {
440		return fmt.Errorf("unable to find %d tasks: %v", pid, err)
441	}
442	expectedProc := fmt.Sprintf("Pid:\t%d", pid)
443	foundAThread := false
444	for _, f := range fs {
445		tf := fmt.Sprintf("/proc/%s/status", f.Name())
446		d, err := os.ReadFile(tf)
447		if err != nil {
448			// There are a surprising number of ways this
449			// can error out on linux.  We've seen all of
450			// the following, so treat any error here as
451			// equivalent to the "process is gone":
452			//    os.IsNotExist(err),
453			//    "... : no such process",
454			//    "... : bad file descriptor.
455			continue
456		}
457		lines := strings.Split(string(d), "\n")
458		for _, line := range lines {
459			// Different kernel vintages pad differently.
460			line = strings.TrimSpace(line)
461			if strings.HasPrefix(line, "Pid:\t") {
462				// On loaded systems, it is possible
463				// for a TID to be reused really
464				// quickly. As such, we need to
465				// validate that the thread status
466				// info we just read is a task of the
467				// same process PID as we are
468				// currently running, and not a
469				// recently terminated thread
470				// resurfaced in a different process.
471				if line != expectedProc {
472					break
473				}
474				// Fall through in the unlikely case
475				// that filter at some point is
476				// "Pid:\t".
477			}
478			if strings.HasPrefix(line, filter) {
479				if line == expected {
480					foundAThread = true
481					break
482				}
483				if filter == "Groups:" && strings.HasPrefix(line, "Groups:\t") {
484					// https://github.com/golang/go/issues/46145
485					// Containers don't reliably output this line in sorted order so manually sort and compare that.
486					a := strings.Split(line[8:], " ")
487					slices.Sort(a)
488					got := strings.Join(a, " ")
489					if got == expected[8:] {
490						foundAThread = true
491						break
492					}
493
494				}
495				return fmt.Errorf("%q got:%q want:%q (bad) [pid=%d file:'%s' %v]\n", tf, line, expected, pid, string(d), expectedProc)
496			}
497		}
498	}
499	if !foundAThread {
500		return fmt.Errorf("found no thread /proc/<TID>/status files for process %q", expectedProc)
501	}
502	return nil
503}
504
505// killAThread locks the goroutine to an OS thread and exits; this
506// causes an OS thread to terminate.
507func killAThread(c <-chan struct{}) {
508	runtime.LockOSThread()
509	<-c
510	return
511}
512
513// TestSetuidEtc performs tests on all of the wrapped system calls
514// that mirror to the 9 glibc syscalls with POSIX semantics. The test
515// here is considered authoritative and should compile and run
516// CGO_ENABLED=0 or 1. Note, there is an extended copy of this same
517// test in ../../misc/cgo/test/issue1435.go which requires
518// CGO_ENABLED=1 and launches pthreads from C that run concurrently
519// with the Go code of the test - and the test validates that these
520// pthreads are also kept in sync with the security state changed with
521// the syscalls. Care should be taken to mirror any enhancements to
522// this test here in that file too.
523func TestSetuidEtc(t *testing.T) {
524	if syscall.Getuid() != 0 {
525		t.Skip("skipping root only test")
526	}
527	if testing.Short() && testenv.Builder() != "" && os.Getenv("USER") == "swarming" {
528		// The Go build system's swarming user is known not to be root.
529		// Unfortunately, it sometimes appears as root due the current
530		// implementation of a no-network check using 'unshare -n -r'.
531		// Since this test does need root to work, we need to skip it.
532		t.Skip("skipping root only test on a non-root builder")
533	}
534	if _, err := os.Stat("/etc/alpine-release"); err == nil {
535		t.Skip("skipping glibc test on alpine - go.dev/issue/19938")
536	}
537	vs := []struct {
538		call           string
539		fn             func() error
540		filter, expect string
541	}{
542		{call: "Setegid(1)", fn: func() error { return syscall.Setegid(1) }, filter: "Gid:", expect: "\t0\t1\t0\t1"},
543		{call: "Setegid(0)", fn: func() error { return syscall.Setegid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
544
545		{call: "Seteuid(1)", fn: func() error { return syscall.Seteuid(1) }, filter: "Uid:", expect: "\t0\t1\t0\t1"},
546		{call: "Setuid(0)", fn: func() error { return syscall.Setuid(0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
547
548		{call: "Setgid(1)", fn: func() error { return syscall.Setgid(1) }, filter: "Gid:", expect: "\t1\t1\t1\t1"},
549		{call: "Setgid(0)", fn: func() error { return syscall.Setgid(0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
550
551		{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"},
552		{call: "Setgroups(nil)", fn: func() error { return syscall.Setgroups(nil) }, filter: "Groups:", expect: ""},
553		{call: "Setgroups([]int{0})", fn: func() error { return syscall.Setgroups([]int{0}) }, filter: "Groups:", expect: "\t0"},
554
555		{call: "Setregid(101,0)", fn: func() error { return syscall.Setregid(101, 0) }, filter: "Gid:", expect: "\t101\t0\t0\t0"},
556		{call: "Setregid(0,102)", fn: func() error { return syscall.Setregid(0, 102) }, filter: "Gid:", expect: "\t0\t102\t102\t102"},
557		{call: "Setregid(0,0)", fn: func() error { return syscall.Setregid(0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
558
559		{call: "Setreuid(1,0)", fn: func() error { return syscall.Setreuid(1, 0) }, filter: "Uid:", expect: "\t1\t0\t0\t0"},
560		{call: "Setreuid(0,2)", fn: func() error { return syscall.Setreuid(0, 2) }, filter: "Uid:", expect: "\t0\t2\t2\t2"},
561		{call: "Setreuid(0,0)", fn: func() error { return syscall.Setreuid(0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
562
563		{call: "Setresgid(101,0,102)", fn: func() error { return syscall.Setresgid(101, 0, 102) }, filter: "Gid:", expect: "\t101\t0\t102\t0"},
564		{call: "Setresgid(0,102,101)", fn: func() error { return syscall.Setresgid(0, 102, 101) }, filter: "Gid:", expect: "\t0\t102\t101\t102"},
565		{call: "Setresgid(0,0,0)", fn: func() error { return syscall.Setresgid(0, 0, 0) }, filter: "Gid:", expect: "\t0\t0\t0\t0"},
566
567		{call: "Setresuid(1,0,2)", fn: func() error { return syscall.Setresuid(1, 0, 2) }, filter: "Uid:", expect: "\t1\t0\t2\t0"},
568		{call: "Setresuid(0,2,1)", fn: func() error { return syscall.Setresuid(0, 2, 1) }, filter: "Uid:", expect: "\t0\t2\t1\t2"},
569		{call: "Setresuid(0,0,0)", fn: func() error { return syscall.Setresuid(0, 0, 0) }, filter: "Uid:", expect: "\t0\t0\t0\t0"},
570	}
571
572	for i, v := range vs {
573		// Generate some thread churn as we execute the tests.
574		c := make(chan struct{})
575		go killAThread(c)
576		close(c)
577
578		if err := v.fn(); err != nil {
579			t.Errorf("[%d] %q failed: %v", i, v.call, err)
580			continue
581		}
582		if err := compareStatus(v.filter, v.expect); err != nil {
583			t.Errorf("[%d] %q comparison: %v", i, v.call, err)
584		}
585	}
586}
587
588// TestAllThreadsSyscallError verifies that errors are properly returned when
589// the syscall fails on the original thread.
590func TestAllThreadsSyscallError(t *testing.T) {
591	// SYS_CAPGET takes pointers as the first two arguments. Since we pass
592	// 0, we expect to get EFAULT back.
593	r1, r2, err := syscall.AllThreadsSyscall(syscall.SYS_CAPGET, 0, 0, 0)
594	if err == syscall.ENOTSUP {
595		t.Skip("AllThreadsSyscall disabled with cgo")
596	}
597	if err != syscall.EFAULT {
598		t.Errorf("AllThreadSyscall(SYS_CAPGET) got %d, %d, %v, want err %v", r1, r2, err, syscall.EFAULT)
599	}
600}
601
602// TestAllThreadsSyscallBlockedSyscall confirms that AllThreadsSyscall
603// can interrupt threads in long-running system calls. This test will
604// deadlock if this doesn't work correctly.
605func TestAllThreadsSyscallBlockedSyscall(t *testing.T) {
606	if _, _, err := syscall.AllThreadsSyscall(syscall.SYS_PRCTL, PR_SET_KEEPCAPS, 0, 0); err == syscall.ENOTSUP {
607		t.Skip("AllThreadsSyscall disabled with cgo")
608	}
609
610	rd, wr, err := os.Pipe()
611	if err != nil {
612		t.Fatalf("unable to obtain a pipe: %v", err)
613	}
614
615	// Perform a blocking read on the pipe.
616	var wg sync.WaitGroup
617	ready := make(chan bool)
618	wg.Add(1)
619	go func() {
620		data := make([]byte, 1)
621
622		// To narrow the window we have to wait for this
623		// goroutine to block in read, synchronize just before
624		// calling read.
625		ready <- true
626
627		// We use syscall.Read directly to avoid the poller.
628		// This will return when the write side is closed.
629		n, err := syscall.Read(int(rd.Fd()), data)
630		if !(n == 0 && err == nil) {
631			t.Errorf("expected read to return 0, got %d, %s", n, err)
632		}
633
634		// Clean up rd and also ensure rd stays reachable so
635		// it doesn't get closed by GC.
636		rd.Close()
637		wg.Done()
638	}()
639	<-ready
640
641	// Loop here to give the goroutine more time to block in read.
642	// Generally this will trigger on the first iteration anyway.
643	pid := syscall.Getpid()
644	for i := 0; i < 100; i++ {
645		if id, _, e := syscall.AllThreadsSyscall(syscall.SYS_GETPID, 0, 0, 0); e != 0 {
646			t.Errorf("[%d] getpid failed: %v", i, e)
647		} else if int(id) != pid {
648			t.Errorf("[%d] getpid got=%d, want=%d", i, id, pid)
649		}
650		// Provide an explicit opportunity for this goroutine
651		// to change Ms.
652		runtime.Gosched()
653	}
654	wr.Close()
655	wg.Wait()
656}
657
658func TestPrlimitSelf(t *testing.T) {
659	origLimit := syscall.OrigRlimitNofile()
660	origRlimitNofile := syscall.GetInternalOrigRlimitNofile()
661
662	if origLimit == nil {
663		defer origRlimitNofile.Store(origLimit)
664		origRlimitNofile.Store(&syscall.Rlimit{
665			Cur: 1024,
666			Max: 65536,
667		})
668	}
669
670	// Get current process's nofile limit
671	var lim syscall.Rlimit
672	if err := syscall.Prlimit(0, syscall.RLIMIT_NOFILE, nil, &lim); err != nil {
673		t.Fatalf("Failed to get the current nofile limit: %v", err)
674	}
675	// Set current process's nofile limit through prlimit
676	if err := syscall.Prlimit(0, syscall.RLIMIT_NOFILE, &lim, nil); err != nil {
677		t.Fatalf("Prlimit self failed: %v", err)
678	}
679
680	rlimLater := origRlimitNofile.Load()
681	if rlimLater != nil {
682		t.Fatalf("origRlimitNofile got=%v, want=nil", rlimLater)
683	}
684}
685
686func TestPrlimitOtherProcess(t *testing.T) {
687	origLimit := syscall.OrigRlimitNofile()
688	origRlimitNofile := syscall.GetInternalOrigRlimitNofile()
689
690	if origLimit == nil {
691		defer origRlimitNofile.Store(origLimit)
692		origRlimitNofile.Store(&syscall.Rlimit{
693			Cur: 1024,
694			Max: 65536,
695		})
696	}
697	rlimOrig := origRlimitNofile.Load()
698
699	// Start a child process firstly,
700	// so we can use Prlimit to set it's nofile limit.
701	cmd := exec.Command("sleep", "infinity")
702	cmd.Start()
703	defer func() {
704		cmd.Process.Kill()
705		cmd.Process.Wait()
706	}()
707
708	// Get child process's current nofile limit
709	var lim syscall.Rlimit
710	if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, nil, &lim); err != nil {
711		t.Fatalf("Failed to get the current nofile limit: %v", err)
712	}
713	// Set child process's nofile rlimit through prlimit
714	if err := syscall.Prlimit(cmd.Process.Pid, syscall.RLIMIT_NOFILE, &lim, nil); err != nil {
715		t.Fatalf("Prlimit(%d) failed: %v", cmd.Process.Pid, err)
716	}
717
718	rlimLater := origRlimitNofile.Load()
719	if rlimLater != rlimOrig {
720		t.Fatalf("origRlimitNofile got=%v, want=%v", rlimLater, rlimOrig)
721	}
722}
723