]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/test/test-execute.c
test-execute: test more % specifiers (#7450)
[thirdparty/systemd.git] / src / test / test-execute.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Ronny Chevalier
6
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
11
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
16
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
19 ***/
20
21 #include <grp.h>
22 #include <pwd.h>
23 #include <stdio.h>
24 #include <sys/prctl.h>
25 #include <sys/types.h>
26
27 #include "errno-list.h"
28 #include "fileio.h"
29 #include "fs-util.h"
30 #include "macro.h"
31 #include "manager.h"
32 #include "mkdir.h"
33 #include "path-util.h"
34 #include "rm-rf.h"
35 #if HAVE_SECCOMP
36 #include "seccomp-util.h"
37 #endif
38 #include "stat-util.h"
39 #include "test-helper.h"
40 #include "tests.h"
41 #include "unit.h"
42 #include "util.h"
43 #include "virt.h"
44
45 typedef void (*test_function_t)(Manager *m);
46
47 static void check(Manager *m, Unit *unit, int status_expected, int code_expected) {
48 Service *service = NULL;
49 usec_t ts;
50 usec_t timeout = 2 * USEC_PER_MINUTE;
51
52 assert_se(m);
53 assert_se(unit);
54
55 service = SERVICE(unit);
56 printf("%s\n", unit->id);
57 exec_context_dump(&service->exec_context, stdout, "\t");
58 ts = now(CLOCK_MONOTONIC);
59 while (!IN_SET(service->state, SERVICE_DEAD, SERVICE_FAILED)) {
60 int r;
61 usec_t n;
62
63 r = sd_event_run(m->event, 100 * USEC_PER_MSEC);
64 assert_se(r >= 0);
65
66 n = now(CLOCK_MONOTONIC);
67 if (ts + timeout < n) {
68 log_error("Test timeout when testing %s", unit->id);
69 exit(EXIT_FAILURE);
70 }
71 }
72 exec_status_dump(&service->main_exec_status, stdout, "\t");
73 assert_se(service->main_exec_status.status == status_expected);
74 assert_se(service->main_exec_status.code == code_expected);
75 }
76
77 static bool is_inaccessible_available(void) {
78 char *p;
79
80 FOREACH_STRING(p,
81 "/run/systemd/inaccessible/reg",
82 "/run/systemd/inaccessible/dir",
83 "/run/systemd/inaccessible/chr",
84 "/run/systemd/inaccessible/blk",
85 "/run/systemd/inaccessible/fifo",
86 "/run/systemd/inaccessible/sock"
87 ) {
88 if (access(p, F_OK) < 0)
89 return false;
90 }
91
92 return true;
93 }
94
95 static void test(Manager *m, const char *unit_name, int status_expected, int code_expected) {
96 Unit *unit;
97
98 assert_se(unit_name);
99
100 assert_se(manager_load_unit(m, unit_name, NULL, NULL, &unit) >= 0);
101 assert_se(UNIT_VTABLE(unit)->start(unit) >= 0);
102 check(m, unit, status_expected, code_expected);
103 }
104
105 static void test_exec_bind_paths(Manager *m) {
106 assert_se(mkdir_p("/tmp/test-exec_bind_paths", 0755) >= 0);
107 assert_se(mkdir_p("/tmp/test-exec_bind_readonly_paths", 0755) >= 0);
108
109 test(m, "exec-bind-paths.service", 0, CLD_EXITED);
110
111 (void) rm_rf("/tmp/test-exec_bind_paths", REMOVE_ROOT|REMOVE_PHYSICAL);
112 (void) rm_rf("/tmp/test-exec_bind_readonly_paths", REMOVE_ROOT|REMOVE_PHYSICAL);
113 }
114
115 static void test_exec_workingdirectory(Manager *m) {
116 assert_se(mkdir_p("/tmp/test-exec_workingdirectory", 0755) >= 0);
117
118 test(m, "exec-workingdirectory.service", 0, CLD_EXITED);
119
120 (void) rm_rf("/tmp/test-exec_workingdirectory", REMOVE_ROOT|REMOVE_PHYSICAL);
121 }
122
123 static void test_exec_personality(Manager *m) {
124 #if defined(__x86_64__)
125 test(m, "exec-personality-x86-64.service", 0, CLD_EXITED);
126
127 #elif defined(__s390__)
128 test(m, "exec-personality-s390.service", 0, CLD_EXITED);
129
130 #elif defined(__powerpc64__)
131 # if __BYTE_ORDER == __BIG_ENDIAN
132 test(m, "exec-personality-ppc64.service", 0, CLD_EXITED);
133 # else
134 test(m, "exec-personality-ppc64le.service", 0, CLD_EXITED);
135 # endif
136
137 #elif defined(__aarch64__)
138 test(m, "exec-personality-aarch64.service", 0, CLD_EXITED);
139
140 #elif defined(__i386__)
141 test(m, "exec-personality-x86.service", 0, CLD_EXITED);
142 #endif
143 }
144
145 static void test_exec_ignoresigpipe(Manager *m) {
146 test(m, "exec-ignoresigpipe-yes.service", 0, CLD_EXITED);
147 test(m, "exec-ignoresigpipe-no.service", SIGPIPE, CLD_KILLED);
148 }
149
150 static void test_exec_privatetmp(Manager *m) {
151 assert_se(touch("/tmp/test-exec_privatetmp") >= 0);
152
153 test(m, "exec-privatetmp-yes.service", 0, CLD_EXITED);
154 test(m, "exec-privatetmp-no.service", 0, CLD_EXITED);
155
156 unlink("/tmp/test-exec_privatetmp");
157 }
158
159 static void test_exec_privatedevices(Manager *m) {
160 if (detect_container() > 0) {
161 log_notice("testing in container, skipping %s", __func__);
162 return;
163 }
164 if (!is_inaccessible_available()) {
165 log_notice("testing without inaccessible, skipping %s", __func__);
166 return;
167 }
168
169 test(m, "exec-privatedevices-yes.service", 0, CLD_EXITED);
170 test(m, "exec-privatedevices-no.service", 0, CLD_EXITED);
171 }
172
173 static void test_exec_privatedevices_capabilities(Manager *m) {
174 int r;
175
176 if (detect_container() > 0) {
177 log_notice("testing in container, skipping %s", __func__);
178 return;
179 }
180 if (!is_inaccessible_available()) {
181 log_notice("testing without inaccessible, skipping %s", __func__);
182 return;
183 }
184
185 /* We use capsh to test if the capabilities are
186 * properly set, so be sure that it exists */
187 r = find_binary("capsh", NULL);
188 if (r < 0) {
189 log_error_errno(r, "Skipping %s, could not find capsh binary: %m", __func__);
190 return;
191 }
192
193 test(m, "exec-privatedevices-yes-capability-mknod.service", 0, CLD_EXITED);
194 test(m, "exec-privatedevices-no-capability-mknod.service", 0, CLD_EXITED);
195 test(m, "exec-privatedevices-yes-capability-sys-rawio.service", 0, CLD_EXITED);
196 test(m, "exec-privatedevices-no-capability-sys-rawio.service", 0, CLD_EXITED);
197 }
198
199 static void test_exec_protectkernelmodules(Manager *m) {
200 int r;
201
202 if (detect_container() > 0) {
203 log_notice("testing in container, skipping %s", __func__);
204 return;
205 }
206 if (!is_inaccessible_available()) {
207 log_notice("testing without inaccessible, skipping %s", __func__);
208 return;
209 }
210
211 r = find_binary("capsh", NULL);
212 if (r < 0) {
213 log_error_errno(r, "Skipping %s, could not find capsh binary: %m", __func__);
214 return;
215 }
216
217
218 test(m, "exec-protectkernelmodules-no-capabilities.service", 0, CLD_EXITED);
219 test(m, "exec-protectkernelmodules-yes-capabilities.service", 0, CLD_EXITED);
220 test(m, "exec-protectkernelmodules-yes-mount-propagation.service", 0, CLD_EXITED);
221 }
222
223 static void test_exec_readonlypaths(Manager *m) {
224
225 if (path_is_read_only_fs("/var") > 0)
226 return;
227
228 test(m, "exec-readonlypaths.service", 0, CLD_EXITED);
229 test(m, "exec-readonlypaths-mount-propagation.service", 0, CLD_EXITED);
230 test(m, "exec-readonlypaths-with-bindpaths.service", 0, CLD_EXITED);
231 }
232
233 static void test_exec_readwritepaths(Manager *m) {
234
235 if (path_is_read_only_fs("/") > 0)
236 return;
237
238 test(m, "exec-readwritepaths-mount-propagation.service", 0, CLD_EXITED);
239 }
240
241 static void test_exec_inaccessiblepaths(Manager *m) {
242
243 if (path_is_read_only_fs("/") > 0)
244 return;
245
246 test(m, "exec-inaccessiblepaths-mount-propagation.service", 0, CLD_EXITED);
247 }
248
249 static void test_exec_inaccessiblepaths_proc(Manager *m) {
250 if (!is_inaccessible_available()) {
251 log_notice("testing without inaccessible, skipping %s", __func__);
252 return;
253 }
254
255 test(m, "exec-inaccessiblepaths-proc.service", 0, CLD_EXITED);
256 }
257
258 static void test_exec_systemcallfilter(Manager *m) {
259 #if HAVE_SECCOMP
260 if (!is_seccomp_available())
261 return;
262 test(m, "exec-systemcallfilter-not-failing.service", 0, CLD_EXITED);
263 test(m, "exec-systemcallfilter-not-failing2.service", 0, CLD_EXITED);
264 test(m, "exec-systemcallfilter-failing.service", SIGSYS, CLD_KILLED);
265 test(m, "exec-systemcallfilter-failing2.service", SIGSYS, CLD_KILLED);
266 test(m, "exec-systemcallfilter-with-errno-name.service", errno_from_name("EILSEQ"), CLD_EXITED);
267 test(m, "exec-systemcallfilter-with-errno-number.service", 255, CLD_EXITED);
268
269 #endif
270 }
271
272 static void test_exec_systemcallerrornumber(Manager *m) {
273 #if HAVE_SECCOMP
274 if (!is_seccomp_available())
275 return;
276 test(m, "exec-systemcallerrornumber-name.service", errno_from_name("EACCES"), CLD_EXITED);
277 test(m, "exec-systemcallerrornumber-number.service", 255, CLD_EXITED);
278 #endif
279 }
280
281 static void test_exec_restrict_namespaces(Manager *m) {
282 #if HAVE_SECCOMP
283 if (!is_seccomp_available())
284 return;
285
286 test(m, "exec-restrict-namespaces-no.service", 0, CLD_EXITED);
287 test(m, "exec-restrict-namespaces-yes.service", 1, CLD_EXITED);
288 test(m, "exec-restrict-namespaces-mnt.service", 0, CLD_EXITED);
289 test(m, "exec-restrict-namespaces-mnt-blacklist.service", 1, CLD_EXITED);
290 #endif
291 }
292
293 static void test_exec_systemcall_system_mode_with_user(Manager *m) {
294 #if HAVE_SECCOMP
295 if (!is_seccomp_available())
296 return;
297 if (getpwnam("nobody"))
298 test(m, "exec-systemcallfilter-system-user.service", 0, CLD_EXITED);
299 else if (getpwnam("nfsnobody"))
300 test(m, "exec-systemcallfilter-system-user-nfsnobody.service", 0, CLD_EXITED);
301 else
302 log_error_errno(errno, "Skipping %s, could not find nobody/nfsnobody user: %m", __func__);
303 #endif
304 }
305
306 static void test_exec_user(Manager *m) {
307 if (getpwnam("nobody"))
308 test(m, "exec-user.service", 0, CLD_EXITED);
309 else if (getpwnam("nfsnobody"))
310 test(m, "exec-user-nfsnobody.service", 0, CLD_EXITED);
311 else
312 log_error_errno(errno, "Skipping %s, could not find nobody/nfsnobody user: %m", __func__);
313 }
314
315 static void test_exec_group(Manager *m) {
316 if (getgrnam("nobody"))
317 test(m, "exec-group.service", 0, CLD_EXITED);
318 else if (getgrnam("nfsnobody"))
319 test(m, "exec-group-nfsnobody.service", 0, CLD_EXITED);
320 else
321 log_error_errno(errno, "Skipping %s, could not find nobody/nfsnobody group: %m", __func__);
322 }
323
324 static void test_exec_supplementary_groups(Manager *m) {
325 test(m, "exec-supplementarygroups.service", 0, CLD_EXITED);
326 test(m, "exec-supplementarygroups-single-group.service", 0, CLD_EXITED);
327 test(m, "exec-supplementarygroups-single-group-user.service", 0, CLD_EXITED);
328 test(m, "exec-supplementarygroups-multiple-groups-default-group-user.service", 0, CLD_EXITED);
329 test(m, "exec-supplementarygroups-multiple-groups-withgid.service", 0, CLD_EXITED);
330 test(m, "exec-supplementarygroups-multiple-groups-withuid.service", 0, CLD_EXITED);
331 }
332
333 static void test_exec_dynamic_user(Manager *m) {
334 test(m, "exec-dynamicuser-fixeduser.service", 0, CLD_EXITED);
335 test(m, "exec-dynamicuser-fixeduser-one-supplementarygroup.service", 0, CLD_EXITED);
336 test(m, "exec-dynamicuser-supplementarygroups.service", 0, CLD_EXITED);
337 test(m, "exec-dynamicuser-state-dir.service", 0, CLD_EXITED);
338 }
339
340 static void test_exec_environment(Manager *m) {
341 test(m, "exec-environment.service", 0, CLD_EXITED);
342 test(m, "exec-environment-multiple.service", 0, CLD_EXITED);
343 test(m, "exec-environment-empty.service", 0, CLD_EXITED);
344 }
345
346 static void test_exec_environmentfile(Manager *m) {
347 static const char e[] =
348 "VAR1='word1 word2'\n"
349 "VAR2=word3 \n"
350 "# comment1\n"
351 "\n"
352 "; comment2\n"
353 " ; # comment3\n"
354 "line without an equal\n"
355 "VAR3='$word 5 6'\n";
356 int r;
357
358 r = write_string_file("/tmp/test-exec_environmentfile.conf", e, WRITE_STRING_FILE_CREATE);
359 assert_se(r == 0);
360
361 test(m, "exec-environmentfile.service", 0, CLD_EXITED);
362
363 unlink("/tmp/test-exec_environmentfile.conf");
364 }
365
366 static void test_exec_passenvironment(Manager *m) {
367 /* test-execute runs under MANAGER_USER which, by default, forwards all
368 * variables present in the environment, but only those that are
369 * present _at the time it is created_!
370 *
371 * So these PassEnvironment checks are still expected to work, since we
372 * are ensuring the variables are not present at manager creation (they
373 * are unset explicitly in main) and are only set here.
374 *
375 * This is still a good approximation of how a test for MANAGER_SYSTEM
376 * would work.
377 */
378 assert_se(setenv("VAR1", "word1 word2", 1) == 0);
379 assert_se(setenv("VAR2", "word3", 1) == 0);
380 assert_se(setenv("VAR3", "$word 5 6", 1) == 0);
381 test(m, "exec-passenvironment.service", 0, CLD_EXITED);
382 test(m, "exec-passenvironment-repeated.service", 0, CLD_EXITED);
383 test(m, "exec-passenvironment-empty.service", 0, CLD_EXITED);
384 assert_se(unsetenv("VAR1") == 0);
385 assert_se(unsetenv("VAR2") == 0);
386 assert_se(unsetenv("VAR3") == 0);
387 test(m, "exec-passenvironment-absent.service", 0, CLD_EXITED);
388 }
389
390 static void test_exec_umask(Manager *m) {
391 test(m, "exec-umask-default.service", 0, CLD_EXITED);
392 test(m, "exec-umask-0177.service", 0, CLD_EXITED);
393 }
394
395 static void test_exec_runtimedirectory(Manager *m) {
396 test(m, "exec-runtimedirectory.service", 0, CLD_EXITED);
397 test(m, "exec-runtimedirectory-mode.service", 0, CLD_EXITED);
398 if (getgrnam("nobody"))
399 test(m, "exec-runtimedirectory-owner.service", 0, CLD_EXITED);
400 else if (getgrnam("nfsnobody"))
401 test(m, "exec-runtimedirectory-owner-nfsnobody.service", 0, CLD_EXITED);
402 else
403 log_error_errno(errno, "Skipping %s, could not find nobody/nfsnobody group: %m", __func__);
404 }
405
406 static void test_exec_capabilityboundingset(Manager *m) {
407 int r;
408
409 r = find_binary("capsh", NULL);
410 if (r < 0) {
411 log_error_errno(r, "Skipping %s, could not find capsh binary: %m", __func__);
412 return;
413 }
414
415 test(m, "exec-capabilityboundingset-simple.service", 0, CLD_EXITED);
416 test(m, "exec-capabilityboundingset-reset.service", 0, CLD_EXITED);
417 test(m, "exec-capabilityboundingset-merge.service", 0, CLD_EXITED);
418 test(m, "exec-capabilityboundingset-invert.service", 0, CLD_EXITED);
419 }
420
421 static void test_exec_capabilityambientset(Manager *m) {
422 int r;
423
424 /* Check if the kernel has support for ambient capabilities. Run
425 * the tests only if that's the case. Clearing all ambient
426 * capabilities is fine, since we are expecting them to be unset
427 * in the first place for the tests. */
428 r = prctl(PR_CAP_AMBIENT, PR_CAP_AMBIENT_CLEAR_ALL, 0, 0, 0);
429 if (r >= 0 || errno != EINVAL) {
430 if (getpwnam("nobody")) {
431 test(m, "exec-capabilityambientset.service", 0, CLD_EXITED);
432 test(m, "exec-capabilityambientset-merge.service", 0, CLD_EXITED);
433 } else if (getpwnam("nfsnobody")) {
434 test(m, "exec-capabilityambientset-nfsnobody.service", 0, CLD_EXITED);
435 test(m, "exec-capabilityambientset-merge-nfsnobody.service", 0, CLD_EXITED);
436 } else
437 log_error_errno(errno, "Skipping %s, could not find nobody/nfsnobody user: %m", __func__);
438 } else
439 log_error_errno(errno, "Skipping %s, the kernel does not support ambient capabilities: %m", __func__);
440 }
441
442 static void test_exec_privatenetwork(Manager *m) {
443 int r;
444
445 r = find_binary("ip", NULL);
446 if (r < 0) {
447 log_error_errno(r, "Skipping %s, could not find ip binary: %m", __func__);
448 return;
449 }
450
451 test(m, "exec-privatenetwork-yes.service", 0, CLD_EXITED);
452 }
453
454 static void test_exec_oomscoreadjust(Manager *m) {
455 test(m, "exec-oomscoreadjust-positive.service", 0, CLD_EXITED);
456 test(m, "exec-oomscoreadjust-negative.service", 0, CLD_EXITED);
457 }
458
459 static void test_exec_ioschedulingclass(Manager *m) {
460 test(m, "exec-ioschedulingclass-none.service", 0, CLD_EXITED);
461 test(m, "exec-ioschedulingclass-idle.service", 0, CLD_EXITED);
462 test(m, "exec-ioschedulingclass-realtime.service", 0, CLD_EXITED);
463 test(m, "exec-ioschedulingclass-best-effort.service", 0, CLD_EXITED);
464 }
465
466 static void test_exec_spec_interpolation(Manager *m) {
467 test(m, "exec-spec-interpolation.service", 0, CLD_EXITED);
468 }
469
470 static void test_exec_read_only_path_suceed(Manager *m) {
471 test(m, "exec-read-only-path-succeed.service", 0, CLD_EXITED);
472 }
473
474 static void test_exec_unset_environment(Manager *m) {
475 test(m, "exec-unset-environment.service", 0, CLD_EXITED);
476 }
477
478 static void test_exec_specifier(Manager *m) {
479 test(m, "exec-specifier.service", 0, CLD_EXITED);
480 test(m, "exec-specifier@foo-bar.service", 0, CLD_EXITED);
481 }
482
483 static void test_exec_stdin_data(Manager *m) {
484 test(m, "exec-stdin-data.service", 0, CLD_EXITED);
485 }
486
487 static void test_exec_stdio_file(Manager *m) {
488 test(m, "exec-stdio-file.service", 0, CLD_EXITED);
489 }
490
491 static int run_tests(UnitFileScope scope, const test_function_t *tests) {
492 const test_function_t *test = NULL;
493 Manager *m = NULL;
494 int r;
495
496 assert_se(tests);
497
498 r = manager_new(scope, MANAGER_TEST_RUN_MINIMAL, &m);
499 if (MANAGER_SKIP_TEST(r)) {
500 log_notice_errno(r, "Skipping test: manager_new: %m");
501 return EXIT_TEST_SKIP;
502 }
503 assert_se(r >= 0);
504 assert_se(manager_startup(m, NULL, NULL) >= 0);
505
506 for (test = tests; test && *test; test++)
507 (*test)(m);
508
509 manager_free(m);
510
511 return 0;
512 }
513
514 int main(int argc, char *argv[]) {
515 static const test_function_t user_tests[] = {
516 test_exec_bind_paths,
517 test_exec_workingdirectory,
518 test_exec_personality,
519 test_exec_ignoresigpipe,
520 test_exec_privatetmp,
521 test_exec_privatedevices,
522 test_exec_privatedevices_capabilities,
523 test_exec_protectkernelmodules,
524 test_exec_readonlypaths,
525 test_exec_readwritepaths,
526 test_exec_inaccessiblepaths,
527 test_exec_inaccessiblepaths_proc,
528 test_exec_privatenetwork,
529 test_exec_systemcallfilter,
530 test_exec_systemcallerrornumber,
531 test_exec_restrict_namespaces,
532 test_exec_user,
533 test_exec_group,
534 test_exec_supplementary_groups,
535 test_exec_environment,
536 test_exec_environmentfile,
537 test_exec_passenvironment,
538 test_exec_umask,
539 test_exec_runtimedirectory,
540 test_exec_capabilityboundingset,
541 test_exec_capabilityambientset,
542 test_exec_oomscoreadjust,
543 test_exec_ioschedulingclass,
544 test_exec_spec_interpolation,
545 test_exec_read_only_path_suceed,
546 test_exec_unset_environment,
547 test_exec_stdin_data,
548 test_exec_stdio_file,
549 NULL,
550 };
551 static const test_function_t system_tests[] = {
552 test_exec_systemcall_system_mode_with_user,
553 test_exec_dynamic_user,
554 test_exec_specifier,
555 NULL,
556 };
557 int r;
558
559 log_set_max_level(LOG_DEBUG);
560 log_parse_environment();
561 log_open();
562
563 /* It is needed otherwise cgroup creation fails */
564 if (getuid() != 0) {
565 puts("Skipping test: not root");
566 return EXIT_TEST_SKIP;
567 }
568
569 r = enter_cgroup_subroot();
570 if (r == -ENOMEDIUM) {
571 puts("Skipping test: cgroupfs not available");
572 return EXIT_TEST_SKIP;
573 }
574
575 assert_se(setenv("XDG_RUNTIME_DIR", "/tmp/", 1) == 0);
576 assert_se(set_unit_path(get_testdata_dir("/test-execute")) >= 0);
577
578 /* Unset VAR1, VAR2 and VAR3 which are used in the PassEnvironment test
579 * cases, otherwise (and if they are present in the environment),
580 * `manager_default_environment` will copy them into the default
581 * environment which is passed to each created job, which will make the
582 * tests that expect those not to be present to fail.
583 */
584 assert_se(unsetenv("VAR1") == 0);
585 assert_se(unsetenv("VAR2") == 0);
586 assert_se(unsetenv("VAR3") == 0);
587
588 r = run_tests(UNIT_FILE_USER, user_tests);
589 if (r != 0)
590 return r;
591
592 return run_tests(UNIT_FILE_SYSTEM, system_tests);
593 }