1// +build linux,cgo,seccomp
2
3package integration
4
5import (
6	"strings"
7	"syscall"
8	"testing"
9
10	"github.com/opencontainers/runc/libcontainer"
11	"github.com/opencontainers/runc/libcontainer/configs"
12	libseccomp "github.com/seccomp/libseccomp-golang"
13)
14
15func TestSeccompDenyGetcwd(t *testing.T) {
16	if testing.Short() {
17		return
18	}
19
20	rootfs, err := newRootfs()
21	if err != nil {
22		t.Fatal(err)
23	}
24	defer remove(rootfs)
25
26	config := newTemplateConfig(rootfs)
27	config.Seccomp = &configs.Seccomp{
28		DefaultAction: configs.Allow,
29		Syscalls: []*configs.Syscall{
30			{
31				Name:   "getcwd",
32				Action: configs.Errno,
33			},
34		},
35	}
36
37	container, err := newContainer(config)
38	if err != nil {
39		t.Fatal(err)
40	}
41	defer container.Destroy()
42
43	buffers := newStdBuffers()
44	pwd := &libcontainer.Process{
45		Cwd:    "/",
46		Args:   []string{"pwd"},
47		Env:    standardEnvironment,
48		Stdin:  buffers.Stdin,
49		Stdout: buffers.Stdout,
50		Stderr: buffers.Stderr,
51		Init:   true,
52	}
53
54	err = container.Run(pwd)
55	if err != nil {
56		t.Fatal(err)
57	}
58	ps, err := pwd.Wait()
59	if err == nil {
60		t.Fatal("Expecting error (negative return code); instead exited cleanly!")
61	}
62
63	var exitCode int
64	status := ps.Sys().(syscall.WaitStatus)
65	if status.Exited() {
66		exitCode = status.ExitStatus()
67	} else if status.Signaled() {
68		exitCode = -int(status.Signal())
69	} else {
70		t.Fatalf("Unrecognized exit reason!")
71	}
72
73	if exitCode == 0 {
74		t.Fatalf("Getcwd should fail with negative exit code, instead got %d!", exitCode)
75	}
76
77	expected := "pwd: getcwd: Operation not permitted"
78	actual := strings.Trim(buffers.Stderr.String(), "\n")
79	if actual != expected {
80		t.Fatalf("Expected output %s but got %s\n", expected, actual)
81	}
82}
83
84func TestSeccompPermitWriteConditional(t *testing.T) {
85	if testing.Short() {
86		return
87	}
88
89	rootfs, err := newRootfs()
90	if err != nil {
91		t.Fatal(err)
92	}
93	defer remove(rootfs)
94
95	config := newTemplateConfig(rootfs)
96	config.Seccomp = &configs.Seccomp{
97		DefaultAction: configs.Allow,
98		Syscalls: []*configs.Syscall{
99			{
100				Name:   "write",
101				Action: configs.Errno,
102				Args: []*configs.Arg{
103					{
104						Index: 0,
105						Value: 2,
106						Op:    configs.EqualTo,
107					},
108				},
109			},
110		},
111	}
112
113	container, err := newContainer(config)
114	if err != nil {
115		t.Fatal(err)
116	}
117	defer container.Destroy()
118
119	buffers := newStdBuffers()
120	dmesg := &libcontainer.Process{
121		Cwd:    "/",
122		Args:   []string{"busybox", "ls", "/"},
123		Env:    standardEnvironment,
124		Stdin:  buffers.Stdin,
125		Stdout: buffers.Stdout,
126		Stderr: buffers.Stderr,
127		Init:   true,
128	}
129
130	err = container.Run(dmesg)
131	if err != nil {
132		t.Fatal(err)
133	}
134	if _, err := dmesg.Wait(); err != nil {
135		t.Fatalf("%s: %s", err, buffers.Stderr)
136	}
137}
138
139func TestSeccompDenyWriteConditional(t *testing.T) {
140	if testing.Short() {
141		return
142	}
143
144	// Only test if library version is v2.2.1 or higher
145	// Conditional filtering will always error in v2.2.0 and lower
146	major, minor, micro := libseccomp.GetLibraryVersion()
147	if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) {
148		return
149	}
150
151	rootfs, err := newRootfs()
152	if err != nil {
153		t.Fatal(err)
154	}
155	defer remove(rootfs)
156
157	config := newTemplateConfig(rootfs)
158	config.Seccomp = &configs.Seccomp{
159		DefaultAction: configs.Allow,
160		Syscalls: []*configs.Syscall{
161			{
162				Name:   "write",
163				Action: configs.Errno,
164				Args: []*configs.Arg{
165					{
166						Index: 0,
167						Value: 2,
168						Op:    configs.EqualTo,
169					},
170				},
171			},
172		},
173	}
174
175	container, err := newContainer(config)
176	if err != nil {
177		t.Fatal(err)
178	}
179	defer container.Destroy()
180
181	buffers := newStdBuffers()
182	dmesg := &libcontainer.Process{
183		Cwd:    "/",
184		Args:   []string{"busybox", "ls", "does_not_exist"},
185		Env:    standardEnvironment,
186		Stdin:  buffers.Stdin,
187		Stdout: buffers.Stdout,
188		Stderr: buffers.Stderr,
189		Init:   true,
190	}
191
192	err = container.Run(dmesg)
193	if err != nil {
194		t.Fatal(err)
195	}
196
197	ps, err := dmesg.Wait()
198	if err == nil {
199		t.Fatal("Expecting negative return, instead got 0!")
200	}
201
202	var exitCode int
203	status := ps.Sys().(syscall.WaitStatus)
204	if status.Exited() {
205		exitCode = status.ExitStatus()
206	} else if status.Signaled() {
207		exitCode = -int(status.Signal())
208	} else {
209		t.Fatalf("Unrecognized exit reason!")
210	}
211
212	if exitCode == 0 {
213		t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
214	}
215
216	// We're denying write to stderr, so we expect an empty buffer
217	expected := ""
218	actual := strings.Trim(buffers.Stderr.String(), "\n")
219	if actual != expected {
220		t.Fatalf("Expected output %s but got %s\n", expected, actual)
221	}
222}
223
224func TestSeccompPermitWriteMultipleConditions(t *testing.T) {
225	if testing.Short() {
226		return
227	}
228
229	rootfs, err := newRootfs()
230	if err != nil {
231		t.Fatal(err)
232	}
233	defer remove(rootfs)
234
235	config := newTemplateConfig(rootfs)
236	config.Seccomp = &configs.Seccomp{
237		DefaultAction: configs.Allow,
238		Syscalls: []*configs.Syscall{
239			{
240				Name:   "write",
241				Action: configs.Errno,
242				Args: []*configs.Arg{
243					{
244						Index: 0,
245						Value: 2,
246						Op:    configs.EqualTo,
247					},
248					{
249						Index: 2,
250						Value: 0,
251						Op:    configs.NotEqualTo,
252					},
253				},
254			},
255		},
256	}
257
258	buffers, exitCode, err := runContainer(config, "", "ls", "/")
259	if err != nil {
260		t.Fatalf("%s: %s", buffers, err)
261	}
262	if exitCode != 0 {
263		t.Fatalf("exit code not 0. code %d buffers %s", exitCode, buffers)
264	}
265	// We don't need to verify the actual thing printed
266	// Just that something was written to stdout
267	if len(buffers.Stdout.String()) == 0 {
268		t.Fatalf("Nothing was written to stdout, write call failed!\n")
269	}
270}
271
272func TestSeccompDenyWriteMultipleConditions(t *testing.T) {
273	if testing.Short() {
274		return
275	}
276
277	// Only test if library version is v2.2.1 or higher
278	// Conditional filtering will always error in v2.2.0 and lower
279	major, minor, micro := libseccomp.GetLibraryVersion()
280	if (major == 2 && minor < 2) || (major == 2 && minor == 2 && micro < 1) {
281		return
282	}
283
284	rootfs, err := newRootfs()
285	if err != nil {
286		t.Fatal(err)
287	}
288	defer remove(rootfs)
289
290	config := newTemplateConfig(rootfs)
291	config.Seccomp = &configs.Seccomp{
292		DefaultAction: configs.Allow,
293		Syscalls: []*configs.Syscall{
294			{
295				Name:   "write",
296				Action: configs.Errno,
297				Args: []*configs.Arg{
298					{
299						Index: 0,
300						Value: 2,
301						Op:    configs.EqualTo,
302					},
303					{
304						Index: 2,
305						Value: 0,
306						Op:    configs.NotEqualTo,
307					},
308				},
309			},
310		},
311	}
312
313	buffers, exitCode, err := runContainer(config, "", "ls", "/does_not_exist")
314	if err == nil {
315		t.Fatalf("Expecting error return, instead got 0")
316	}
317	if exitCode == 0 {
318		t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
319	}
320
321	expected := ""
322	actual := strings.Trim(buffers.Stderr.String(), "\n")
323	if actual != expected {
324		t.Fatalf("Expected output %s but got %s\n", expected, actual)
325	}
326}
327
328func TestSeccompMultipleConditionSameArgDeniesStdout(t *testing.T) {
329	if testing.Short() {
330		return
331	}
332
333	rootfs, err := newRootfs()
334	if err != nil {
335		t.Fatal(err)
336	}
337	defer remove(rootfs)
338
339	// Prevent writing to both stdout and stderr
340	config := newTemplateConfig(rootfs)
341	config.Seccomp = &configs.Seccomp{
342		DefaultAction: configs.Allow,
343		Syscalls: []*configs.Syscall{
344			{
345				Name:   "write",
346				Action: configs.Errno,
347				Args: []*configs.Arg{
348					{
349						Index: 0,
350						Value: 1,
351						Op:    configs.EqualTo,
352					},
353					{
354						Index: 0,
355						Value: 2,
356						Op:    configs.EqualTo,
357					},
358				},
359			},
360		},
361	}
362
363	buffers, exitCode, err := runContainer(config, "", "ls", "/")
364	if err != nil {
365		t.Fatalf("%s: %s", buffers, err)
366	}
367	if exitCode != 0 {
368		t.Fatalf("exit code not 0. code %d buffers %s", exitCode, buffers)
369	}
370	// Verify that nothing was printed
371	if len(buffers.Stdout.String()) != 0 {
372		t.Fatalf("Something was written to stdout, write call succeeded!\n")
373	}
374}
375
376func TestSeccompMultipleConditionSameArgDeniesStderr(t *testing.T) {
377	if testing.Short() {
378		return
379	}
380
381	rootfs, err := newRootfs()
382	if err != nil {
383		t.Fatal(err)
384	}
385	defer remove(rootfs)
386
387	// Prevent writing to both stdout and stderr
388	config := newTemplateConfig(rootfs)
389	config.Seccomp = &configs.Seccomp{
390		DefaultAction: configs.Allow,
391		Syscalls: []*configs.Syscall{
392			{
393				Name:   "write",
394				Action: configs.Errno,
395				Args: []*configs.Arg{
396					{
397						Index: 0,
398						Value: 1,
399						Op:    configs.EqualTo,
400					},
401					{
402						Index: 0,
403						Value: 2,
404						Op:    configs.EqualTo,
405					},
406				},
407			},
408		},
409	}
410
411	buffers, exitCode, err := runContainer(config, "", "ls", "/does_not_exist")
412	if err == nil {
413		t.Fatalf("Expecting error return, instead got 0")
414	}
415	if exitCode == 0 {
416		t.Fatalf("Busybox should fail with negative exit code, instead got %d!", exitCode)
417	}
418	// Verify nothing was printed
419	if len(buffers.Stderr.String()) != 0 {
420		t.Fatalf("Something was written to stderr, write call succeeded!\n")
421	}
422}
423