• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
4  * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
5  * Copyright (C) 2024 SUSE LLC Andrea Cervesato <andrea.cervesato@suse.com>
6  */
7 
8 /*\
9  * [Description]
10  *
11  * Test PR_GET_SECCOMP and PR_SET_SECCOMP with both prctl(2) and seccomp(2).
12  * The second one is called via __NR_seccomp using tst_syscall().
13  *
14  * - If PR_SET_SECCOMP sets the SECCOMP_MODE_STRICT for the calling thread,
15  *   the only system call that the thread is permitted to make are read(2),
16  *   write(2),_exit(2)(but not exit_group(2)), and sigreturn(2).  Other
17  *   system calls result in the delivery of a SIGKILL signal. This operation
18  *   is available only if the kernel is configured with CONFIG_SECCOMP enabled.
19  *
20  * - If PR_SET_SECCOMP sets the SECCOMP_MODE_FILTER for the calling thread,
21  *   the system calls allowed are defined by a pointer to a Berkeley Packet
22  *   Filter. Other system calls result int the delivery of a SIGSYS signal
23  *   with SECCOMP_RET_KILL. The SECCOMP_SET_MODE_FILTER operation is available
24  *   only if the kernel is configured with CONFIG_SECCOMP_FILTER enabled.
25  *
26  * - If SECCOMP_MODE_FILTER filters permit fork(2), then the seccomp mode
27  *   is inherited by children created by fork(2).
28  */
29 
30 #include <errno.h>
31 #include <signal.h>
32 #include <sys/wait.h>
33 #include <sys/types.h>
34 #include <linux/filter.h>
35 #include <unistd.h>
36 #include <stdlib.h>
37 #include <stddef.h>
38 #include "tst_test.h"
39 #include "tst_kconfig.h"
40 #include "lapi/syscalls.h"
41 #include "lapi/prctl.h"
42 #include "config.h"
43 #include "lapi/seccomp.h"
44 
45 #define FNAME "filename"
46 
47 static const struct sock_filter  strict_filter[] = {
48 	BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))),
49 
50 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_waitid, 7, 0),
51 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_rt_sigprocmask, 6, 0),
52 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_close, 5, 0),
53 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_exit,  4, 0),
54 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_wait4, 3, 0),
55 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_write, 2, 0),
56 	BPF_JUMP(BPF_JMP | BPF_JEQ, __NR_clone, 1, 0),
57 
58 	BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
59 	BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW)
60 };
61 
62 static const struct sock_fprog  strict = {
63 	.len = (unsigned short)ARRAY_SIZE(strict_filter),
64 	.filter = (struct sock_filter *)strict_filter
65 };
66 
67 static void check_strict_mode(int mode);
68 static void check_filter_mode(int mode);
69 
70 static struct tcase {
71 	void (*func_check)(int mode);
72 	int pass_flag;
73 	int val;
74 	int exp_signal;
75 	char *message;
76 } tcases[] = {
77 	{check_strict_mode, 1, 1, SIGKILL,
78 	"SECCOMP_MODE_STRICT doesn't permit GET_SECCOMP call"},
79 
80 	{check_strict_mode, 0, 2, SIGKILL,
81 	"SECCOMP_MODE_STRICT doesn't permit read(2) write(2) and _exit(2)"},
82 
83 	{check_strict_mode, 1, 3, SIGKILL,
84 	"SECCOMP_MODE_STRICT doesn't permit close(2)"},
85 
86 	{check_filter_mode, 1, 1, SIGSYS,
87 	"SECCOMP_MODE_FILTER doestn't permit GET_SECCOMP call"},
88 
89 	{check_filter_mode, 0, 2, SIGSYS,
90 	"SECCOMP_MODE_FILTER doesn't permit close(2)"},
91 
92 	{check_filter_mode, 2, 3, SIGSYS,
93 	"SECCOMP_MODE_FILTER doesn't permit exit()"},
94 
95 	{check_filter_mode, 0, 4, SIGSYS,
96 	"SECCOMP_MODE_FILTER doesn't permit exit()"}
97 };
98 
99 static int strict_not_supported;
100 static int filter_not_supported;
101 
check_filter_mode_inherit(void)102 static void check_filter_mode_inherit(void)
103 {
104 	int childpid;
105 	int childstatus;
106 
107 	childpid = SAFE_FORK();
108 	if (childpid == 0) {
109 		tst_res(TPASS, "SECCOMP_MODE_FILTER permits fork(2)");
110 		exit(0);
111 	}
112 
113 	wait4(childpid, &childstatus, 0, NULL);
114 	if (WIFSIGNALED(childstatus) && WTERMSIG(childstatus) == SIGSYS)
115 		tst_res(TPASS,
116 			"SECCOMP_MODE_FILTER has been inherited by child");
117 	else
118 		tst_res(TFAIL,
119 			"SECCOMP_MODE_FILTER isn't been inherited by child");
120 }
121 
check_strict_mode(int val)122 static void check_strict_mode(int val)
123 {
124 	int fd;
125 	char buf[2];
126 
127 	if (strict_not_supported)
128 		return;
129 
130 	fd = SAFE_OPEN(FNAME, O_RDWR | O_CREAT, 0666);
131 
132 	if (tst_variant == 1) {
133 		TEST(tst_syscall(__NR_seccomp, SECCOMP_SET_MODE_STRICT, 0, NULL));
134 		if (TST_RET == -1)
135 			tst_brk(TBROK | TERRNO, "seccomp(SECCOMP_SET_MODE_STRICT) error");
136 	} else {
137 		TEST(prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT, 0, NULL));
138 
139 		if (TST_RET == -1)
140 			tst_brk(TBROK | TERRNO, "prctl(SECCOMP_MODE_STRICT) error");
141 	}
142 
143 	switch (val) {
144 	case 1:
145 		tst_res(TPASS,
146 			"prctl(PR_SET_SECCOMP) sets SECCOMP_MODE_STRICT succeed");
147 		prctl(PR_GET_SECCOMP);
148 		tst_res(TFAIL, "prctl(PR_GET_SECCOMP) succeed unexpectedly");
149 	break;
150 	case 2:
151 		SAFE_WRITE(SAFE_WRITE_ALL, fd, "a", 1);
152 		SAFE_READ(0, fd, buf, 1);
153 		tst_res(TPASS,
154 			"SECCOMP_MODE_STRICT permits read(2) write(2) and _exit(2)");
155 	break;
156 	case 3:
157 		close(fd);
158 		tst_res(TFAIL,
159 			"SECCOMP_MODE_STRICT permits close(2) unexpectdly");
160 	break;
161 	}
162 
163 	tst_syscall(__NR_exit, 0);
164 }
165 
check_filter_mode(int val)166 static void check_filter_mode(int val)
167 {
168 	int fd;
169 
170 	if (filter_not_supported)
171 		return;
172 
173 	fd = SAFE_OPEN(FNAME, O_RDWR | O_CREAT, 0666);
174 
175 	if (tst_variant == 1) {
176 		TEST(tst_syscall(__NR_seccomp, SECCOMP_SET_MODE_FILTER, 0, &strict));
177 		if (TST_RET == -1)
178 			tst_brk(TBROK | TERRNO, "seccomp(SECCOMP_SET_MODE_FILTER) error");
179 	} else {
180 		TEST(prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &strict));
181 
182 		if (TST_RET == -1)
183 			tst_brk(TBROK | TERRNO, "prctl(SECCOMP_MODE_FILTER) error");
184 	}
185 
186 	switch (val) {
187 	case 1:
188 		tst_res(TPASS,
189 			"prctl(PR_SET_SECCOMP) sets SECCOMP_MODE_FILTER succeed");
190 		prctl(PR_GET_SECCOMP);
191 		tst_res(TFAIL, "prctl(PR_GET_SECCOMP) succeed unexpectedly");
192 	break;
193 	case 2:
194 		close(fd);
195 		tst_res(TPASS, "SECCOMP_MODE_FILTER permits close(2)");
196 	break;
197 	case 3:
198 		exit(0);
199 	break;
200 	case 4:
201 		check_filter_mode_inherit();
202 	break;
203 	}
204 
205 	tst_syscall(__NR_exit, 0);
206 }
207 
verify_prctl(unsigned int n)208 static void verify_prctl(unsigned int n)
209 {
210 	int pid;
211 	int status;
212 	struct tcase *tc = &tcases[n];
213 
214 	pid = SAFE_FORK();
215 	if (pid == 0) {
216 		tc->func_check(tc->val);
217 	} else {
218 		SAFE_WAITPID(pid, &status, 0);
219 		if (WIFSIGNALED(status) && WTERMSIG(status) == tc->exp_signal) {
220 			if (tc->pass_flag)
221 				tst_res(TPASS, "%s", tc->message);
222 			else
223 				tst_res(TFAIL, "%s", tc->message);
224 			return;
225 		}
226 
227 		if (tc->pass_flag == 2)
228 			tst_res(TFAIL, "SECCOMP_MODE_FILTER permits exit() unexpectedly");
229 	}
230 }
231 
setup(void)232 static void setup(void)
233 {
234 	static const char * const kconf_strict[] = {"CONFIG_SECCOMP=y", NULL};
235 	static const char * const kconf_filter[] = {"CONFIG_SECCOMP_FILTER=y", NULL};
236 
237 	tst_res(TINFO, "Testing variant: %s",
238 		tst_variant == 1 ? "seccomp()" : "pctrl(PR_SET_SECCOMP)");
239 
240 	if (tst_kconfig_check(kconf_strict)) {
241 		tst_brk(TCONF, "kernel doesn't support SECCOMP_MODE_STRICT. "
242 				"Skipping CONFIG_SECCOMP tests");
243 
244 		strict_not_supported = 1;
245 	} else {
246 		tst_res(TINFO, "kernel supports SECCOMP_MODE_STRICT");
247 	}
248 
249 	if (tst_kconfig_check(kconf_filter)) {
250 		tst_brk(TCONF, "kernel doesn't support SECCOMP_MODE_FILTER. "
251 				"Skipping CONFIG_SECCOMP_FILTER tests");
252 
253 		filter_not_supported = 1;
254 	} else {
255 		tst_res(TINFO, "kernel supports SECCOMP_MODE_FILTER");
256 	}
257 }
258 
259 static struct tst_test test = {
260 	.setup = setup,
261 	.test = verify_prctl,
262 	.tcnt = ARRAY_SIZE(tcases),
263 	.test_variants = 2,
264 	.forks_child = 1,
265 	.needs_tmpdir = 1,
266 	.needs_root = 1,
267 };
268