Branch data Line data Source code
1 : : /* SPDX-License-Identifier: LGPL-2.1+ */
2 : :
3 : : #include <sched.h>
4 : : #include <sys/utsname.h>
5 : :
6 : : #include "analyze-security.h"
7 : : #include "bus-error.h"
8 : : #include "bus-unit-util.h"
9 : : #include "bus-util.h"
10 : : #include "env-util.h"
11 : : #include "format-table.h"
12 : : #include "in-addr-util.h"
13 : : #include "locale-util.h"
14 : : #include "macro.h"
15 : : #include "missing.h"
16 : : #include "nulstr-util.h"
17 : : #include "parse-util.h"
18 : : #include "path-util.h"
19 : : #include "pretty-print.h"
20 : : #if HAVE_SECCOMP
21 : : # include "seccomp-util.h"
22 : : #endif
23 : : #include "set.h"
24 : : #include "stdio-util.h"
25 : : #include "strv.h"
26 : : #include "terminal-util.h"
27 : : #include "unit-def.h"
28 : : #include "unit-name.h"
29 : :
30 : : struct security_info {
31 : : char *id;
32 : : char *type;
33 : : char *load_state;
34 : : char *fragment_path;
35 : : bool default_dependencies;
36 : :
37 : : uint64_t ambient_capabilities;
38 : : uint64_t capability_bounding_set;
39 : :
40 : : char *user;
41 : : char **supplementary_groups;
42 : : bool dynamic_user;
43 : :
44 : : bool ip_address_deny_all;
45 : : bool ip_address_allow_localhost;
46 : : bool ip_address_allow_other;
47 : :
48 : : bool ip_filters_custom_ingress;
49 : : bool ip_filters_custom_egress;
50 : :
51 : : char *keyring_mode;
52 : : bool lock_personality;
53 : : bool memory_deny_write_execute;
54 : : bool no_new_privileges;
55 : : char *notify_access;
56 : : bool protect_hostname;
57 : :
58 : : bool private_devices;
59 : : bool private_mounts;
60 : : bool private_network;
61 : : bool private_tmp;
62 : : bool private_users;
63 : :
64 : : bool protect_control_groups;
65 : : bool protect_kernel_modules;
66 : : bool protect_kernel_tunables;
67 : :
68 : : char *protect_home;
69 : : char *protect_system;
70 : :
71 : : bool remove_ipc;
72 : :
73 : : bool restrict_address_family_inet;
74 : : bool restrict_address_family_unix;
75 : : bool restrict_address_family_netlink;
76 : : bool restrict_address_family_packet;
77 : : bool restrict_address_family_other;
78 : :
79 : : uint64_t restrict_namespaces;
80 : : bool restrict_realtime;
81 : : bool restrict_suid_sgid;
82 : :
83 : : char *root_directory;
84 : : char *root_image;
85 : :
86 : : bool delegate;
87 : : char *device_policy;
88 : : bool device_allow_non_empty;
89 : :
90 : : char **system_call_architectures;
91 : :
92 : : bool system_call_filter_whitelist;
93 : : Set *system_call_filter;
94 : :
95 : : uint32_t _umask;
96 : : };
97 : :
98 : : struct security_assessor {
99 : : const char *id;
100 : : const char *description_good;
101 : : const char *description_bad;
102 : : const char *description_na;
103 : : const char *url;
104 : : uint64_t weight;
105 : : uint64_t range;
106 : : int (*assess)(
107 : : const struct security_assessor *a,
108 : : const struct security_info *info,
109 : : const void *data,
110 : : uint64_t *ret_badness,
111 : : char **ret_description);
112 : : size_t offset;
113 : : uint64_t parameter;
114 : : bool default_dependencies_only;
115 : : };
116 : :
117 : 0 : static void security_info_free(struct security_info *i) {
118 [ # # ]: 0 : if (!i)
119 : 0 : return;
120 : :
121 : 0 : free(i->id);
122 : 0 : free(i->type);
123 : 0 : free(i->load_state);
124 : 0 : free(i->fragment_path);
125 : :
126 : 0 : free(i->user);
127 : :
128 : 0 : free(i->protect_home);
129 : 0 : free(i->protect_system);
130 : :
131 : 0 : free(i->root_directory);
132 : 0 : free(i->root_image);
133 : :
134 : 0 : free(i->keyring_mode);
135 : 0 : free(i->notify_access);
136 : :
137 : 0 : free(i->device_policy);
138 : :
139 : 0 : strv_free(i->supplementary_groups);
140 : 0 : strv_free(i->system_call_architectures);
141 : :
142 : 0 : set_free_free(i->system_call_filter);
143 : : }
144 : :
145 : 0 : static bool security_info_runs_privileged(const struct security_info *i) {
146 [ # # ]: 0 : assert(i);
147 : :
148 [ # # # # : 0 : if (STRPTR_IN_SET(i->user, "0", "root"))
# # ]
149 : 0 : return true;
150 : :
151 [ # # ]: 0 : if (i->dynamic_user)
152 : 0 : return false;
153 : :
154 : 0 : return isempty(i->user);
155 : : }
156 : :
157 : 0 : static int assess_bool(
158 : : const struct security_assessor *a,
159 : : const struct security_info *info,
160 : : const void *data,
161 : : uint64_t *ret_badness,
162 : : char **ret_description) {
163 : :
164 : 0 : const bool *b = data;
165 : :
166 [ # # ]: 0 : assert(b);
167 [ # # ]: 0 : assert(ret_badness);
168 [ # # ]: 0 : assert(ret_description);
169 : :
170 [ # # ]: 0 : *ret_badness = a->parameter ? *b : !*b;
171 : 0 : *ret_description = NULL;
172 : :
173 : 0 : return 0;
174 : : }
175 : :
176 : 0 : static int assess_user(
177 : : const struct security_assessor *a,
178 : : const struct security_info *info,
179 : : const void *data,
180 : : uint64_t *ret_badness,
181 : : char **ret_description) {
182 : :
183 : 0 : _cleanup_free_ char *d = NULL;
184 : : uint64_t b;
185 : :
186 [ # # ]: 0 : assert(ret_badness);
187 [ # # ]: 0 : assert(ret_description);
188 : :
189 [ # # ]: 0 : if (streq_ptr(info->user, NOBODY_USER_NAME)) {
190 : 0 : d = strdup("Service runs under as '" NOBODY_USER_NAME "' user, which should not be used for services");
191 : 0 : b = 9;
192 [ # # # # ]: 0 : } else if (info->dynamic_user && !STR_IN_SET(info->user, "0", "root")) {
193 : 0 : d = strdup("Service runs under a transient non-root user identity");
194 : 0 : b = 0;
195 [ # # # # ]: 0 : } else if (info->user && !STR_IN_SET(info->user, "0", "root", "")) {
196 : 0 : d = strdup("Service runs under a static non-root user identity");
197 : 0 : b = 0;
198 : : } else {
199 : 0 : *ret_badness = 10;
200 : 0 : *ret_description = NULL;
201 : 0 : return 0;
202 : : }
203 : :
204 [ # # ]: 0 : if (!d)
205 : 0 : return log_oom();
206 : :
207 : 0 : *ret_badness = b;
208 : 0 : *ret_description = TAKE_PTR(d);
209 : :
210 : 0 : return 0;
211 : : }
212 : :
213 : 0 : static int assess_protect_home(
214 : : const struct security_assessor *a,
215 : : const struct security_info *info,
216 : : const void *data,
217 : : uint64_t *ret_badness,
218 : : char **ret_description) {
219 : :
220 : : const char *description;
221 : : uint64_t badness;
222 : : char *copy;
223 : : int r;
224 : :
225 [ # # ]: 0 : assert(ret_badness);
226 [ # # ]: 0 : assert(ret_description);
227 : :
228 : 0 : badness = 10;
229 : 0 : description = "Service has full access to home directories";
230 : :
231 : 0 : r = parse_boolean(info->protect_home);
232 [ # # ]: 0 : if (r < 0) {
233 [ # # ]: 0 : if (streq_ptr(info->protect_home, "read-only")) {
234 : 0 : badness = 5;
235 : 0 : description = "Service has read-only access to home directories";
236 [ # # ]: 0 : } else if (streq_ptr(info->protect_home, "tmpfs")) {
237 : 0 : badness = 1;
238 : 0 : description = "Service has access to fake empty home directories";
239 : : }
240 [ # # ]: 0 : } else if (r > 0) {
241 : 0 : badness = 0;
242 : 0 : description = "Service has no access to home directories";
243 : : }
244 : :
245 : 0 : copy = strdup(description);
246 [ # # ]: 0 : if (!copy)
247 : 0 : return log_oom();
248 : :
249 : 0 : *ret_badness = badness;
250 : 0 : *ret_description = copy;
251 : :
252 : 0 : return 0;
253 : : }
254 : :
255 : 0 : static int assess_protect_system(
256 : : const struct security_assessor *a,
257 : : const struct security_info *info,
258 : : const void *data,
259 : : uint64_t *ret_badness,
260 : : char **ret_description) {
261 : :
262 : : const char *description;
263 : : uint64_t badness;
264 : : char *copy;
265 : : int r;
266 : :
267 [ # # ]: 0 : assert(ret_badness);
268 [ # # ]: 0 : assert(ret_description);
269 : :
270 : 0 : badness = 10;
271 : 0 : description = "Service has full access to the OS file hierarchy";
272 : :
273 : 0 : r = parse_boolean(info->protect_system);
274 [ # # ]: 0 : if (r < 0) {
275 [ # # ]: 0 : if (streq_ptr(info->protect_system, "full")) {
276 : 0 : badness = 3;
277 : 0 : description = "Service has very limited write access to the OS file hierarchy";
278 [ # # ]: 0 : } else if (streq_ptr(info->protect_system, "strict")) {
279 : 0 : badness = 0;
280 : 0 : description = "Service has strict read-only access to the OS file hierarchy";
281 : : }
282 [ # # ]: 0 : } else if (r > 0) {
283 : 0 : badness = 5;
284 : 0 : description = "Service has limited write access to the OS file hierarchy";
285 : : }
286 : :
287 : 0 : copy = strdup(description);
288 [ # # ]: 0 : if (!copy)
289 : 0 : return log_oom();
290 : :
291 : 0 : *ret_badness = badness;
292 : 0 : *ret_description = copy;
293 : :
294 : 0 : return 0;
295 : : }
296 : :
297 : 0 : static int assess_root_directory(
298 : : const struct security_assessor *a,
299 : : const struct security_info *info,
300 : : const void *data,
301 : : uint64_t *ret_badness,
302 : : char **ret_description) {
303 : :
304 [ # # ]: 0 : assert(ret_badness);
305 [ # # ]: 0 : assert(ret_description);
306 : :
307 : 0 : *ret_badness =
308 [ # # # # ]: 0 : empty_or_root(info->root_directory) ||
309 : 0 : empty_or_root(info->root_image);
310 : 0 : *ret_description = NULL;
311 : :
312 : 0 : return 0;
313 : : }
314 : :
315 : 0 : static int assess_capability_bounding_set(
316 : : const struct security_assessor *a,
317 : : const struct security_info *info,
318 : : const void *data,
319 : : uint64_t *ret_badness,
320 : : char **ret_description) {
321 : :
322 [ # # ]: 0 : assert(ret_badness);
323 [ # # ]: 0 : assert(ret_description);
324 : :
325 : 0 : *ret_badness = !!(info->capability_bounding_set & a->parameter);
326 : 0 : *ret_description = NULL;
327 : :
328 : 0 : return 0;
329 : : }
330 : :
331 : 0 : static int assess_umask(
332 : : const struct security_assessor *a,
333 : : const struct security_info *info,
334 : : const void *data,
335 : : uint64_t *ret_badness,
336 : : char **ret_description) {
337 : :
338 : 0 : char *copy = NULL;
339 : : const char *d;
340 : : uint64_t b;
341 : :
342 [ # # ]: 0 : assert(ret_badness);
343 [ # # ]: 0 : assert(ret_description);
344 : :
345 [ # # ]: 0 : if (!FLAGS_SET(info->_umask, 0002)) {
346 : 0 : d = "Files created by service are world-writable by default";
347 : 0 : b = 10;
348 [ # # ]: 0 : } else if (!FLAGS_SET(info->_umask, 0004)) {
349 : 0 : d = "Files created by service are world-readable by default";
350 : 0 : b = 5;
351 [ # # ]: 0 : } else if (!FLAGS_SET(info->_umask, 0020)) {
352 : 0 : d = "Files created by service are group-writable by default";
353 : 0 : b = 2;
354 [ # # ]: 0 : } else if (!FLAGS_SET(info->_umask, 0040)) {
355 : 0 : d = "Files created by service are group-readable by default";
356 : 0 : b = 1;
357 : : } else {
358 : 0 : d = "Files created by service are accessible only by service's own user by default";
359 : 0 : b = 0;
360 : : }
361 : :
362 : 0 : copy = strdup(d);
363 [ # # ]: 0 : if (!copy)
364 : 0 : return log_oom();
365 : :
366 : 0 : *ret_badness = b;
367 : 0 : *ret_description = copy;
368 : :
369 : 0 : return 0;
370 : : }
371 : :
372 : 0 : static int assess_keyring_mode(
373 : : const struct security_assessor *a,
374 : : const struct security_info *info,
375 : : const void *data,
376 : : uint64_t *ret_badness,
377 : : char **ret_description) {
378 : :
379 [ # # ]: 0 : assert(ret_badness);
380 [ # # ]: 0 : assert(ret_description);
381 : :
382 : 0 : *ret_badness = !streq_ptr(info->keyring_mode, "private");
383 : 0 : *ret_description = NULL;
384 : :
385 : 0 : return 0;
386 : : }
387 : :
388 : 0 : static int assess_notify_access(
389 : : const struct security_assessor *a,
390 : : const struct security_info *info,
391 : : const void *data,
392 : : uint64_t *ret_badness,
393 : : char **ret_description) {
394 : :
395 [ # # ]: 0 : assert(ret_badness);
396 [ # # ]: 0 : assert(ret_description);
397 : :
398 : 0 : *ret_badness = streq_ptr(info->notify_access, "all");
399 : 0 : *ret_description = NULL;
400 : :
401 : 0 : return 0;
402 : : }
403 : :
404 : 0 : static int assess_remove_ipc(
405 : : const struct security_assessor *a,
406 : : const struct security_info *info,
407 : : const void *data,
408 : : uint64_t *ret_badness,
409 : : char **ret_description) {
410 : :
411 [ # # ]: 0 : assert(ret_badness);
412 [ # # ]: 0 : assert(ret_description);
413 : :
414 [ # # ]: 0 : if (security_info_runs_privileged(info))
415 : 0 : *ret_badness = UINT64_MAX;
416 : : else
417 : 0 : *ret_badness = !info->remove_ipc;
418 : :
419 : 0 : *ret_description = NULL;
420 : 0 : return 0;
421 : : }
422 : :
423 : 0 : static int assess_supplementary_groups(
424 : : const struct security_assessor *a,
425 : : const struct security_info *info,
426 : : const void *data,
427 : : uint64_t *ret_badness,
428 : : char **ret_description) {
429 : :
430 [ # # ]: 0 : assert(ret_badness);
431 [ # # ]: 0 : assert(ret_description);
432 : :
433 [ # # ]: 0 : if (security_info_runs_privileged(info))
434 : 0 : *ret_badness = UINT64_MAX;
435 : : else
436 : 0 : *ret_badness = !strv_isempty(info->supplementary_groups);
437 : :
438 : 0 : *ret_description = NULL;
439 : 0 : return 0;
440 : : }
441 : :
442 : 0 : static int assess_restrict_namespaces(
443 : : const struct security_assessor *a,
444 : : const struct security_info *info,
445 : : const void *data,
446 : : uint64_t *ret_badness,
447 : : char **ret_description) {
448 : :
449 [ # # ]: 0 : assert(ret_badness);
450 [ # # ]: 0 : assert(ret_description);
451 : :
452 : 0 : *ret_badness = !!(info->restrict_namespaces & a->parameter);
453 : 0 : *ret_description = NULL;
454 : :
455 : 0 : return 0;
456 : : }
457 : :
458 : 0 : static int assess_system_call_architectures(
459 : : const struct security_assessor *a,
460 : : const struct security_info *info,
461 : : const void *data,
462 : : uint64_t *ret_badness,
463 : : char **ret_description) {
464 : :
465 : : char *d;
466 : : uint64_t b;
467 : :
468 [ # # ]: 0 : assert(ret_badness);
469 [ # # ]: 0 : assert(ret_description);
470 : :
471 [ # # ]: 0 : if (strv_isempty(info->system_call_architectures)) {
472 : 0 : b = 10;
473 : 0 : d = strdup("Service may execute system calls with all ABIs");
474 [ # # ]: 0 : } else if (strv_equal(info->system_call_architectures, STRV_MAKE("native"))) {
475 : 0 : b = 0;
476 : 0 : d = strdup("Service may execute system calls only with native ABI");
477 : : } else {
478 : 0 : b = 8;
479 : 0 : d = strdup("Service may execute system calls with multiple ABIs");
480 : : }
481 : :
482 [ # # ]: 0 : if (!d)
483 : 0 : return log_oom();
484 : :
485 : 0 : *ret_badness = b;
486 : 0 : *ret_description = d;
487 : :
488 : 0 : return 0;
489 : : }
490 : :
491 : : #if HAVE_SECCOMP
492 : :
493 : 0 : static bool syscall_names_in_filter(Set *s, bool whitelist, const SyscallFilterSet *f) {
494 : : const char *syscall;
495 : :
496 [ # # # # ]: 0 : NULSTR_FOREACH(syscall, f->value) {
497 : : int id;
498 : :
499 [ # # ]: 0 : if (syscall[0] == '@') {
500 : : const SyscallFilterSet *g;
501 : :
502 [ # # ]: 0 : assert_se(g = syscall_filter_set_find(syscall));
503 [ # # ]: 0 : if (syscall_names_in_filter(s, whitelist, g))
504 : 0 : return true; /* bad! */
505 : :
506 : 0 : continue;
507 : : }
508 : :
509 : : /* Let's see if the system call actually exists on this platform, before complaining */
510 : 0 : id = seccomp_syscall_resolve_name(syscall);
511 [ # # ]: 0 : if (id < 0)
512 : 0 : continue;
513 : :
514 [ # # ]: 0 : if (set_contains(s, syscall) == whitelist) {
515 [ # # ]: 0 : log_debug("Offending syscall filter item: %s", syscall);
516 : 0 : return true; /* bad! */
517 : : }
518 : : }
519 : :
520 : 0 : return false;
521 : : }
522 : :
523 : 0 : static int assess_system_call_filter(
524 : : const struct security_assessor *a,
525 : : const struct security_info *info,
526 : : const void *data,
527 : : uint64_t *ret_badness,
528 : : char **ret_description) {
529 : :
530 : : const SyscallFilterSet *f;
531 : 0 : char *d = NULL;
532 : : uint64_t b;
533 : :
534 [ # # ]: 0 : assert(a);
535 [ # # ]: 0 : assert(info);
536 [ # # ]: 0 : assert(ret_badness);
537 [ # # ]: 0 : assert(ret_description);
538 : :
539 [ # # ]: 0 : assert(a->parameter < _SYSCALL_FILTER_SET_MAX);
540 : 0 : f = syscall_filter_sets + a->parameter;
541 : :
542 [ # # # # ]: 0 : if (!info->system_call_filter_whitelist && set_isempty(info->system_call_filter)) {
543 : 0 : d = strdup("Service does not filter system calls");
544 : 0 : b = 10;
545 : : } else {
546 : : bool bad;
547 : :
548 [ # # ]: 0 : log_debug("Analyzing system call filter, checking against: %s", f->name);
549 : 0 : bad = syscall_names_in_filter(info->system_call_filter, info->system_call_filter_whitelist, f);
550 [ # # # # ]: 0 : log_debug("Result: %s", bad ? "bad" : "good");
551 : :
552 [ # # ]: 0 : if (info->system_call_filter_whitelist) {
553 [ # # ]: 0 : if (bad) {
554 : 0 : (void) asprintf(&d, "System call whitelist defined for service, and %s is included", f->name);
555 : 0 : b = 9;
556 : : } else {
557 : 0 : (void) asprintf(&d, "System call whitelist defined for service, and %s is not included", f->name);
558 : 0 : b = 0;
559 : : }
560 : : } else {
561 [ # # ]: 0 : if (bad) {
562 : 0 : (void) asprintf(&d, "System call blacklist defined for service, and %s is not included", f->name);
563 : 0 : b = 10;
564 : : } else {
565 : 0 : (void) asprintf(&d, "System call blacklist defined for service, and %s is included", f->name);
566 : 0 : b = 5;
567 : : }
568 : : }
569 : : }
570 : :
571 [ # # ]: 0 : if (!d)
572 : 0 : return log_oom();
573 : :
574 : 0 : *ret_badness = b;
575 : 0 : *ret_description = d;
576 : :
577 : 0 : return 0;
578 : : }
579 : :
580 : : #endif
581 : :
582 : 0 : static int assess_ip_address_allow(
583 : : const struct security_assessor *a,
584 : : const struct security_info *info,
585 : : const void *data,
586 : : uint64_t *ret_badness,
587 : : char **ret_description) {
588 : :
589 : 0 : char *d = NULL;
590 : : uint64_t b;
591 : :
592 [ # # ]: 0 : assert(info);
593 [ # # ]: 0 : assert(ret_badness);
594 [ # # ]: 0 : assert(ret_description);
595 : :
596 [ # # # # ]: 0 : if (info->ip_filters_custom_ingress || info->ip_filters_custom_egress) {
597 : 0 : d = strdup("Service defines custom ingress/egress IP filters with BPF programs");
598 : 0 : b = 0;
599 [ # # ]: 0 : } else if (!info->ip_address_deny_all) {
600 : 0 : d = strdup("Service does not define an IP address whitelist");
601 : 0 : b = 10;
602 [ # # ]: 0 : } else if (info->ip_address_allow_other) {
603 : 0 : d = strdup("Service defines IP address whitelist with non-localhost entries");
604 : 0 : b = 5;
605 [ # # ]: 0 : } else if (info->ip_address_allow_localhost) {
606 : 0 : d = strdup("Service defines IP address whitelist with only localhost entries");
607 : 0 : b = 2;
608 : : } else {
609 : 0 : d = strdup("Service blocks all IP address ranges");
610 : 0 : b = 0;
611 : : }
612 : :
613 [ # # ]: 0 : if (!d)
614 : 0 : return log_oom();
615 : :
616 : 0 : *ret_badness = b;
617 : 0 : *ret_description = d;
618 : :
619 : 0 : return 0;
620 : : }
621 : :
622 : 0 : static int assess_device_allow(
623 : : const struct security_assessor *a,
624 : : const struct security_info *info,
625 : : const void *data,
626 : : uint64_t *ret_badness,
627 : : char **ret_description) {
628 : :
629 : 0 : char *d = NULL;
630 : : uint64_t b;
631 : :
632 [ # # ]: 0 : assert(info);
633 [ # # ]: 0 : assert(ret_badness);
634 [ # # ]: 0 : assert(ret_description);
635 : :
636 [ # # # # : 0 : if (STRPTR_IN_SET(info->device_policy, "strict", "closed")) {
# # ]
637 : :
638 [ # # ]: 0 : if (info->device_allow_non_empty) {
639 : 0 : d = strdup("Service has a device ACL with some special devices");
640 : 0 : b = 5;
641 : : } else {
642 : 0 : d = strdup("Service has a minimal device ACL");
643 : 0 : b = 0;
644 : : }
645 : : } else {
646 : 0 : d = strdup("Service has no device ACL");
647 : 0 : b = 10;
648 : : }
649 : :
650 [ # # ]: 0 : if (!d)
651 : 0 : return log_oom();
652 : :
653 : 0 : *ret_badness = b;
654 : 0 : *ret_description = d;
655 : :
656 : 0 : return 0;
657 : : }
658 : :
659 : 0 : static int assess_ambient_capabilities(
660 : : const struct security_assessor *a,
661 : : const struct security_info *info,
662 : : const void *data,
663 : : uint64_t *ret_badness,
664 : : char **ret_description) {
665 : :
666 [ # # ]: 0 : assert(ret_badness);
667 [ # # ]: 0 : assert(ret_description);
668 : :
669 : 0 : *ret_badness = info->ambient_capabilities != 0;
670 : 0 : *ret_description = NULL;
671 : :
672 : 0 : return 0;
673 : : }
674 : :
675 : : static const struct security_assessor security_assessor_table[] = {
676 : : {
677 : : .id = "User=/DynamicUser=",
678 : : .description_bad = "Service runs as root user",
679 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#User=",
680 : : .weight = 2000,
681 : : .range = 10,
682 : : .assess = assess_user,
683 : : },
684 : : {
685 : : .id = "SupplementaryGroups=",
686 : : .description_good = "Service has no supplementary groups",
687 : : .description_bad = "Service runs with supplementary groups",
688 : : .description_na = "Service runs as root, option does not matter",
689 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SupplementaryGroups=",
690 : : .weight = 200,
691 : : .range = 1,
692 : : .assess = assess_supplementary_groups,
693 : : },
694 : : {
695 : : .id = "PrivateDevices=",
696 : : .description_good = "Service has no access to hardware devices",
697 : : .description_bad = "Service potentially has access to hardware devices",
698 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateDevices=",
699 : : .weight = 1000,
700 : : .range = 1,
701 : : .assess = assess_bool,
702 : : .offset = offsetof(struct security_info, private_devices),
703 : : },
704 : : {
705 : : .id = "PrivateMounts=",
706 : : .description_good = "Service cannot install system mounts",
707 : : .description_bad = "Service may install system mounts",
708 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateMounts=",
709 : : .weight = 1000,
710 : : .range = 1,
711 : : .assess = assess_bool,
712 : : .offset = offsetof(struct security_info, private_mounts),
713 : : },
714 : : {
715 : : .id = "PrivateNetwork=",
716 : : .description_good = "Service has no access to the host's network",
717 : : .description_bad = "Service has access to the host's network",
718 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateNetwork=",
719 : : .weight = 2500,
720 : : .range = 1,
721 : : .assess = assess_bool,
722 : : .offset = offsetof(struct security_info, private_network),
723 : : },
724 : : {
725 : : .id = "PrivateTmp=",
726 : : .description_good = "Service has no access to other software's temporary files",
727 : : .description_bad = "Service has access to other software's temporary files",
728 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateTmp=",
729 : : .weight = 1000,
730 : : .range = 1,
731 : : .assess = assess_bool,
732 : : .offset = offsetof(struct security_info, private_tmp),
733 : : .default_dependencies_only = true,
734 : : },
735 : : {
736 : : .id = "PrivateUsers=",
737 : : .description_good = "Service does not have access to other users",
738 : : .description_bad = "Service has access to other users",
739 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateUsers=",
740 : : .weight = 1000,
741 : : .range = 1,
742 : : .assess = assess_bool,
743 : : .offset = offsetof(struct security_info, private_users),
744 : : },
745 : : {
746 : : .id = "ProtectControlGroups=",
747 : : .description_good = "Service cannot modify the control group file system",
748 : : .description_bad = "Service may modify to the control group file system",
749 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=",
750 : : .weight = 1000,
751 : : .range = 1,
752 : : .assess = assess_bool,
753 : : .offset = offsetof(struct security_info, protect_control_groups),
754 : : },
755 : : {
756 : : .id = "ProtectKernelModules=",
757 : : .description_good = "Service cannot load or read kernel modules",
758 : : .description_bad = "Service may load or read kernel modules",
759 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelModules=",
760 : : .weight = 1000,
761 : : .range = 1,
762 : : .assess = assess_bool,
763 : : .offset = offsetof(struct security_info, protect_kernel_modules),
764 : : },
765 : : {
766 : : .id = "ProtectKernelTunables=",
767 : : .description_good = "Service cannot alter kernel tunables (/proc/sys, …)",
768 : : .description_bad = "Service may alter kernel tunables",
769 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelTunables=",
770 : : .weight = 1000,
771 : : .range = 1,
772 : : .assess = assess_bool,
773 : : .offset = offsetof(struct security_info, protect_kernel_tunables),
774 : : },
775 : : {
776 : : .id = "ProtectHome=",
777 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
778 : : .weight = 1000,
779 : : .range = 10,
780 : : .assess = assess_protect_home,
781 : : .default_dependencies_only = true,
782 : : },
783 : : {
784 : : .id = "ProtectHostname=",
785 : : .description_good = "Service cannot change system host/domainname",
786 : : .description_bad = "Service may change system host/domainname",
787 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHostname=",
788 : : .weight = 50,
789 : : .range = 1,
790 : : .assess = assess_bool,
791 : : .offset = offsetof(struct security_info, protect_hostname),
792 : : },
793 : : {
794 : : .id = "ProtectSystem=",
795 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=",
796 : : .weight = 1000,
797 : : .range = 10,
798 : : .assess = assess_protect_system,
799 : : .default_dependencies_only = true,
800 : : },
801 : : {
802 : : .id = "RootDirectory=/RootImage=",
803 : : .description_good = "Service has its own root directory/image",
804 : : .description_bad = "Service runs within the host's root directory",
805 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RootDirectory=",
806 : : .weight = 200,
807 : : .range = 1,
808 : : .assess = assess_root_directory,
809 : : .default_dependencies_only = true,
810 : : },
811 : : {
812 : : .id = "LockPersonality=",
813 : : .description_good = "Service cannot change ABI personality",
814 : : .description_bad = "Service may change ABI personality",
815 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LockPersonality=",
816 : : .weight = 100,
817 : : .range = 1,
818 : : .assess = assess_bool,
819 : : .offset = offsetof(struct security_info, lock_personality),
820 : : },
821 : : {
822 : : .id = "MemoryDenyWriteExecute=",
823 : : .description_good = "Service cannot create writable executable memory mappings",
824 : : .description_bad = "Service may create writable executable memory mappings",
825 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#MemoryDenyWriteExecute=",
826 : : .weight = 100,
827 : : .range = 1,
828 : : .assess = assess_bool,
829 : : .offset = offsetof(struct security_info, memory_deny_write_execute),
830 : : },
831 : : {
832 : : .id = "NoNewPrivileges=",
833 : : .description_good = "Service processes cannot acquire new privileges",
834 : : .description_bad = "Service processes may acquire new privileges",
835 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NoNewPrivileges=",
836 : : .weight = 1000,
837 : : .range = 1,
838 : : .assess = assess_bool,
839 : : .offset = offsetof(struct security_info, no_new_privileges),
840 : : },
841 : : {
842 : : .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN",
843 : : .description_good = "Service has no administrator privileges",
844 : : .description_bad = "Service has administrator privileges",
845 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
846 : : .weight = 1500,
847 : : .range = 1,
848 : : .assess = assess_capability_bounding_set,
849 : : .parameter = UINT64_C(1) << CAP_SYS_ADMIN,
850 : : },
851 : : {
852 : : .id = "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
853 : : .description_good = "Service cannot change UID/GID identities/capabilities",
854 : : .description_bad = "Service may change UID/GID identities/capabilities",
855 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
856 : : .weight = 1500,
857 : : .range = 1,
858 : : .assess = assess_capability_bounding_set,
859 : : .parameter = (UINT64_C(1) << CAP_SETUID)|
860 : : (UINT64_C(1) << CAP_SETGID)|
861 : : (UINT64_C(1) << CAP_SETPCAP),
862 : : },
863 : : {
864 : : .id = "CapabilityBoundingSet=~CAP_SYS_PTRACE",
865 : : .description_good = "Service has no ptrace() debugging abilities",
866 : : .description_bad = "Service has ptrace() debugging abilities",
867 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
868 : : .weight = 1500,
869 : : .range = 1,
870 : : .assess = assess_capability_bounding_set,
871 : : .parameter = (UINT64_C(1) << CAP_SYS_PTRACE),
872 : : },
873 : : {
874 : : .id = "CapabilityBoundingSet=~CAP_SYS_TIME",
875 : : .description_good = "Service processes cannot change the system clock",
876 : : .description_bad = "Service processes may change the system clock",
877 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
878 : : .weight = 1000,
879 : : .range = 1,
880 : : .assess = assess_capability_bounding_set,
881 : : .parameter = UINT64_C(1) << CAP_SYS_TIME,
882 : : },
883 : : {
884 : : .id = "CapabilityBoundingSet=~CAP_NET_ADMIN",
885 : : .description_good = "Service has no network configuration privileges",
886 : : .description_bad = "Service has network configuration privileges",
887 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
888 : : .weight = 1000,
889 : : .range = 1,
890 : : .assess = assess_capability_bounding_set,
891 : : .parameter = (UINT64_C(1) << CAP_NET_ADMIN),
892 : : },
893 : : {
894 : : .id = "CapabilityBoundingSet=~CAP_RAWIO",
895 : : .description_good = "Service has no raw I/O access",
896 : : .description_bad = "Service has raw I/O access",
897 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
898 : : .weight = 1000,
899 : : .range = 1,
900 : : .assess = assess_capability_bounding_set,
901 : : .parameter = (UINT64_C(1) << CAP_SYS_RAWIO),
902 : : },
903 : : {
904 : : .id = "CapabilityBoundingSet=~CAP_SYS_MODULE",
905 : : .description_good = "Service cannot load kernel modules",
906 : : .description_bad = "Service may load kernel modules",
907 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
908 : : .weight = 1000,
909 : : .range = 1,
910 : : .assess = assess_capability_bounding_set,
911 : : .parameter = (UINT64_C(1) << CAP_SYS_MODULE),
912 : : },
913 : : {
914 : : .id = "CapabilityBoundingSet=~CAP_AUDIT_*",
915 : : .description_good = "Service has no audit subsystem access",
916 : : .description_bad = "Service has audit subsystem access",
917 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
918 : : .weight = 500,
919 : : .range = 1,
920 : : .assess = assess_capability_bounding_set,
921 : : .parameter = (UINT64_C(1) << CAP_AUDIT_CONTROL) |
922 : : (UINT64_C(1) << CAP_AUDIT_READ) |
923 : : (UINT64_C(1) << CAP_AUDIT_WRITE),
924 : : },
925 : : {
926 : : .id = "CapabilityBoundingSet=~CAP_SYSLOG",
927 : : .description_good = "Service has no access to kernel logging",
928 : : .description_bad = "Service has access to kernel logging",
929 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
930 : : .weight = 500,
931 : : .range = 1,
932 : : .assess = assess_capability_bounding_set,
933 : : .parameter = (UINT64_C(1) << CAP_SYSLOG),
934 : : },
935 : : {
936 : : .id = "CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)",
937 : : .description_good = "Service has no privileges to change resource use parameters",
938 : : .description_bad = "Service has privileges to change resource use parameters",
939 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
940 : : .weight = 500,
941 : : .range = 1,
942 : : .assess = assess_capability_bounding_set,
943 : : .parameter = (UINT64_C(1) << CAP_SYS_NICE) |
944 : : (UINT64_C(1) << CAP_SYS_RESOURCE),
945 : : },
946 : : {
947 : : .id = "CapabilityBoundingSet=~CAP_MKNOD",
948 : : .description_good = "Service cannot create device nodes",
949 : : .description_bad = "Service may create device nodes",
950 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
951 : : .weight = 500,
952 : : .range = 1,
953 : : .assess = assess_capability_bounding_set,
954 : : .parameter = (UINT64_C(1) << CAP_MKNOD),
955 : : },
956 : : {
957 : : .id = "CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)",
958 : : .description_good = "Service cannot change file ownership/access mode/capabilities",
959 : : .description_bad = "Service may change file ownership/access mode/capabilities unrestricted",
960 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
961 : : .weight = 1000,
962 : : .range = 1,
963 : : .assess = assess_capability_bounding_set,
964 : : .parameter = (UINT64_C(1) << CAP_CHOWN) |
965 : : (UINT64_C(1) << CAP_FSETID) |
966 : : (UINT64_C(1) << CAP_SETFCAP),
967 : : },
968 : : {
969 : : .id = "CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)",
970 : : .description_good = "Service cannot override UNIX file/IPC permission checks",
971 : : .description_bad = "Service may override UNIX file/IPC permission checks",
972 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
973 : : .weight = 1000,
974 : : .range = 1,
975 : : .assess = assess_capability_bounding_set,
976 : : .parameter = (UINT64_C(1) << CAP_DAC_OVERRIDE) |
977 : : (UINT64_C(1) << CAP_DAC_READ_SEARCH) |
978 : : (UINT64_C(1) << CAP_FOWNER) |
979 : : (UINT64_C(1) << CAP_IPC_OWNER),
980 : : },
981 : : {
982 : : .id = "CapabilityBoundingSet=~CAP_KILL",
983 : : .description_good = "Service cannot send UNIX signals to arbitrary processes",
984 : : .description_bad = "Service may send UNIX signals to arbitrary processes",
985 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
986 : : .weight = 500,
987 : : .range = 1,
988 : : .assess = assess_capability_bounding_set,
989 : : .parameter = (UINT64_C(1) << CAP_KILL),
990 : : },
991 : : {
992 : : .id = "CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)",
993 : : .description_good = "Service has no elevated networking privileges",
994 : : .description_bad = "Service has elevated networking privileges",
995 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
996 : : .weight = 500,
997 : : .range = 1,
998 : : .assess = assess_capability_bounding_set,
999 : : .parameter = (UINT64_C(1) << CAP_NET_BIND_SERVICE) |
1000 : : (UINT64_C(1) << CAP_NET_BROADCAST) |
1001 : : (UINT64_C(1) << CAP_NET_RAW),
1002 : : },
1003 : : {
1004 : : .id = "CapabilityBoundingSet=~CAP_SYS_BOOT",
1005 : : .description_good = "Service cannot issue reboot()",
1006 : : .description_bad = "Service may issue reboot()",
1007 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1008 : : .weight = 100,
1009 : : .range = 1,
1010 : : .assess = assess_capability_bounding_set,
1011 : : .parameter = (UINT64_C(1) << CAP_SYS_BOOT),
1012 : : },
1013 : : {
1014 : : .id = "CapabilityBoundingSet=~CAP_MAC_*",
1015 : : .description_good = "Service cannot adjust SMACK MAC",
1016 : : .description_bad = "Service may adjust SMACK MAC",
1017 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1018 : : .weight = 100,
1019 : : .range = 1,
1020 : : .assess = assess_capability_bounding_set,
1021 : : .parameter = (UINT64_C(1) << CAP_MAC_ADMIN)|
1022 : : (UINT64_C(1) << CAP_MAC_OVERRIDE),
1023 : : },
1024 : : {
1025 : : .id = "CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE",
1026 : : .description_good = "Service cannot mark files immutable",
1027 : : .description_bad = "Service may mark files immutable",
1028 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1029 : : .weight = 75,
1030 : : .range = 1,
1031 : : .assess = assess_capability_bounding_set,
1032 : : .parameter = (UINT64_C(1) << CAP_LINUX_IMMUTABLE),
1033 : : },
1034 : : {
1035 : : .id = "CapabilityBoundingSet=~CAP_IPC_LOCK",
1036 : : .description_good = "Service cannot lock memory into RAM",
1037 : : .description_bad = "Service may lock memory into RAM",
1038 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1039 : : .weight = 50,
1040 : : .range = 1,
1041 : : .assess = assess_capability_bounding_set,
1042 : : .parameter = (UINT64_C(1) << CAP_IPC_LOCK),
1043 : : },
1044 : : {
1045 : : .id = "CapabilityBoundingSet=~CAP_SYS_CHROOT",
1046 : : .description_good = "Service cannot issue chroot()",
1047 : : .description_bad = "Service may issue chroot()",
1048 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1049 : : .weight = 50,
1050 : : .range = 1,
1051 : : .assess = assess_capability_bounding_set,
1052 : : .parameter = (UINT64_C(1) << CAP_SYS_CHROOT),
1053 : : },
1054 : : {
1055 : : .id = "CapabilityBoundingSet=~CAP_BLOCK_SUSPEND",
1056 : : .description_good = "Service cannot establish wake locks",
1057 : : .description_bad = "Service may establish wake locks",
1058 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1059 : : .weight = 25,
1060 : : .range = 1,
1061 : : .assess = assess_capability_bounding_set,
1062 : : .parameter = (UINT64_C(1) << CAP_BLOCK_SUSPEND),
1063 : : },
1064 : : {
1065 : : .id = "CapabilityBoundingSet=~CAP_WAKE_ALARM",
1066 : : .description_good = "Service cannot program timers that wake up the system",
1067 : : .description_bad = "Service may program timers that wake up the system",
1068 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1069 : : .weight = 25,
1070 : : .range = 1,
1071 : : .assess = assess_capability_bounding_set,
1072 : : .parameter = (UINT64_C(1) << CAP_WAKE_ALARM),
1073 : : },
1074 : : {
1075 : : .id = "CapabilityBoundingSet=~CAP_LEASE",
1076 : : .description_good = "Service cannot create file leases",
1077 : : .description_bad = "Service may create file leases",
1078 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1079 : : .weight = 25,
1080 : : .range = 1,
1081 : : .assess = assess_capability_bounding_set,
1082 : : .parameter = (UINT64_C(1) << CAP_LEASE),
1083 : : },
1084 : : {
1085 : : .id = "CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG",
1086 : : .description_good = "Service cannot issue vhangup()",
1087 : : .description_bad = "Service may issue vhangup()",
1088 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1089 : : .weight = 25,
1090 : : .range = 1,
1091 : : .assess = assess_capability_bounding_set,
1092 : : .parameter = (UINT64_C(1) << CAP_SYS_TTY_CONFIG),
1093 : : },
1094 : : {
1095 : : .id = "CapabilityBoundingSet=~CAP_SYS_PACCT",
1096 : : .description_good = "Service cannot use acct()",
1097 : : .description_bad = "Service may use acct()",
1098 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1099 : : .weight = 25,
1100 : : .range = 1,
1101 : : .assess = assess_capability_bounding_set,
1102 : : .parameter = (UINT64_C(1) << CAP_SYS_PACCT),
1103 : : },
1104 : : {
1105 : : .id = "UMask=",
1106 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#UMask=",
1107 : : .weight = 100,
1108 : : .range = 10,
1109 : : .assess = assess_umask,
1110 : : },
1111 : : {
1112 : : .id = "KeyringMode=",
1113 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#KeyringMode=",
1114 : : .description_good = "Service doesn't share key material with other services",
1115 : : .description_bad = "Service shares key material with other service",
1116 : : .weight = 1000,
1117 : : .range = 1,
1118 : : .assess = assess_keyring_mode,
1119 : : },
1120 : : {
1121 : : .id = "NotifyAccess=",
1122 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NotifyAccess=",
1123 : : .description_good = "Service child processes cannot alter service state",
1124 : : .description_bad = "Service child processes may alter service state",
1125 : : .weight = 1000,
1126 : : .range = 1,
1127 : : .assess = assess_notify_access,
1128 : : },
1129 : : {
1130 : : .id = "RemoveIPC=",
1131 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RemoveIPC=",
1132 : : .description_good = "Service user cannot leave SysV IPC objects around",
1133 : : .description_bad = "Service user may leave SysV IPC objects around",
1134 : : .description_na = "Service runs as root, option does not apply",
1135 : : .weight = 100,
1136 : : .range = 1,
1137 : : .assess = assess_remove_ipc,
1138 : : .offset = offsetof(struct security_info, remove_ipc),
1139 : : },
1140 : : {
1141 : : .id = "Delegate=",
1142 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Delegate=",
1143 : : .description_good = "Service does not maintain its own delegated control group subtree",
1144 : : .description_bad = "Service maintains its own delegated control group subtree",
1145 : : .weight = 100,
1146 : : .range = 1,
1147 : : .assess = assess_bool,
1148 : : .offset = offsetof(struct security_info, delegate),
1149 : : .parameter = true, /* invert! */
1150 : : },
1151 : : {
1152 : : .id = "RestrictRealtime=",
1153 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictRealtime=",
1154 : : .description_good = "Service realtime scheduling access is restricted",
1155 : : .description_bad = "Service may acquire realtime scheduling",
1156 : : .weight = 500,
1157 : : .range = 1,
1158 : : .assess = assess_bool,
1159 : : .offset = offsetof(struct security_info, restrict_realtime),
1160 : : },
1161 : : {
1162 : : .id = "RestrictSUIDSGID=",
1163 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictSUIDSGID=",
1164 : : .description_good = "SUID/SGID file creation by service is restricted",
1165 : : .description_bad = "Service may create SUID/SGID files",
1166 : : .weight = 1000,
1167 : : .range = 1,
1168 : : .assess = assess_bool,
1169 : : .offset = offsetof(struct security_info, restrict_suid_sgid),
1170 : : },
1171 : : {
1172 : : .id = "RestrictNamespaces=~CLONE_NEWUSER",
1173 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1174 : : .description_good = "Service cannot create user namespaces",
1175 : : .description_bad = "Service may create user namespaces",
1176 : : .weight = 1500,
1177 : : .range = 1,
1178 : : .assess = assess_restrict_namespaces,
1179 : : .parameter = CLONE_NEWUSER,
1180 : : },
1181 : : {
1182 : : .id = "RestrictNamespaces=~CLONE_NEWNS",
1183 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1184 : : .description_good = "Service cannot create file system namespaces",
1185 : : .description_bad = "Service may create file system namespaces",
1186 : : .weight = 500,
1187 : : .range = 1,
1188 : : .assess = assess_restrict_namespaces,
1189 : : .parameter = CLONE_NEWNS,
1190 : : },
1191 : : {
1192 : : .id = "RestrictNamespaces=~CLONE_NEWIPC",
1193 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1194 : : .description_good = "Service cannot create IPC namespaces",
1195 : : .description_bad = "Service may create IPC namespaces",
1196 : : .weight = 500,
1197 : : .range = 1,
1198 : : .assess = assess_restrict_namespaces,
1199 : : .parameter = CLONE_NEWIPC,
1200 : : },
1201 : : {
1202 : : .id = "RestrictNamespaces=~CLONE_NEWPID",
1203 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1204 : : .description_good = "Service cannot create process namespaces",
1205 : : .description_bad = "Service may create process namespaces",
1206 : : .weight = 500,
1207 : : .range = 1,
1208 : : .assess = assess_restrict_namespaces,
1209 : : .parameter = CLONE_NEWPID,
1210 : : },
1211 : : {
1212 : : .id = "RestrictNamespaces=~CLONE_NEWCGROUP",
1213 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1214 : : .description_good = "Service cannot create cgroup namespaces",
1215 : : .description_bad = "Service may create cgroup namespaces",
1216 : : .weight = 500,
1217 : : .range = 1,
1218 : : .assess = assess_restrict_namespaces,
1219 : : .parameter = CLONE_NEWCGROUP,
1220 : : },
1221 : : {
1222 : : .id = "RestrictNamespaces=~CLONE_NEWNET",
1223 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1224 : : .description_good = "Service cannot create network namespaces",
1225 : : .description_bad = "Service may create network namespaces",
1226 : : .weight = 500,
1227 : : .range = 1,
1228 : : .assess = assess_restrict_namespaces,
1229 : : .parameter = CLONE_NEWNET,
1230 : : },
1231 : : {
1232 : : .id = "RestrictNamespaces=~CLONE_NEWUTS",
1233 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1234 : : .description_good = "Service cannot create hostname namespaces",
1235 : : .description_bad = "Service may create hostname namespaces",
1236 : : .weight = 100,
1237 : : .range = 1,
1238 : : .assess = assess_restrict_namespaces,
1239 : : .parameter = CLONE_NEWUTS,
1240 : : },
1241 : : {
1242 : : .id = "RestrictAddressFamilies=~AF_(INET|INET6)",
1243 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1244 : : .description_good = "Service cannot allocate Internet sockets",
1245 : : .description_bad = "Service may allocate Internet sockets",
1246 : : .weight = 1500,
1247 : : .range = 1,
1248 : : .assess = assess_bool,
1249 : : .offset = offsetof(struct security_info, restrict_address_family_inet),
1250 : : },
1251 : : {
1252 : : .id = "RestrictAddressFamilies=~AF_UNIX",
1253 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1254 : : .description_good = "Service cannot allocate local sockets",
1255 : : .description_bad = "Service may allocate local sockets",
1256 : : .weight = 25,
1257 : : .range = 1,
1258 : : .assess = assess_bool,
1259 : : .offset = offsetof(struct security_info, restrict_address_family_unix),
1260 : : },
1261 : : {
1262 : : .id = "RestrictAddressFamilies=~AF_NETLINK",
1263 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1264 : : .description_good = "Service cannot allocate netlink sockets",
1265 : : .description_bad = "Service may allocate netlink sockets",
1266 : : .weight = 200,
1267 : : .range = 1,
1268 : : .assess = assess_bool,
1269 : : .offset = offsetof(struct security_info, restrict_address_family_netlink),
1270 : : },
1271 : : {
1272 : : .id = "RestrictAddressFamilies=~AF_PACKET",
1273 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1274 : : .description_good = "Service cannot allocate packet sockets",
1275 : : .description_bad = "Service may allocate packet sockets",
1276 : : .weight = 1000,
1277 : : .range = 1,
1278 : : .assess = assess_bool,
1279 : : .offset = offsetof(struct security_info, restrict_address_family_packet),
1280 : : },
1281 : : {
1282 : : .id = "RestrictAddressFamilies=~…",
1283 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1284 : : .description_good = "Service cannot allocate exotic sockets",
1285 : : .description_bad = "Service may allocate exotic sockets",
1286 : : .weight = 1250,
1287 : : .range = 1,
1288 : : .assess = assess_bool,
1289 : : .offset = offsetof(struct security_info, restrict_address_family_other),
1290 : : },
1291 : : {
1292 : : .id = "SystemCallArchitectures=",
1293 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=",
1294 : : .weight = 1000,
1295 : : .range = 10,
1296 : : .assess = assess_system_call_architectures,
1297 : : },
1298 : : #if HAVE_SECCOMP
1299 : : {
1300 : : .id = "SystemCallFilter=~@swap",
1301 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1302 : : .weight = 1000,
1303 : : .range = 10,
1304 : : .assess = assess_system_call_filter,
1305 : : .parameter = SYSCALL_FILTER_SET_SWAP,
1306 : : },
1307 : : {
1308 : : .id = "SystemCallFilter=~@obsolete",
1309 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1310 : : .weight = 250,
1311 : : .range = 10,
1312 : : .assess = assess_system_call_filter,
1313 : : .parameter = SYSCALL_FILTER_SET_OBSOLETE,
1314 : : },
1315 : : {
1316 : : .id = "SystemCallFilter=~@clock",
1317 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1318 : : .weight = 1000,
1319 : : .range = 10,
1320 : : .assess = assess_system_call_filter,
1321 : : .parameter = SYSCALL_FILTER_SET_CLOCK,
1322 : : },
1323 : : {
1324 : : .id = "SystemCallFilter=~@cpu-emulation",
1325 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1326 : : .weight = 250,
1327 : : .range = 10,
1328 : : .assess = assess_system_call_filter,
1329 : : .parameter = SYSCALL_FILTER_SET_CPU_EMULATION,
1330 : : },
1331 : : {
1332 : : .id = "SystemCallFilter=~@debug",
1333 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1334 : : .weight = 1000,
1335 : : .range = 10,
1336 : : .assess = assess_system_call_filter,
1337 : : .parameter = SYSCALL_FILTER_SET_DEBUG,
1338 : : },
1339 : : {
1340 : : .id = "SystemCallFilter=~@mount",
1341 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1342 : : .weight = 1000,
1343 : : .range = 10,
1344 : : .assess = assess_system_call_filter,
1345 : : .parameter = SYSCALL_FILTER_SET_MOUNT,
1346 : : },
1347 : : {
1348 : : .id = "SystemCallFilter=~@module",
1349 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1350 : : .weight = 1000,
1351 : : .range = 10,
1352 : : .assess = assess_system_call_filter,
1353 : : .parameter = SYSCALL_FILTER_SET_MODULE,
1354 : : },
1355 : : {
1356 : : .id = "SystemCallFilter=~@raw-io",
1357 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1358 : : .weight = 1000,
1359 : : .range = 10,
1360 : : .assess = assess_system_call_filter,
1361 : : .parameter = SYSCALL_FILTER_SET_RAW_IO,
1362 : : },
1363 : : {
1364 : : .id = "SystemCallFilter=~@reboot",
1365 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1366 : : .weight = 1000,
1367 : : .range = 10,
1368 : : .assess = assess_system_call_filter,
1369 : : .parameter = SYSCALL_FILTER_SET_REBOOT,
1370 : : },
1371 : : {
1372 : : .id = "SystemCallFilter=~@privileged",
1373 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1374 : : .weight = 700,
1375 : : .range = 10,
1376 : : .assess = assess_system_call_filter,
1377 : : .parameter = SYSCALL_FILTER_SET_PRIVILEGED,
1378 : : },
1379 : : {
1380 : : .id = "SystemCallFilter=~@resources",
1381 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1382 : : .weight = 700,
1383 : : .range = 10,
1384 : : .assess = assess_system_call_filter,
1385 : : .parameter = SYSCALL_FILTER_SET_RESOURCES,
1386 : : },
1387 : : #endif
1388 : : {
1389 : : .id = "IPAddressDeny=",
1390 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#IPAddressDeny=",
1391 : : .weight = 1000,
1392 : : .range = 10,
1393 : : .assess = assess_ip_address_allow,
1394 : : },
1395 : : {
1396 : : .id = "DeviceAllow=",
1397 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DeviceAllow=",
1398 : : .weight = 1000,
1399 : : .range = 10,
1400 : : .assess = assess_device_allow,
1401 : : },
1402 : : {
1403 : : .id = "AmbientCapabilities=",
1404 : : .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#AmbientCapabilities=",
1405 : : .description_good = "Service process does not receive ambient capabilities",
1406 : : .description_bad = "Service process receives ambient capabilities",
1407 : : .weight = 500,
1408 : : .range = 1,
1409 : : .assess = assess_ambient_capabilities,
1410 : : },
1411 : : };
1412 : :
1413 : 0 : static int assess(const struct security_info *info, Table *overview_table, AnalyzeSecurityFlags flags) {
1414 : : static const struct {
1415 : : uint64_t exposure;
1416 : : const char *name;
1417 : : const char *color;
1418 : : SpecialGlyph smiley;
1419 : : } badness_table[] = {
1420 : : { 100, "DANGEROUS", ANSI_HIGHLIGHT_RED, SPECIAL_GLYPH_DEPRESSED_SMILEY },
1421 : : { 90, "UNSAFE", ANSI_HIGHLIGHT_RED, SPECIAL_GLYPH_UNHAPPY_SMILEY },
1422 : : { 75, "EXPOSED", ANSI_HIGHLIGHT_YELLOW, SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY },
1423 : : { 50, "MEDIUM", NULL, SPECIAL_GLYPH_NEUTRAL_SMILEY },
1424 : : { 10, "OK", ANSI_HIGHLIGHT_GREEN, SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY },
1425 : : { 1, "SAFE", ANSI_HIGHLIGHT_GREEN, SPECIAL_GLYPH_HAPPY_SMILEY },
1426 : : { 0, "PERFECT", ANSI_HIGHLIGHT_GREEN, SPECIAL_GLYPH_ECSTATIC_SMILEY },
1427 : : };
1428 : :
1429 : 0 : uint64_t badness_sum = 0, weight_sum = 0, exposure;
1430 : 0 : _cleanup_(table_unrefp) Table *details_table = NULL;
1431 : : size_t i;
1432 : : int r;
1433 : :
1434 [ # # # # ]: 0 : if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
1435 : 0 : details_table = table_new(" ", "name", "description", "weight", "badness", "range", "exposure");
1436 [ # # ]: 0 : if (!details_table)
1437 : 0 : return log_oom();
1438 : :
1439 : 0 : (void) table_set_sort(details_table, 3, 1, (size_t) -1);
1440 : 0 : (void) table_set_reverse(details_table, 3, true);
1441 : :
1442 [ # # ]: 0 : if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
1443 : 0 : (void) table_set_display(details_table, 0, 1, 2, 6, (size_t) -1);
1444 : : }
1445 : :
1446 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
1447 : 0 : const struct security_assessor *a = security_assessor_table + i;
1448 [ # # ]: 0 : _cleanup_free_ char *d = NULL;
1449 : : uint64_t badness;
1450 : : void *data;
1451 : :
1452 : 0 : data = (uint8_t *) info + a->offset;
1453 : :
1454 [ # # # # ]: 0 : if (a->default_dependencies_only && !info->default_dependencies) {
1455 : 0 : badness = UINT64_MAX;
1456 : 0 : d = strdup("Service runs in special boot phase, option does not apply");
1457 [ # # ]: 0 : if (!d)
1458 : 0 : return log_oom();
1459 : : } else {
1460 : 0 : r = a->assess(a, info, data, &badness, &d);
1461 [ # # ]: 0 : if (r < 0)
1462 : 0 : return r;
1463 : : }
1464 : :
1465 [ # # ]: 0 : assert(a->range > 0);
1466 : :
1467 [ # # ]: 0 : if (badness != UINT64_MAX) {
1468 [ # # ]: 0 : assert(badness <= a->range);
1469 : :
1470 : 0 : badness_sum += DIV_ROUND_UP(badness * a->weight, a->range);
1471 : 0 : weight_sum += a->weight;
1472 : : }
1473 : :
1474 [ # # ]: 0 : if (details_table) {
1475 : 0 : const char *checkmark, *description, *color = NULL;
1476 : : TableCell *cell;
1477 : :
1478 [ # # ]: 0 : if (badness == UINT64_MAX) {
1479 : 0 : checkmark = " ";
1480 : 0 : description = a->description_na;
1481 : 0 : color = NULL;
1482 [ # # ]: 0 : } else if (badness == a->range) {
1483 : 0 : checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
1484 : 0 : description = a->description_bad;
1485 : 0 : color = ansi_highlight_red();
1486 [ # # ]: 0 : } else if (badness == 0) {
1487 : 0 : checkmark = special_glyph(SPECIAL_GLYPH_CHECK_MARK);
1488 : 0 : description = a->description_good;
1489 : 0 : color = ansi_highlight_green();
1490 : : } else {
1491 : 0 : checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK);
1492 : 0 : description = NULL;
1493 : 0 : color = ansi_highlight_red();
1494 : : }
1495 : :
1496 [ # # ]: 0 : if (d)
1497 : 0 : description = d;
1498 : :
1499 : 0 : r = table_add_cell_full(details_table, &cell, TABLE_STRING, checkmark, 1, 1, 0, 0, 0);
1500 [ # # ]: 0 : if (r < 0)
1501 [ # # ]: 0 : return log_error_errno(r, "Failed to add cell to table: %m");
1502 [ # # ]: 0 : if (color)
1503 : 0 : (void) table_set_color(details_table, cell, color);
1504 : :
1505 : 0 : r = table_add_many(details_table,
1506 : : TABLE_STRING, a->id, TABLE_SET_URL, a->url,
1507 : : TABLE_STRING, description,
1508 : : TABLE_UINT64, a->weight, TABLE_SET_ALIGN_PERCENT, 100,
1509 : : TABLE_UINT64, badness, TABLE_SET_ALIGN_PERCENT, 100,
1510 : : TABLE_UINT64, a->range, TABLE_SET_ALIGN_PERCENT, 100,
1511 : : TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100);
1512 [ # # ]: 0 : if (r < 0)
1513 [ # # ]: 0 : return log_error_errno(r, "Failed to add cells to table: %m");
1514 : : }
1515 : : }
1516 : :
1517 [ # # ]: 0 : assert(weight_sum > 0);
1518 : :
1519 [ # # ]: 0 : if (details_table) {
1520 : : size_t row;
1521 : :
1522 [ # # ]: 0 : for (row = 1; row < table_get_rows(details_table); row++) {
1523 : : char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
1524 : : const uint64_t *weight, *badness, *range;
1525 : : TableCell *cell;
1526 : : uint64_t x;
1527 : :
1528 [ # # ]: 0 : assert_se(weight = table_get_at(details_table, row, 3));
1529 [ # # ]: 0 : assert_se(badness = table_get_at(details_table, row, 4));
1530 [ # # ]: 0 : assert_se(range = table_get_at(details_table, row, 5));
1531 : :
1532 [ # # # # ]: 0 : if (*badness == UINT64_MAX || *badness == 0)
1533 : 0 : continue;
1534 : :
1535 [ # # ]: 0 : assert_se(cell = table_get_cell(details_table, row, 6));
1536 : :
1537 : 0 : x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
1538 [ # # ]: 0 : xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
1539 : :
1540 : 0 : r = table_update(details_table, cell, TABLE_STRING, buf);
1541 [ # # ]: 0 : if (r < 0)
1542 [ # # ]: 0 : return log_error_errno(r, "Failed to update cell in table: %m");
1543 : : }
1544 : :
1545 : 0 : r = table_print(details_table, stdout);
1546 [ # # ]: 0 : if (r < 0)
1547 [ # # ]: 0 : return log_error_errno(r, "Failed to output table: %m");
1548 : : }
1549 : :
1550 : 0 : exposure = DIV_ROUND_UP(badness_sum * 100U, weight_sum);
1551 : :
1552 [ # # ]: 0 : for (i = 0; i < ELEMENTSOF(badness_table); i++)
1553 [ # # ]: 0 : if (exposure >= badness_table[i].exposure)
1554 : 0 : break;
1555 : :
1556 [ # # ]: 0 : assert(i < ELEMENTSOF(badness_table));
1557 : :
1558 [ # # ]: 0 : if (details_table) {
1559 [ # # ]: 0 : _cleanup_free_ char *clickable = NULL;
1560 : : const char *name;
1561 : :
1562 : : /* If we shall output the details table, also print the brief summary underneath */
1563 : :
1564 [ # # ]: 0 : if (info->fragment_path) {
1565 : 0 : r = terminal_urlify_path(info->fragment_path, info->id, &clickable);
1566 [ # # ]: 0 : if (r < 0)
1567 : 0 : return log_oom();
1568 : :
1569 : 0 : name = clickable;
1570 : : } else
1571 : 0 : name = info->id;
1572 : :
1573 [ # # ]: 0 : printf("\n%s %sOverall exposure level for %s%s: %s%" PRIu64 ".%" PRIu64 " %s%s %s\n",
1574 : : special_glyph(SPECIAL_GLYPH_ARROW),
1575 : : ansi_highlight(),
1576 : : name,
1577 : : ansi_normal(),
1578 : 0 : colors_enabled() ? strempty(badness_table[i].color) : "",
1579 : : exposure / 10, exposure % 10,
1580 : : badness_table[i].name,
1581 : : ansi_normal(),
1582 : : special_glyph(badness_table[i].smiley));
1583 : : }
1584 : :
1585 : 0 : fflush(stdout);
1586 : :
1587 [ # # ]: 0 : if (overview_table) {
1588 : : char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
1589 : : TableCell *cell;
1590 : :
1591 : 0 : r = table_add_cell(overview_table, &cell, TABLE_STRING, info->id);
1592 [ # # ]: 0 : if (r < 0)
1593 [ # # ]: 0 : return log_error_errno(r, "Failed to add cell to table: %m");
1594 [ # # ]: 0 : if (info->fragment_path) {
1595 [ # # ]: 0 : _cleanup_free_ char *url = NULL;
1596 : :
1597 : 0 : r = file_url_from_path(info->fragment_path, &url);
1598 [ # # ]: 0 : if (r < 0)
1599 [ # # ]: 0 : return log_error_errno(r, "Failed to generate URL from path: %m");
1600 : :
1601 : 0 : (void) table_set_url(overview_table, cell, url);
1602 : : }
1603 : :
1604 [ # # ]: 0 : xsprintf(buf, "%" PRIu64 ".%" PRIu64, exposure / 10, exposure % 10);
1605 : 0 : r = table_add_cell(overview_table, &cell, TABLE_STRING, buf);
1606 [ # # ]: 0 : if (r < 0)
1607 [ # # ]: 0 : return log_error_errno(r, "Failed to add cell to table: %m");
1608 : 0 : (void) table_set_align_percent(overview_table, cell, 100);
1609 : :
1610 : 0 : r = table_add_cell(overview_table, &cell, TABLE_STRING, badness_table[i].name);
1611 [ # # ]: 0 : if (r < 0)
1612 [ # # ]: 0 : return log_error_errno(r, "Failed to add cell to table: %m");
1613 : 0 : (void) table_set_color(overview_table, cell, strempty(badness_table[i].color));
1614 : :
1615 : 0 : r = table_add_cell(overview_table, NULL, TABLE_STRING, special_glyph(badness_table[i].smiley));
1616 [ # # ]: 0 : if (r < 0)
1617 [ # # ]: 0 : return log_error_errno(r, "Failed to add cell to table: %m");
1618 : : }
1619 : :
1620 : 0 : return 0;
1621 : : }
1622 : :
1623 : 0 : static int property_read_restrict_address_families(
1624 : : sd_bus *bus,
1625 : : const char *member,
1626 : : sd_bus_message *m,
1627 : : sd_bus_error *error,
1628 : : void *userdata) {
1629 : :
1630 : 0 : struct security_info *info = userdata;
1631 : : int whitelist, r;
1632 : :
1633 [ # # ]: 0 : assert(bus);
1634 [ # # ]: 0 : assert(member);
1635 [ # # ]: 0 : assert(m);
1636 : :
1637 : 0 : r = sd_bus_message_enter_container(m, 'r', "bas");
1638 [ # # ]: 0 : if (r < 0)
1639 : 0 : return r;
1640 : :
1641 : 0 : r = sd_bus_message_read(m, "b", &whitelist);
1642 [ # # ]: 0 : if (r < 0)
1643 : 0 : return r;
1644 : :
1645 : 0 : info->restrict_address_family_inet =
1646 : 0 : info->restrict_address_family_unix =
1647 : 0 : info->restrict_address_family_netlink =
1648 : 0 : info->restrict_address_family_packet =
1649 : 0 : info->restrict_address_family_other = whitelist;
1650 : :
1651 : 0 : r = sd_bus_message_enter_container(m, 'a', "s");
1652 [ # # ]: 0 : if (r < 0)
1653 : 0 : return r;
1654 : :
1655 : 0 : for (;;) {
1656 : : const char *name;
1657 : :
1658 : 0 : r = sd_bus_message_read(m, "s", &name);
1659 [ # # ]: 0 : if (r < 0)
1660 : 0 : return r;
1661 [ # # ]: 0 : if (r == 0)
1662 : 0 : break;
1663 : :
1664 [ # # ]: 0 : if (STR_IN_SET(name, "AF_INET", "AF_INET6"))
1665 : 0 : info->restrict_address_family_inet = !whitelist;
1666 [ # # ]: 0 : else if (streq(name, "AF_UNIX"))
1667 : 0 : info->restrict_address_family_unix = !whitelist;
1668 [ # # ]: 0 : else if (streq(name, "AF_NETLINK"))
1669 : 0 : info->restrict_address_family_netlink = !whitelist;
1670 [ # # ]: 0 : else if (streq(name, "AF_PACKET"))
1671 : 0 : info->restrict_address_family_packet = !whitelist;
1672 : : else
1673 : 0 : info->restrict_address_family_other = !whitelist;
1674 : : }
1675 : :
1676 : 0 : r = sd_bus_message_exit_container(m);
1677 [ # # ]: 0 : if (r < 0)
1678 : 0 : return r;
1679 : :
1680 : 0 : return sd_bus_message_exit_container(m);
1681 : : }
1682 : :
1683 : 0 : static int property_read_system_call_filter(
1684 : : sd_bus *bus,
1685 : : const char *member,
1686 : : sd_bus_message *m,
1687 : : sd_bus_error *error,
1688 : : void *userdata) {
1689 : :
1690 : 0 : struct security_info *info = userdata;
1691 : : int whitelist, r;
1692 : :
1693 [ # # ]: 0 : assert(bus);
1694 [ # # ]: 0 : assert(member);
1695 [ # # ]: 0 : assert(m);
1696 : :
1697 : 0 : r = sd_bus_message_enter_container(m, 'r', "bas");
1698 [ # # ]: 0 : if (r < 0)
1699 : 0 : return r;
1700 : :
1701 : 0 : r = sd_bus_message_read(m, "b", &whitelist);
1702 [ # # ]: 0 : if (r < 0)
1703 : 0 : return r;
1704 : :
1705 : 0 : info->system_call_filter_whitelist = whitelist;
1706 : :
1707 : 0 : r = sd_bus_message_enter_container(m, 'a', "s");
1708 [ # # ]: 0 : if (r < 0)
1709 : 0 : return r;
1710 : :
1711 : 0 : for (;;) {
1712 : : const char *name;
1713 : :
1714 : 0 : r = sd_bus_message_read(m, "s", &name);
1715 [ # # ]: 0 : if (r < 0)
1716 : 0 : return r;
1717 [ # # ]: 0 : if (r == 0)
1718 : 0 : break;
1719 : :
1720 : 0 : r = set_ensure_allocated(&info->system_call_filter, &string_hash_ops);
1721 [ # # ]: 0 : if (r < 0)
1722 : 0 : return r;
1723 : :
1724 : 0 : r = set_put_strdup(info->system_call_filter, name);
1725 [ # # ]: 0 : if (r < 0)
1726 : 0 : return r;
1727 : : }
1728 : :
1729 : 0 : r = sd_bus_message_exit_container(m);
1730 [ # # ]: 0 : if (r < 0)
1731 : 0 : return r;
1732 : :
1733 : 0 : return sd_bus_message_exit_container(m);
1734 : : }
1735 : :
1736 : 0 : static int property_read_ip_address_allow(
1737 : : sd_bus *bus,
1738 : : const char *member,
1739 : : sd_bus_message *m,
1740 : : sd_bus_error *error,
1741 : : void *userdata) {
1742 : :
1743 : 0 : struct security_info *info = userdata;
1744 : 0 : bool deny_ipv4 = false, deny_ipv6 = false;
1745 : : int r;
1746 : :
1747 [ # # ]: 0 : assert(bus);
1748 [ # # ]: 0 : assert(member);
1749 [ # # ]: 0 : assert(m);
1750 : :
1751 : 0 : r = sd_bus_message_enter_container(m, 'a', "(iayu)");
1752 [ # # ]: 0 : if (r < 0)
1753 : 0 : return r;
1754 : :
1755 : 0 : for (;;) {
1756 : : const void *data;
1757 : : size_t size;
1758 : : int32_t family;
1759 : : uint32_t prefixlen;
1760 : :
1761 : 0 : r = sd_bus_message_enter_container(m, 'r', "iayu");
1762 [ # # ]: 0 : if (r < 0)
1763 : 0 : return r;
1764 [ # # ]: 0 : if (r == 0)
1765 : 0 : break;
1766 : :
1767 : 0 : r = sd_bus_message_read(m, "i", &family);
1768 [ # # ]: 0 : if (r < 0)
1769 : 0 : return r;
1770 : :
1771 : 0 : r = sd_bus_message_read_array(m, 'y', &data, &size);
1772 [ # # ]: 0 : if (r < 0)
1773 : 0 : return r;
1774 : :
1775 : 0 : r = sd_bus_message_read(m, "u", &prefixlen);
1776 [ # # ]: 0 : if (r < 0)
1777 : 0 : return r;
1778 : :
1779 : 0 : r = sd_bus_message_exit_container(m);
1780 [ # # ]: 0 : if (r < 0)
1781 : 0 : return r;
1782 : :
1783 [ # # ]: 0 : if (streq(member, "IPAddressAllow")) {
1784 : : union in_addr_union u;
1785 : :
1786 [ # # # # : 0 : if (family == AF_INET && size == 4 && prefixlen == 8)
# # ]
1787 : 0 : memcpy(&u.in, data, size);
1788 [ # # # # : 0 : else if (family == AF_INET6 && size == 16 && prefixlen == 128)
# # ]
1789 : 0 : memcpy(&u.in6, data, size);
1790 : : else {
1791 : 0 : info->ip_address_allow_other = true;
1792 : 0 : continue;
1793 : : }
1794 : :
1795 [ # # ]: 0 : if (in_addr_is_localhost(family, &u))
1796 : 0 : info->ip_address_allow_localhost = true;
1797 : : else
1798 : 0 : info->ip_address_allow_other = true;
1799 : : } else {
1800 [ # # ]: 0 : assert(streq(member, "IPAddressDeny"));
1801 : :
1802 [ # # # # : 0 : if (family == AF_INET && size == 4 && prefixlen == 0)
# # ]
1803 : 0 : deny_ipv4 = true;
1804 [ # # # # : 0 : else if (family == AF_INET6 && size == 16 && prefixlen == 0)
# # ]
1805 : 0 : deny_ipv6 = true;
1806 : : }
1807 : : }
1808 : :
1809 [ # # # # ]: 0 : info->ip_address_deny_all = deny_ipv4 && deny_ipv6;
1810 : :
1811 : 0 : return sd_bus_message_exit_container(m);
1812 : : }
1813 : :
1814 : 0 : static int property_read_ip_filters(
1815 : : sd_bus *bus,
1816 : : const char *member,
1817 : : sd_bus_message *m,
1818 : : sd_bus_error *error,
1819 : : void *userdata) {
1820 : :
1821 : 0 : struct security_info *info = userdata;
1822 : 0 : _cleanup_(strv_freep) char **l = NULL;
1823 : : int r;
1824 : :
1825 [ # # ]: 0 : assert(bus);
1826 [ # # ]: 0 : assert(member);
1827 [ # # ]: 0 : assert(m);
1828 : :
1829 : 0 : r = sd_bus_message_read_strv(m, &l);
1830 [ # # ]: 0 : if (r < 0)
1831 : 0 : return r;
1832 : :
1833 [ # # ]: 0 : if (streq(member, "IPIngressFilterPath"))
1834 : 0 : info->ip_filters_custom_ingress = !strv_isempty(l);
1835 [ # # ]: 0 : else if (streq(member, "IPEgressFilterPath"))
1836 : 0 : info->ip_filters_custom_ingress = !strv_isempty(l);
1837 : :
1838 : 0 : return 0;
1839 : : }
1840 : :
1841 : 0 : static int property_read_device_allow(
1842 : : sd_bus *bus,
1843 : : const char *member,
1844 : : sd_bus_message *m,
1845 : : sd_bus_error *error,
1846 : : void *userdata) {
1847 : :
1848 : 0 : struct security_info *info = userdata;
1849 : 0 : size_t n = 0;
1850 : : int r;
1851 : :
1852 [ # # ]: 0 : assert(bus);
1853 [ # # ]: 0 : assert(member);
1854 [ # # ]: 0 : assert(m);
1855 : :
1856 : 0 : r = sd_bus_message_enter_container(m, 'a', "(ss)");
1857 [ # # ]: 0 : if (r < 0)
1858 : 0 : return r;
1859 : :
1860 : 0 : for (;;) {
1861 : : const char *name, *policy;
1862 : :
1863 : 0 : r = sd_bus_message_read(m, "(ss)", &name, &policy);
1864 [ # # ]: 0 : if (r < 0)
1865 : 0 : return r;
1866 [ # # ]: 0 : if (r == 0)
1867 : 0 : break;
1868 : :
1869 : 0 : n++;
1870 : : }
1871 : :
1872 : 0 : info->device_allow_non_empty = n > 0;
1873 : :
1874 : 0 : return sd_bus_message_exit_container(m);
1875 : : }
1876 : :
1877 : 0 : static int acquire_security_info(sd_bus *bus, const char *name, struct security_info *info, AnalyzeSecurityFlags flags) {
1878 : :
1879 : : static const struct bus_properties_map security_map[] = {
1880 : : { "AmbientCapabilities", "t", NULL, offsetof(struct security_info, ambient_capabilities) },
1881 : : { "CapabilityBoundingSet", "t", NULL, offsetof(struct security_info, capability_bounding_set) },
1882 : : { "DefaultDependencies", "b", NULL, offsetof(struct security_info, default_dependencies) },
1883 : : { "Delegate", "b", NULL, offsetof(struct security_info, delegate) },
1884 : : { "DeviceAllow", "a(ss)", property_read_device_allow, 0 },
1885 : : { "DevicePolicy", "s", NULL, offsetof(struct security_info, device_policy) },
1886 : : { "DynamicUser", "b", NULL, offsetof(struct security_info, dynamic_user) },
1887 : : { "FragmentPath", "s", NULL, offsetof(struct security_info, fragment_path) },
1888 : : { "IPAddressAllow", "a(iayu)", property_read_ip_address_allow, 0 },
1889 : : { "IPAddressDeny", "a(iayu)", property_read_ip_address_allow, 0 },
1890 : : { "IPIngressFilterPath", "as", property_read_ip_filters, 0 },
1891 : : { "IPEgressFilterPath", "as", property_read_ip_filters, 0 },
1892 : : { "Id", "s", NULL, offsetof(struct security_info, id) },
1893 : : { "KeyringMode", "s", NULL, offsetof(struct security_info, keyring_mode) },
1894 : : { "LoadState", "s", NULL, offsetof(struct security_info, load_state) },
1895 : : { "LockPersonality", "b", NULL, offsetof(struct security_info, lock_personality) },
1896 : : { "MemoryDenyWriteExecute", "b", NULL, offsetof(struct security_info, memory_deny_write_execute) },
1897 : : { "NoNewPrivileges", "b", NULL, offsetof(struct security_info, no_new_privileges) },
1898 : : { "NotifyAccess", "s", NULL, offsetof(struct security_info, notify_access) },
1899 : : { "PrivateDevices", "b", NULL, offsetof(struct security_info, private_devices) },
1900 : : { "PrivateMounts", "b", NULL, offsetof(struct security_info, private_mounts) },
1901 : : { "PrivateNetwork", "b", NULL, offsetof(struct security_info, private_network) },
1902 : : { "PrivateTmp", "b", NULL, offsetof(struct security_info, private_tmp) },
1903 : : { "PrivateUsers", "b", NULL, offsetof(struct security_info, private_users) },
1904 : : { "ProtectControlGroups", "b", NULL, offsetof(struct security_info, protect_control_groups) },
1905 : : { "ProtectHome", "s", NULL, offsetof(struct security_info, protect_home) },
1906 : : { "ProtectHostname", "b", NULL, offsetof(struct security_info, protect_hostname) },
1907 : : { "ProtectKernelModules", "b", NULL, offsetof(struct security_info, protect_kernel_modules) },
1908 : : { "ProtectKernelTunables", "b", NULL, offsetof(struct security_info, protect_kernel_tunables) },
1909 : : { "ProtectSystem", "s", NULL, offsetof(struct security_info, protect_system) },
1910 : : { "RemoveIPC", "b", NULL, offsetof(struct security_info, remove_ipc) },
1911 : : { "RestrictAddressFamilies", "(bas)", property_read_restrict_address_families, 0 },
1912 : : { "RestrictNamespaces", "t", NULL, offsetof(struct security_info, restrict_namespaces) },
1913 : : { "RestrictRealtime", "b", NULL, offsetof(struct security_info, restrict_realtime) },
1914 : : { "RestrictSUIDSGID", "b", NULL, offsetof(struct security_info, restrict_suid_sgid) },
1915 : : { "RootDirectory", "s", NULL, offsetof(struct security_info, root_directory) },
1916 : : { "RootImage", "s", NULL, offsetof(struct security_info, root_image) },
1917 : : { "SupplementaryGroups", "as", NULL, offsetof(struct security_info, supplementary_groups) },
1918 : : { "SystemCallArchitectures", "as", NULL, offsetof(struct security_info, system_call_architectures) },
1919 : : { "SystemCallFilter", "(as)", property_read_system_call_filter, 0 },
1920 : : { "Type", "s", NULL, offsetof(struct security_info, type) },
1921 : : { "UMask", "u", NULL, offsetof(struct security_info, _umask) },
1922 : : { "User", "s", NULL, offsetof(struct security_info, user) },
1923 : : {}
1924 : : };
1925 : :
1926 : 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
1927 : 0 : _cleanup_free_ char *path = NULL;
1928 : : int r;
1929 : :
1930 : : /* Note: this mangles *info on failure! */
1931 : :
1932 [ # # ]: 0 : assert(bus);
1933 [ # # ]: 0 : assert(name);
1934 [ # # ]: 0 : assert(info);
1935 : :
1936 : 0 : path = unit_dbus_path_from_name(name);
1937 [ # # ]: 0 : if (!path)
1938 : 0 : return log_oom();
1939 : :
1940 : 0 : r = bus_map_all_properties(
1941 : : bus,
1942 : : "org.freedesktop.systemd1",
1943 : : path,
1944 : : security_map,
1945 : : BUS_MAP_STRDUP | BUS_MAP_BOOLEAN_AS_BOOL,
1946 : : &error,
1947 : : NULL,
1948 : : info);
1949 [ # # ]: 0 : if (r < 0)
1950 [ # # ]: 0 : return log_error_errno(r, "Failed to get unit properties: %s", bus_error_message(&error, r));
1951 : :
1952 [ # # ]: 0 : if (!streq_ptr(info->load_state, "loaded")) {
1953 : :
1954 [ # # ]: 0 : if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LOADED))
1955 : 0 : return -EMEDIUMTYPE;
1956 : :
1957 [ # # ]: 0 : if (streq_ptr(info->load_state, "not-found"))
1958 [ # # ]: 0 : log_error("Unit %s not found, cannot analyze.", name);
1959 [ # # ]: 0 : else if (streq_ptr(info->load_state, "masked"))
1960 [ # # ]: 0 : log_error("Unit %s is masked, cannot analyze.", name);
1961 : : else
1962 [ # # ]: 0 : log_error("Unit %s not loaded properly, cannot analyze.", name);
1963 : :
1964 : 0 : return -EINVAL;
1965 : : }
1966 : :
1967 [ # # # # ]: 0 : if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LONG_RUNNING) && streq_ptr(info->type, "oneshot"))
1968 : 0 : return -EMEDIUMTYPE;
1969 : :
1970 [ # # ]: 0 : if (info->private_devices ||
1971 [ # # ]: 0 : info->private_tmp ||
1972 [ # # ]: 0 : info->protect_control_groups ||
1973 [ # # ]: 0 : info->protect_kernel_tunables ||
1974 [ # # ]: 0 : info->protect_kernel_modules ||
1975 [ # # ]: 0 : !streq_ptr(info->protect_home, "no") ||
1976 [ # # ]: 0 : !streq_ptr(info->protect_system, "no") ||
1977 [ # # ]: 0 : info->root_image)
1978 : 0 : info->private_mounts = true;
1979 : :
1980 [ # # ]: 0 : if (info->protect_kernel_modules)
1981 : 0 : info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYS_MODULE);
1982 : :
1983 [ # # ]: 0 : if (info->private_devices)
1984 : 0 : info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) |
1985 : : (UINT64_C(1) << CAP_SYS_RAWIO));
1986 : :
1987 : 0 : return 0;
1988 : : }
1989 : :
1990 : 0 : static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table, AnalyzeSecurityFlags flags) {
1991 : 0 : _cleanup_(security_info_free) struct security_info info = {
1992 : : .default_dependencies = true,
1993 : : .capability_bounding_set = UINT64_MAX,
1994 : : .restrict_namespaces = UINT64_MAX,
1995 : : ._umask = 0002,
1996 : : };
1997 : : int r;
1998 : :
1999 [ # # ]: 0 : assert(bus);
2000 [ # # ]: 0 : assert(name);
2001 : :
2002 : 0 : r = acquire_security_info(bus, name, &info, flags);
2003 [ # # ]: 0 : if (r == -EMEDIUMTYPE) /* Ignore this one because not loaded or Type is oneshot */
2004 : 0 : return 0;
2005 [ # # ]: 0 : if (r < 0)
2006 : 0 : return r;
2007 : :
2008 : 0 : r = assess(&info, overview_table, flags);
2009 [ # # ]: 0 : if (r < 0)
2010 : 0 : return r;
2011 : :
2012 : 0 : return 0;
2013 : : }
2014 : :
2015 : 0 : int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) {
2016 : 0 : _cleanup_(table_unrefp) Table *overview_table = NULL;
2017 : 0 : int ret = 0, r;
2018 : :
2019 [ # # ]: 0 : assert(bus);
2020 : :
2021 [ # # ]: 0 : if (strv_length(units) != 1) {
2022 : 0 : overview_table = table_new("unit", "exposure", "predicate", "happy");
2023 [ # # ]: 0 : if (!overview_table)
2024 : 0 : return log_oom();
2025 : : }
2026 : :
2027 [ # # ]: 0 : if (strv_isempty(units)) {
2028 [ # # ]: 0 : _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2029 [ # # ]: 0 : _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
2030 [ # # ]: 0 : _cleanup_strv_free_ char **list = NULL;
2031 : 0 : size_t allocated = 0, n = 0;
2032 : : char **i;
2033 : :
2034 : 0 : r = sd_bus_call_method(
2035 : : bus,
2036 : : "org.freedesktop.systemd1",
2037 : : "/org/freedesktop/systemd1",
2038 : : "org.freedesktop.systemd1.Manager",
2039 : : "ListUnits",
2040 : : &error,
2041 : : &reply,
2042 : : NULL);
2043 [ # # ]: 0 : if (r < 0)
2044 [ # # ]: 0 : return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
2045 : :
2046 : 0 : r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
2047 [ # # ]: 0 : if (r < 0)
2048 [ # # ]: 0 : return bus_log_parse_error(r);
2049 : :
2050 : 0 : for (;;) {
2051 : : UnitInfo info;
2052 : 0 : char *copy = NULL;
2053 : :
2054 : 0 : r = bus_parse_unit_info(reply, &info);
2055 [ # # ]: 0 : if (r < 0)
2056 [ # # ]: 0 : return bus_log_parse_error(r);
2057 [ # # ]: 0 : if (r == 0)
2058 : 0 : break;
2059 : :
2060 [ # # ]: 0 : if (!endswith(info.id, ".service"))
2061 : 0 : continue;
2062 : :
2063 [ # # ]: 0 : if (!GREEDY_REALLOC(list, allocated, n + 2))
2064 : 0 : return log_oom();
2065 : :
2066 : 0 : copy = strdup(info.id);
2067 [ # # ]: 0 : if (!copy)
2068 : 0 : return log_oom();
2069 : :
2070 : 0 : list[n++] = copy;
2071 : 0 : list[n] = NULL;
2072 : : }
2073 : :
2074 : 0 : strv_sort(list);
2075 : :
2076 : 0 : flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
2077 : :
2078 [ # # # # ]: 0 : STRV_FOREACH(i, list) {
2079 : 0 : r = analyze_security_one(bus, *i, overview_table, flags);
2080 [ # # # # ]: 0 : if (r < 0 && ret >= 0)
2081 : 0 : ret = r;
2082 : : }
2083 : :
2084 : : } else {
2085 : : char **i;
2086 : :
2087 [ # # # # ]: 0 : STRV_FOREACH(i, units) {
2088 [ # # # # ]: 0 : _cleanup_free_ char *mangled = NULL, *instance = NULL;
2089 : : const char *name;
2090 : :
2091 [ # # # # : 0 : if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT) && i != units) {
# # ]
2092 : 0 : putc('\n', stdout);
2093 : 0 : fflush(stdout);
2094 : : }
2095 : :
2096 : 0 : r = unit_name_mangle_with_suffix(*i, 0, ".service", &mangled);
2097 [ # # ]: 0 : if (r < 0)
2098 [ # # ]: 0 : return log_error_errno(r, "Failed to mangle unit name '%s': %m", *i);
2099 : :
2100 [ # # ]: 0 : if (!endswith(mangled, ".service")) {
2101 [ # # ]: 0 : log_error("Unit %s is not a service unit, refusing.", *i);
2102 : 0 : return -EINVAL;
2103 : : }
2104 : :
2105 [ # # ]: 0 : if (unit_name_is_valid(mangled, UNIT_NAME_TEMPLATE)) {
2106 : 0 : r = unit_name_replace_instance(mangled, "test-instance", &instance);
2107 [ # # ]: 0 : if (r < 0)
2108 : 0 : return log_oom();
2109 : :
2110 : 0 : name = instance;
2111 : : } else
2112 : 0 : name = mangled;
2113 : :
2114 : 0 : r = analyze_security_one(bus, name, overview_table, flags);
2115 [ # # # # ]: 0 : if (r < 0 && ret >= 0)
2116 : 0 : ret = r;
2117 : : }
2118 : : }
2119 : :
2120 [ # # ]: 0 : if (overview_table) {
2121 [ # # # # ]: 0 : if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
2122 : 0 : putc('\n', stdout);
2123 : 0 : fflush(stdout);
2124 : : }
2125 : :
2126 : 0 : r = table_print(overview_table, stdout);
2127 [ # # ]: 0 : if (r < 0)
2128 [ # # ]: 0 : return log_error_errno(r, "Failed to output table: %m");
2129 : : }
2130 : :
2131 : 0 : return ret;
2132 : : }
|