xref: /qemu/tests/unit/test-seccomp.c (revision b49f4755)
1 /*
2  * QEMU seccomp test suite
3  *
4  * Copyright (c) 2021 Red Hat, Inc.
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Lesser General Public
8  * License as published by the Free Software Foundation; either
9  * version 2.1 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Lesser General Public License for more details.
15  *
16  * You should have received a copy of the GNU Lesser General Public
17  * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18  *
19  */
20 
21 #include "qemu/osdep.h"
22 #include "qemu/config-file.h"
23 #include "qemu/option.h"
24 #include "sysemu/seccomp.h"
25 #include "qapi/error.h"
26 #include "qemu/module.h"
27 
28 #include <sys/syscall.h>
29 
30 static void test_seccomp_helper(const char *args, bool killed,
31                                 int errnum, int (*doit)(void))
32 {
33     if (g_test_subprocess()) {
34         QemuOptsList *olist;
35         QemuOpts *opts;
36         int ret;
37 
38         module_call_init(MODULE_INIT_OPTS);
39         olist = qemu_find_opts("sandbox");
40         g_assert(olist != NULL);
41 
42         opts = qemu_opts_parse_noisily(olist, args, true);
43         g_assert(opts != NULL);
44 
45         parse_sandbox(NULL, opts, &error_abort);
46 
47         /* Running in a child process */
48         ret = doit();
49 
50         if (errnum != 0) {
51             g_assert(ret != 0);
52             g_assert(errno == errnum);
53         } else {
54             g_assert(ret == 0);
55         }
56 
57         _exit(0);
58     } else {
59         /* Running in main test process, spawning the child */
60         g_test_trap_subprocess(NULL, 0, 0);
61         if (killed) {
62             g_test_trap_assert_failed();
63         } else {
64             g_test_trap_assert_passed();
65         }
66     }
67 }
68 
69 
70 static void test_seccomp_killed(const char *args, int (*doit)(void))
71 {
72     test_seccomp_helper(args, true, 0, doit);
73 }
74 
75 static void test_seccomp_errno(const char *args, int errnum, int (*doit)(void))
76 {
77     test_seccomp_helper(args, false, errnum, doit);
78 }
79 
80 static void test_seccomp_passed(const char *args, int (*doit)(void))
81 {
82     test_seccomp_helper(args, false, 0, doit);
83 }
84 
85 #ifdef SYS_fork
86 static int doit_sys_fork(void)
87 {
88     int ret = syscall(SYS_fork);
89     if (ret < 0) {
90         return ret;
91     }
92     if (ret == 0) {
93         _exit(0);
94     }
95     return 0;
96 }
97 
98 static void test_seccomp_sys_fork_on_nospawn(void)
99 {
100     test_seccomp_killed("on,spawn=deny", doit_sys_fork);
101 }
102 
103 static void test_seccomp_sys_fork_on(void)
104 {
105     test_seccomp_passed("on", doit_sys_fork);
106 }
107 
108 static void test_seccomp_sys_fork_off(void)
109 {
110     test_seccomp_passed("off", doit_sys_fork);
111 }
112 #endif
113 
114 static int doit_fork(void)
115 {
116     int ret = fork();
117     if (ret < 0) {
118         return ret;
119     }
120     if (ret == 0) {
121         _exit(0);
122     }
123     return 0;
124 }
125 
126 static void test_seccomp_fork_on_nospawn(void)
127 {
128     test_seccomp_killed("on,spawn=deny", doit_fork);
129 }
130 
131 static void test_seccomp_fork_on(void)
132 {
133     test_seccomp_passed("on", doit_fork);
134 }
135 
136 static void test_seccomp_fork_off(void)
137 {
138     test_seccomp_passed("off", doit_fork);
139 }
140 
141 static void *noop(void *arg)
142 {
143     return arg;
144 }
145 
146 static int doit_thread(void)
147 {
148     pthread_t th;
149     int ret = pthread_create(&th, NULL, noop, NULL);
150     if (ret != 0) {
151         errno = ret;
152         return -1;
153     } else {
154         pthread_join(th, NULL);
155         return 0;
156     }
157 }
158 
159 static void test_seccomp_thread_on(void)
160 {
161     test_seccomp_passed("on", doit_thread);
162 }
163 
164 static void test_seccomp_thread_on_nospawn(void)
165 {
166     test_seccomp_passed("on,spawn=deny", doit_thread);
167 }
168 
169 static void test_seccomp_thread_off(void)
170 {
171     test_seccomp_passed("off", doit_thread);
172 }
173 
174 static int doit_sched(void)
175 {
176     struct sched_param param = { .sched_priority = 0 };
177     return sched_setscheduler(getpid(), SCHED_OTHER, &param);
178 }
179 
180 static void test_seccomp_sched_on_nores(void)
181 {
182     test_seccomp_errno("on,resourcecontrol=deny", EPERM, doit_sched);
183 }
184 
185 static void test_seccomp_sched_on(void)
186 {
187     test_seccomp_passed("on", doit_sched);
188 }
189 
190 static void test_seccomp_sched_off(void)
191 {
192     test_seccomp_passed("off", doit_sched);
193 }
194 
195 static bool can_play_with_seccomp(void)
196 {
197     g_autofree char *status = NULL;
198     g_auto(GStrv) lines = NULL;
199     size_t i;
200 
201     if (!g_file_get_contents("/proc/self/status", &status, NULL, NULL)) {
202         return false;
203     }
204 
205     lines = g_strsplit(status, "\n", 0);
206 
207     for (i = 0; lines[i] != NULL; i++) {
208         if (g_str_has_prefix(lines[i], "Seccomp:")) {
209             /*
210              * "Seccomp: 1" or "Seccomp: 2" indicate we're already
211              * confined, probably as we're inside a container. In
212              * this case our tests might get unexpected results,
213              * so we can't run reliably
214              */
215             if (!strchr(lines[i], '0')) {
216                 return false;
217             }
218 
219             return true;
220         }
221     }
222 
223     /* Doesn't look like seccomp is enabled in the kernel */
224     return false;
225 }
226 
227 int main(int argc, char **argv)
228 {
229     g_test_init(&argc, &argv, NULL);
230     if (can_play_with_seccomp()) {
231 #ifdef SYS_fork
232         g_test_add_func("/seccomp/sys-fork/on",
233                         test_seccomp_sys_fork_on);
234         g_test_add_func("/seccomp/sys-fork/on-nospawn",
235                         test_seccomp_sys_fork_on_nospawn);
236         g_test_add_func("/seccomp/sys-fork/off",
237                         test_seccomp_sys_fork_off);
238 #endif
239 
240         g_test_add_func("/seccomp/fork/on",
241                         test_seccomp_fork_on);
242         g_test_add_func("/seccomp/fork/on-nospawn",
243                         test_seccomp_fork_on_nospawn);
244         g_test_add_func("/seccomp/fork/off",
245                         test_seccomp_fork_off);
246 
247         g_test_add_func("/seccomp/thread/on",
248                         test_seccomp_thread_on);
249         g_test_add_func("/seccomp/thread/on-nospawn",
250                         test_seccomp_thread_on_nospawn);
251         g_test_add_func("/seccomp/thread/off",
252                         test_seccomp_thread_off);
253 
254         if (doit_sched() == 0) {
255             /*
256              * musl doesn't impl sched_setscheduler, hence
257              * we check above if it works first
258              */
259             g_test_add_func("/seccomp/sched/on",
260                             test_seccomp_sched_on);
261             g_test_add_func("/seccomp/sched/on-nores",
262                             test_seccomp_sched_on_nores);
263             g_test_add_func("/seccomp/sched/off",
264                             test_seccomp_sched_off);
265         }
266     }
267     return g_test_run();
268 }
269