1// Copyright 2014 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
5//go:build freebsd
6// +build freebsd
7
8package unix_test
9
10import (
11	"flag"
12	"fmt"
13	"io/ioutil"
14	"net"
15	"os"
16	"os/exec"
17	"path"
18	"path/filepath"
19	"runtime"
20	"testing"
21
22	"golang.org/x/sys/unix"
23)
24
25func TestSysctlUint64(t *testing.T) {
26	_, err := unix.SysctlUint64("vm.swap_total")
27	if err != nil {
28		t.Fatal(err)
29	}
30}
31
32// FIXME: Infrastructure for launching tests in subprocesses stolen from openbsd_test.go - refactor?
33// testCmd generates a proper command that, when executed, runs the test
34// corresponding to the given key.
35
36type testProc struct {
37	fn      func()                    // should always exit instead of returning
38	arg     func(t *testing.T) string // generate argument for test
39	cleanup func(arg string) error    // for instance, delete coredumps from testing pledge
40	success bool                      // whether zero-exit means success or failure
41}
42
43var (
44	testProcs = map[string]testProc{}
45	procName  = ""
46	procArg   = ""
47)
48
49const (
50	optName = "sys-unix-internal-procname"
51	optArg  = "sys-unix-internal-arg"
52)
53
54func init() {
55	flag.StringVar(&procName, optName, "", "internal use only")
56	flag.StringVar(&procArg, optArg, "", "internal use only")
57
58}
59
60func testCmd(procName string, procArg string) (*exec.Cmd, error) {
61	exe, err := filepath.Abs(os.Args[0])
62	if err != nil {
63		return nil, err
64	}
65	cmd := exec.Command(exe, "-"+optName+"="+procName, "-"+optArg+"="+procArg)
66	cmd.Stdout, cmd.Stderr = os.Stdout, os.Stderr
67	return cmd, nil
68}
69
70// ExitsCorrectly is a comprehensive, one-line-of-use wrapper for testing
71// a testProc with a key.
72func ExitsCorrectly(t *testing.T, procName string) {
73	s := testProcs[procName]
74	arg := "-"
75	if s.arg != nil {
76		arg = s.arg(t)
77	}
78	c, err := testCmd(procName, arg)
79	defer func(arg string) {
80		if err := s.cleanup(arg); err != nil {
81			t.Fatalf("Failed to run cleanup for %s %s %#v", procName, err, err)
82		}
83	}(arg)
84	if err != nil {
85		t.Fatalf("Failed to construct command for %s", procName)
86	}
87	if (c.Run() == nil) != s.success {
88		result := "succeed"
89		if !s.success {
90			result = "fail"
91		}
92		t.Fatalf("Process did not %s when it was supposed to", result)
93	}
94}
95
96func TestMain(m *testing.M) {
97	flag.Parse()
98	if procName != "" {
99		t := testProcs[procName]
100		t.fn()
101		os.Stderr.WriteString("test function did not exit\n")
102		if t.success {
103			os.Exit(1)
104		} else {
105			os.Exit(0)
106		}
107	}
108	os.Exit(m.Run())
109}
110
111// end of infrastructure
112
113const testfile = "gocapmodetest"
114const testfile2 = testfile + "2"
115
116func CapEnterTest() {
117	_, err := os.OpenFile(path.Join(procArg, testfile), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
118	if err != nil {
119		panic(fmt.Sprintf("OpenFile: %s", err))
120	}
121
122	err = unix.CapEnter()
123	if err != nil {
124		panic(fmt.Sprintf("CapEnter: %s", err))
125	}
126
127	_, err = os.OpenFile(path.Join(procArg, testfile2), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
128	if err == nil {
129		panic("OpenFile works!")
130	}
131	if err.(*os.PathError).Err != unix.ECAPMODE {
132		panic(fmt.Sprintf("OpenFile failed wrong: %s %#v", err, err))
133	}
134	os.Exit(0)
135}
136
137func makeTempDir(t *testing.T) string {
138	d, err := ioutil.TempDir("", "go_openat_test")
139	if err != nil {
140		t.Fatalf("TempDir failed: %s", err)
141	}
142	return d
143}
144
145func removeTempDir(arg string) error {
146	err := os.RemoveAll(arg)
147	if err != nil && err.(*os.PathError).Err == unix.ENOENT {
148		return nil
149	}
150	return err
151}
152
153func init() {
154	testProcs["cap_enter"] = testProc{
155		CapEnterTest,
156		makeTempDir,
157		removeTempDir,
158		true,
159	}
160}
161
162func TestCapEnter(t *testing.T) {
163	if runtime.GOARCH != "amd64" {
164		t.Skipf("skipping test on %s", runtime.GOARCH)
165	}
166	ExitsCorrectly(t, "cap_enter")
167}
168
169func OpenatTest() {
170	f, err := os.Open(procArg)
171	if err != nil {
172		panic(err)
173	}
174
175	err = unix.CapEnter()
176	if err != nil {
177		panic(fmt.Sprintf("CapEnter: %s", err))
178	}
179
180	fxx, err := unix.Openat(int(f.Fd()), "xx", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
181	if err != nil {
182		panic(err)
183	}
184	unix.Close(fxx)
185
186	// The right to open BASE/xx is not ambient
187	_, err = os.OpenFile(procArg+"/xx", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
188	if err == nil {
189		panic("OpenFile succeeded")
190	}
191	if err.(*os.PathError).Err != unix.ECAPMODE {
192		panic(fmt.Sprintf("OpenFile failed wrong: %s %#v", err, err))
193	}
194
195	// Can't make a new directory either
196	err = os.Mkdir(procArg+"2", 0777)
197	if err == nil {
198		panic("MKdir succeeded")
199	}
200	if err.(*os.PathError).Err != unix.ECAPMODE {
201		panic(fmt.Sprintf("Mkdir failed wrong: %s %#v", err, err))
202	}
203
204	// Remove all caps except read and lookup.
205	r, err := unix.CapRightsInit([]uint64{unix.CAP_READ, unix.CAP_LOOKUP})
206	if err != nil {
207		panic(fmt.Sprintf("CapRightsInit failed: %s %#v", err, err))
208	}
209	err = unix.CapRightsLimit(f.Fd(), r)
210	if err != nil {
211		panic(fmt.Sprintf("CapRightsLimit failed: %s %#v", err, err))
212	}
213
214	// Check we can get the rights back again
215	r, err = unix.CapRightsGet(f.Fd())
216	if err != nil {
217		panic(fmt.Sprintf("CapRightsGet failed: %s %#v", err, err))
218	}
219	b, err := unix.CapRightsIsSet(r, []uint64{unix.CAP_READ, unix.CAP_LOOKUP})
220	if err != nil {
221		panic(fmt.Sprintf("CapRightsIsSet failed: %s %#v", err, err))
222	}
223	if !b {
224		panic(fmt.Sprintf("Unexpected rights"))
225	}
226	b, err = unix.CapRightsIsSet(r, []uint64{unix.CAP_READ, unix.CAP_LOOKUP, unix.CAP_WRITE})
227	if err != nil {
228		panic(fmt.Sprintf("CapRightsIsSet failed: %s %#v", err, err))
229	}
230	if b {
231		panic(fmt.Sprintf("Unexpected rights (2)"))
232	}
233
234	// Can no longer create a file
235	_, err = unix.Openat(int(f.Fd()), "xx2", os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
236	if err == nil {
237		panic("Openat succeeded")
238	}
239	if err != unix.ENOTCAPABLE {
240		panic(fmt.Sprintf("OpenFileAt failed wrong: %s %#v", err, err))
241	}
242
243	// But can read an existing one
244	_, err = unix.Openat(int(f.Fd()), "xx", os.O_RDONLY, 0666)
245	if err != nil {
246		panic(fmt.Sprintf("Openat failed: %s %#v", err, err))
247	}
248
249	os.Exit(0)
250}
251
252func init() {
253	testProcs["openat"] = testProc{
254		OpenatTest,
255		makeTempDir,
256		removeTempDir,
257		true,
258	}
259}
260
261func TestOpenat(t *testing.T) {
262	if runtime.GOARCH != "amd64" {
263		t.Skipf("skipping test on %s", runtime.GOARCH)
264	}
265	ExitsCorrectly(t, "openat")
266}
267
268func TestCapRightsSetAndClear(t *testing.T) {
269	r, err := unix.CapRightsInit([]uint64{unix.CAP_READ, unix.CAP_WRITE, unix.CAP_PDWAIT})
270	if err != nil {
271		t.Fatalf("CapRightsInit failed: %s", err)
272	}
273
274	err = unix.CapRightsSet(r, []uint64{unix.CAP_EVENT, unix.CAP_LISTEN})
275	if err != nil {
276		t.Fatalf("CapRightsSet failed: %s", err)
277	}
278
279	b, err := unix.CapRightsIsSet(r, []uint64{unix.CAP_READ, unix.CAP_WRITE, unix.CAP_PDWAIT, unix.CAP_EVENT, unix.CAP_LISTEN})
280	if err != nil {
281		t.Fatalf("CapRightsIsSet failed: %s", err)
282	}
283	if !b {
284		t.Fatalf("Wrong rights set")
285	}
286
287	err = unix.CapRightsClear(r, []uint64{unix.CAP_READ, unix.CAP_PDWAIT})
288	if err != nil {
289		t.Fatalf("CapRightsClear failed: %s", err)
290	}
291
292	b, err = unix.CapRightsIsSet(r, []uint64{unix.CAP_WRITE, unix.CAP_EVENT, unix.CAP_LISTEN})
293	if err != nil {
294		t.Fatalf("CapRightsIsSet failed: %s", err)
295	}
296	if !b {
297		t.Fatalf("Wrong rights set")
298	}
299}
300
301func TestGetsockoptXucred(t *testing.T) {
302	fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM, 0)
303	if err != nil {
304		t.Fatalf("Socketpair: %v", err)
305	}
306
307	srvFile := os.NewFile(uintptr(fds[0]), "server")
308	cliFile := os.NewFile(uintptr(fds[1]), "client")
309	defer srvFile.Close()
310	defer cliFile.Close()
311
312	srv, err := net.FileConn(srvFile)
313	if err != nil {
314		t.Fatalf("FileConn: %v", err)
315	}
316	defer srv.Close()
317
318	cli, err := net.FileConn(cliFile)
319	if err != nil {
320		t.Fatalf("FileConn: %v", err)
321	}
322	defer cli.Close()
323
324	cred, err := unix.GetsockoptXucred(fds[1], unix.SOL_LOCAL, unix.LOCAL_PEERCRED)
325	if err == unix.ENOTCONN {
326		t.Skip("GetsockoptXucred not supported with Socketpair on FreeBSD 11 and earlier")
327	} else if err != nil {
328		t.Fatal(err)
329	}
330	t.Logf("got: %+v", cred)
331	if got, want := cred.Uid, os.Getuid(); int(got) != int(want) {
332		t.Errorf("uid = %v; want %v", got, want)
333	}
334}
335
336// stringsFromByteSlice converts a sequence of attributes to a []string.
337// On FreeBSD, each entry consists of a single byte containing the length
338// of the attribute name, followed by the attribute name.
339// The name is _not_ NULL-terminated.
340func stringsFromByteSlice(buf []byte) []string {
341	var result []string
342	i := 0
343	for i < len(buf) {
344		next := i + 1 + int(buf[i])
345		result = append(result, string(buf[i+1:next]))
346		i = next
347	}
348	return result
349}
350