]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/analyze/analyze-security.c
Merge pull request #22531 from poettering/mempcpy-cocci
[thirdparty/systemd.git] / src / analyze / analyze-security.c
CommitLineData
db9ecf05 1/* SPDX-License-Identifier: LGPL-2.1-or-later */
ec16f3b6 2
ec16f3b6
LP
3#include <sys/utsname.h>
4
1624114d 5#include "af-list.h"
ec16f3b6 6#include "analyze-security.h"
bb43d853 7#include "analyze-verify.h"
ec16f3b6 8#include "bus-error.h"
807542be 9#include "bus-map-properties.h"
ec16f3b6
LP
10#include "bus-unit-util.h"
11#include "bus-util.h"
04469211 12#include "copy.h"
ec16f3b6
LP
13#include "env-util.h"
14#include "format-table.h"
84ebe6f0 15#include "in-addr-prefix-util.h"
ec16f3b6
LP
16#include "locale-util.h"
17#include "macro.h"
bb43d853 18#include "manager.h"
f5947a5e
YW
19#include "missing_capability.h"
20#include "missing_sched.h"
04469211 21#include "mkdir.h"
d8b4d14d 22#include "nulstr-util.h"
ec16f3b6
LP
23#include "parse-util.h"
24#include "path-util.h"
25#include "pretty-print.h"
3a5d89fa
WKI
26#if HAVE_SECCOMP
27# include "seccomp-util.h"
28#endif
1624114d 29#include "service.h"
ec16f3b6
LP
30#include "set.h"
31#include "stdio-util.h"
32#include "strv.h"
33#include "terminal-util.h"
34#include "unit-def.h"
35#include "unit-name.h"
1624114d 36#include "unit-serialize.h"
ec16f3b6 37
1624114d 38typedef struct SecurityInfo {
ec16f3b6
LP
39 char *id;
40 char *type;
41 char *load_state;
42 char *fragment_path;
43 bool default_dependencies;
44
45 uint64_t ambient_capabilities;
46 uint64_t capability_bounding_set;
47
48 char *user;
49 char **supplementary_groups;
50 bool dynamic_user;
51
52 bool ip_address_deny_all;
53 bool ip_address_allow_localhost;
54 bool ip_address_allow_other;
55
fab34748
KL
56 bool ip_filters_custom_ingress;
57 bool ip_filters_custom_egress;
58
ec16f3b6 59 char *keyring_mode;
ed125c93
LP
60 char *protect_proc;
61 char *proc_subset;
ec16f3b6
LP
62 bool lock_personality;
63 bool memory_deny_write_execute;
64 bool no_new_privileges;
65 char *notify_access;
527bd7f1 66 bool protect_hostname;
ec16f3b6
LP
67
68 bool private_devices;
69 bool private_mounts;
70 bool private_network;
71 bool private_tmp;
72 bool private_users;
73
74 bool protect_control_groups;
75 bool protect_kernel_modules;
76 bool protect_kernel_tunables;
82dce83b 77 bool protect_kernel_logs;
9f37272a 78 bool protect_clock;
ec16f3b6
LP
79
80 char *protect_home;
81 char *protect_system;
82
83 bool remove_ipc;
84
85 bool restrict_address_family_inet;
86 bool restrict_address_family_unix;
87 bool restrict_address_family_netlink;
88 bool restrict_address_family_packet;
89 bool restrict_address_family_other;
90
1624114d 91 unsigned long long restrict_namespaces;
ec16f3b6 92 bool restrict_realtime;
9d880b70 93 bool restrict_suid_sgid;
ec16f3b6
LP
94
95 char *root_directory;
96 char *root_image;
97
98 bool delegate;
99 char *device_policy;
100 bool device_allow_non_empty;
101
1624114d 102 Set *system_call_architectures;
ec16f3b6 103
6b000af4 104 bool system_call_filter_allow_list;
1624114d 105 Hashmap *system_call_filter;
ec16f3b6 106
1624114d
MG
107 mode_t _umask;
108} SecurityInfo;
ec16f3b6
LP
109
110struct security_assessor {
111 const char *id;
3838d22c 112 const char *json_field;
ec16f3b6
LP
113 const char *description_good;
114 const char *description_bad;
115 const char *description_na;
116 const char *url;
117 uint64_t weight;
118 uint64_t range;
1ace223c
SJ
119 int (*assess)(
120 const struct security_assessor *a,
1624114d 121 const SecurityInfo *info,
1ace223c
SJ
122 const void *data,
123 uint64_t *ret_badness,
124 char **ret_description);
ec16f3b6
LP
125 size_t offset;
126 uint64_t parameter;
127 bool default_dependencies_only;
128};
129
1624114d
MG
130static SecurityInfo *security_info_new(void) {
131 SecurityInfo *info = new(SecurityInfo, 1);
132 if (!info)
133 return NULL;
134
135 *info = (SecurityInfo) {
136 .default_dependencies = true,
137 .capability_bounding_set = UINT64_MAX,
138 .restrict_namespaces = UINT64_MAX,
139 ._umask = 0002,
140 };
141
142 return info;
143}
144
145static SecurityInfo *security_info_free(SecurityInfo *i) {
ec16f3b6 146 if (!i)
1624114d 147 return NULL;
ec16f3b6
LP
148
149 free(i->id);
150 free(i->type);
151 free(i->load_state);
152 free(i->fragment_path);
153
154 free(i->user);
155
156 free(i->protect_home);
157 free(i->protect_system);
158
159 free(i->root_directory);
160 free(i->root_image);
161
162 free(i->keyring_mode);
ed125c93
LP
163 free(i->protect_proc);
164 free(i->proc_subset);
ec16f3b6
LP
165 free(i->notify_access);
166
167 free(i->device_policy);
168
169 strv_free(i->supplementary_groups);
1624114d 170 set_free(i->system_call_architectures);
ec16f3b6 171
1624114d
MG
172 hashmap_free(i->system_call_filter);
173
174 return mfree(i);
ec16f3b6
LP
175}
176
1624114d
MG
177DEFINE_TRIVIAL_CLEANUP_FUNC(SecurityInfo*, security_info_free);
178
179static bool security_info_runs_privileged(const SecurityInfo *i) {
ec16f3b6
LP
180 assert(i);
181
182 if (STRPTR_IN_SET(i->user, "0", "root"))
183 return true;
184
185 if (i->dynamic_user)
186 return false;
187
188 return isempty(i->user);
189}
190
191static int assess_bool(
192 const struct security_assessor *a,
1624114d 193 const SecurityInfo *info,
ec16f3b6
LP
194 const void *data,
195 uint64_t *ret_badness,
196 char **ret_description) {
197
198 const bool *b = data;
199
200 assert(b);
201 assert(ret_badness);
202 assert(ret_description);
203
204 *ret_badness = a->parameter ? *b : !*b;
205 *ret_description = NULL;
206
207 return 0;
208}
209
210static int assess_user(
211 const struct security_assessor *a,
1624114d 212 const SecurityInfo *info,
ec16f3b6
LP
213 const void *data,
214 uint64_t *ret_badness,
215 char **ret_description) {
216
217 _cleanup_free_ char *d = NULL;
218 uint64_t b;
219
220 assert(ret_badness);
221 assert(ret_description);
222
223 if (streq_ptr(info->user, NOBODY_USER_NAME)) {
224 d = strdup("Service runs under as '" NOBODY_USER_NAME "' user, which should not be used for services");
225 b = 9;
226 } else if (info->dynamic_user && !STR_IN_SET(info->user, "0", "root")) {
227 d = strdup("Service runs under a transient non-root user identity");
228 b = 0;
229 } else if (info->user && !STR_IN_SET(info->user, "0", "root", "")) {
230 d = strdup("Service runs under a static non-root user identity");
231 b = 0;
232 } else {
233 *ret_badness = 10;
234 *ret_description = NULL;
235 return 0;
236 }
237
238 if (!d)
239 return log_oom();
240
241 *ret_badness = b;
242 *ret_description = TAKE_PTR(d);
243
244 return 0;
245}
246
247static int assess_protect_home(
248 const struct security_assessor *a,
1624114d 249 const SecurityInfo *info,
ec16f3b6
LP
250 const void *data,
251 uint64_t *ret_badness,
252 char **ret_description) {
253
254 const char *description;
255 uint64_t badness;
256 char *copy;
257 int r;
258
259 assert(ret_badness);
260 assert(ret_description);
261
262 badness = 10;
263 description = "Service has full access to home directories";
264
265 r = parse_boolean(info->protect_home);
266 if (r < 0) {
267 if (streq_ptr(info->protect_home, "read-only")) {
268 badness = 5;
269 description = "Service has read-only access to home directories";
270 } else if (streq_ptr(info->protect_home, "tmpfs")) {
271 badness = 1;
272 description = "Service has access to fake empty home directories";
273 }
274 } else if (r > 0) {
275 badness = 0;
276 description = "Service has no access to home directories";
277 }
278
279 copy = strdup(description);
280 if (!copy)
281 return log_oom();
282
283 *ret_badness = badness;
284 *ret_description = copy;
285
286 return 0;
287}
288
289static int assess_protect_system(
290 const struct security_assessor *a,
1624114d 291 const SecurityInfo *info,
ec16f3b6
LP
292 const void *data,
293 uint64_t *ret_badness,
294 char **ret_description) {
295
296 const char *description;
297 uint64_t badness;
298 char *copy;
299 int r;
300
301 assert(ret_badness);
302 assert(ret_description);
303
304 badness = 10;
1e8817b3 305 description = "Service has full access to the OS file hierarchy";
ec16f3b6
LP
306
307 r = parse_boolean(info->protect_system);
308 if (r < 0) {
309 if (streq_ptr(info->protect_system, "full")) {
310 badness = 3;
1e8817b3 311 description = "Service has very limited write access to the OS file hierarchy";
ec16f3b6
LP
312 } else if (streq_ptr(info->protect_system, "strict")) {
313 badness = 0;
314 description = "Service has strict read-only access to the OS file hierarchy";
315 }
316 } else if (r > 0) {
317 badness = 5;
318 description = "Service has limited write access to the OS file hierarchy";
319 }
320
321 copy = strdup(description);
322 if (!copy)
323 return log_oom();
324
325 *ret_badness = badness;
326 *ret_description = copy;
327
328 return 0;
329}
330
331static int assess_root_directory(
332 const struct security_assessor *a,
1624114d 333 const SecurityInfo *info,
ec16f3b6
LP
334 const void *data,
335 uint64_t *ret_badness,
336 char **ret_description) {
337
338 assert(ret_badness);
339 assert(ret_description);
340
341 *ret_badness =
d909b40f 342 empty_or_root(info->root_directory) &&
d737b451 343 empty_or_root(info->root_image);
ec16f3b6
LP
344 *ret_description = NULL;
345
346 return 0;
347}
348
349static int assess_capability_bounding_set(
350 const struct security_assessor *a,
1624114d 351 const SecurityInfo *info,
ec16f3b6
LP
352 const void *data,
353 uint64_t *ret_badness,
354 char **ret_description) {
355
356 assert(ret_badness);
357 assert(ret_description);
358
359 *ret_badness = !!(info->capability_bounding_set & a->parameter);
360 *ret_description = NULL;
361
362 return 0;
363}
364
365static int assess_umask(
366 const struct security_assessor *a,
1624114d 367 const SecurityInfo *info,
ec16f3b6
LP
368 const void *data,
369 uint64_t *ret_badness,
370 char **ret_description) {
371
372 char *copy = NULL;
373 const char *d;
374 uint64_t b;
375
376 assert(ret_badness);
377 assert(ret_description);
378
379 if (!FLAGS_SET(info->_umask, 0002)) {
380 d = "Files created by service are world-writable by default";
381 b = 10;
382 } else if (!FLAGS_SET(info->_umask, 0004)) {
383 d = "Files created by service are world-readable by default";
384 b = 5;
385 } else if (!FLAGS_SET(info->_umask, 0020)) {
386 d = "Files created by service are group-writable by default";
387 b = 2;
388 } else if (!FLAGS_SET(info->_umask, 0040)) {
389 d = "Files created by service are group-readable by default";
390 b = 1;
391 } else {
392 d = "Files created by service are accessible only by service's own user by default";
393 b = 0;
394 }
395
396 copy = strdup(d);
397 if (!copy)
398 return log_oom();
399
400 *ret_badness = b;
401 *ret_description = copy;
402
403 return 0;
404}
405
406static int assess_keyring_mode(
407 const struct security_assessor *a,
1624114d 408 const SecurityInfo *info,
ec16f3b6
LP
409 const void *data,
410 uint64_t *ret_badness,
411 char **ret_description) {
412
413 assert(ret_badness);
414 assert(ret_description);
415
416 *ret_badness = !streq_ptr(info->keyring_mode, "private");
417 *ret_description = NULL;
418
419 return 0;
420}
421
ed125c93
LP
422static int assess_protect_proc(
423 const struct security_assessor *a,
1624114d 424 const SecurityInfo *info,
ed125c93
LP
425 const void *data,
426 uint64_t *ret_badness,
427 char **ret_description) {
428
429 assert(ret_badness);
430 assert(ret_description);
431
432 if (streq_ptr(info->protect_proc, "noaccess"))
433 *ret_badness = 1;
434 else if (STRPTR_IN_SET(info->protect_proc, "invisible", "ptraceable"))
435 *ret_badness = 0;
436 else
437 *ret_badness = 3;
438
439 *ret_description = NULL;
440
441 return 0;
442}
443
444static int assess_proc_subset(
445 const struct security_assessor *a,
1624114d 446 const SecurityInfo *info,
ed125c93
LP
447 const void *data,
448 uint64_t *ret_badness,
449 char **ret_description) {
450
451 assert(ret_badness);
452 assert(ret_description);
453
454 *ret_badness = !streq_ptr(info->proc_subset, "pid");
455 *ret_description = NULL;
456
457 return 0;
458}
459
ec16f3b6
LP
460static int assess_notify_access(
461 const struct security_assessor *a,
1624114d 462 const SecurityInfo *info,
ec16f3b6
LP
463 const void *data,
464 uint64_t *ret_badness,
465 char **ret_description) {
466
467 assert(ret_badness);
468 assert(ret_description);
469
470 *ret_badness = streq_ptr(info->notify_access, "all");
471 *ret_description = NULL;
472
473 return 0;
474}
475
476static int assess_remove_ipc(
477 const struct security_assessor *a,
1624114d 478 const SecurityInfo *info,
ec16f3b6
LP
479 const void *data,
480 uint64_t *ret_badness,
481 char **ret_description) {
482
483 assert(ret_badness);
484 assert(ret_description);
485
486 if (security_info_runs_privileged(info))
487 *ret_badness = UINT64_MAX;
488 else
489 *ret_badness = !info->remove_ipc;
490
491 *ret_description = NULL;
492 return 0;
493}
494
495static int assess_supplementary_groups(
496 const struct security_assessor *a,
1624114d 497 const SecurityInfo *info,
ec16f3b6
LP
498 const void *data,
499 uint64_t *ret_badness,
500 char **ret_description) {
501
502 assert(ret_badness);
503 assert(ret_description);
504
505 if (security_info_runs_privileged(info))
506 *ret_badness = UINT64_MAX;
507 else
508 *ret_badness = !strv_isempty(info->supplementary_groups);
509
510 *ret_description = NULL;
511 return 0;
512}
513
514static int assess_restrict_namespaces(
515 const struct security_assessor *a,
1624114d 516 const SecurityInfo *info,
ec16f3b6
LP
517 const void *data,
518 uint64_t *ret_badness,
519 char **ret_description) {
520
521 assert(ret_badness);
522 assert(ret_description);
523
524 *ret_badness = !!(info->restrict_namespaces & a->parameter);
525 *ret_description = NULL;
526
527 return 0;
528}
529
530static int assess_system_call_architectures(
531 const struct security_assessor *a,
1624114d 532 const SecurityInfo *info,
ec16f3b6
LP
533 const void *data,
534 uint64_t *ret_badness,
535 char **ret_description) {
536
537 char *d;
538 uint64_t b;
539
540 assert(ret_badness);
541 assert(ret_description);
542
1624114d 543 if (set_isempty(info->system_call_architectures)) {
ec16f3b6
LP
544 b = 10;
545 d = strdup("Service may execute system calls with all ABIs");
1624114d
MG
546 } else if (set_contains(info->system_call_architectures, "native") &&
547 set_size(info->system_call_architectures) == 1) {
ec16f3b6
LP
548 b = 0;
549 d = strdup("Service may execute system calls only with native ABI");
550 } else {
551 b = 8;
552 d = strdup("Service may execute system calls with multiple ABIs");
553 }
554
555 if (!d)
556 return log_oom();
557
558 *ret_badness = b;
559 *ret_description = d;
560
561 return 0;
562}
563
3a5d89fa
WKI
564#if HAVE_SECCOMP
565
1624114d 566static bool syscall_names_in_filter(Hashmap *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) {
ec16f3b6
LP
567 const char *syscall;
568
569 NULSTR_FOREACH(syscall, f->value) {
95832a0f 570 int id;
ec16f3b6
LP
571
572 if (syscall[0] == '@') {
573 const SyscallFilterSet *g;
ec16f3b6 574
95832a0f 575 assert_se(g = syscall_filter_set_find(syscall));
a9134af2 576 if (syscall_names_in_filter(s, allow_list, g, ret_offending_syscall))
95832a0f 577 return true; /* bad! */
ec16f3b6 578
95832a0f 579 continue;
ec16f3b6
LP
580 }
581
95832a0f
YW
582 /* Let's see if the system call actually exists on this platform, before complaining */
583 id = seccomp_syscall_resolve_name(syscall);
584 if (id < 0)
585 continue;
586
1624114d 587 if (hashmap_contains(s, syscall) == allow_list) {
ec16f3b6 588 log_debug("Offending syscall filter item: %s", syscall);
a9134af2
ZJS
589 if (ret_offending_syscall)
590 *ret_offending_syscall = syscall;
ec16f3b6
LP
591 return true; /* bad! */
592 }
593 }
594
a9134af2 595 *ret_offending_syscall = NULL;
ec16f3b6
LP
596 return false;
597}
598
599static int assess_system_call_filter(
600 const struct security_assessor *a,
1624114d 601 const SecurityInfo *info,
ec16f3b6
LP
602 const void *data,
603 uint64_t *ret_badness,
604 char **ret_description) {
605
ec16f3b6
LP
606 assert(a);
607 assert(info);
608 assert(ret_badness);
609 assert(ret_description);
610
611 assert(a->parameter < _SYSCALL_FILTER_SET_MAX);
a9134af2
ZJS
612 const SyscallFilterSet *f = syscall_filter_sets + a->parameter;
613
12619d0a 614 _cleanup_free_ char *d = NULL;
a9134af2 615 uint64_t b;
12619d0a 616 int r;
ec16f3b6 617
1624114d 618 if (!info->system_call_filter_allow_list && hashmap_isempty(info->system_call_filter)) {
12619d0a 619 r = free_and_strdup(&d, "Service does not filter system calls");
ec16f3b6
LP
620 b = 10;
621 } else {
622 bool bad;
a9134af2 623 const char *offender = NULL;
ec16f3b6
LP
624
625 log_debug("Analyzing system call filter, checking against: %s", f->name);
a9134af2 626 bad = syscall_names_in_filter(info->system_call_filter, info->system_call_filter_allow_list, f, &offender);
ec16f3b6
LP
627 log_debug("Result: %s", bad ? "bad" : "good");
628
6b000af4 629 if (info->system_call_filter_allow_list) {
ec16f3b6 630 if (bad) {
12619d0a
ZJS
631 r = asprintf(&d, "System call allow list defined for service, and %s is included "
632 "(e.g. %s is allowed)",
633 f->name, offender);
ec16f3b6
LP
634 b = 9;
635 } else {
12619d0a
ZJS
636 r = asprintf(&d, "System call allow list defined for service, and %s is not included",
637 f->name);
ec16f3b6
LP
638 b = 0;
639 }
640 } else {
641 if (bad) {
12619d0a
ZJS
642 r = asprintf(&d, "System call deny list defined for service, and %s is not included "
643 "(e.g. %s is allowed)",
644 f->name, offender);
ec16f3b6
LP
645 b = 10;
646 } else {
12619d0a
ZJS
647 r = asprintf(&d, "System call deny list defined for service, and %s is included",
648 f->name);
01ecb367 649 b = 0;
ec16f3b6
LP
650 }
651 }
652 }
12619d0a 653 if (r < 0)
ec16f3b6
LP
654 return log_oom();
655
656 *ret_badness = b;
12619d0a 657 *ret_description = TAKE_PTR(d);
ec16f3b6
LP
658
659 return 0;
660}
661
3a5d89fa
WKI
662#endif
663
ec16f3b6
LP
664static int assess_ip_address_allow(
665 const struct security_assessor *a,
1624114d 666 const SecurityInfo *info,
ec16f3b6
LP
667 const void *data,
668 uint64_t *ret_badness,
669 char **ret_description) {
670
671 char *d = NULL;
672 uint64_t b;
673
674 assert(info);
675 assert(ret_badness);
676 assert(ret_description);
677
fab34748
KL
678 if (info->ip_filters_custom_ingress || info->ip_filters_custom_egress) {
679 d = strdup("Service defines custom ingress/egress IP filters with BPF programs");
680 b = 0;
681 } else if (!info->ip_address_deny_all) {
6b000af4 682 d = strdup("Service does not define an IP address allow list");
ec16f3b6
LP
683 b = 10;
684 } else if (info->ip_address_allow_other) {
6b000af4 685 d = strdup("Service defines IP address allow list with non-localhost entries");
ec16f3b6
LP
686 b = 5;
687 } else if (info->ip_address_allow_localhost) {
6b000af4 688 d = strdup("Service defines IP address allow list with only localhost entries");
ec16f3b6
LP
689 b = 2;
690 } else {
691 d = strdup("Service blocks all IP address ranges");
692 b = 0;
693 }
694
695 if (!d)
696 return log_oom();
697
698 *ret_badness = b;
699 *ret_description = d;
700
701 return 0;
702}
703
704static int assess_device_allow(
705 const struct security_assessor *a,
1624114d 706 const SecurityInfo *info,
ec16f3b6
LP
707 const void *data,
708 uint64_t *ret_badness,
709 char **ret_description) {
710
711 char *d = NULL;
712 uint64_t b;
713
714 assert(info);
715 assert(ret_badness);
716 assert(ret_description);
717
718 if (STRPTR_IN_SET(info->device_policy, "strict", "closed")) {
719
720 if (info->device_allow_non_empty) {
721 d = strdup("Service has a device ACL with some special devices");
722 b = 5;
723 } else {
724 d = strdup("Service has a minimal device ACL");
725 b = 0;
726 }
727 } else {
728 d = strdup("Service has no device ACL");
729 b = 10;
730 }
731
732 if (!d)
733 return log_oom();
734
735 *ret_badness = b;
736 *ret_description = d;
737
738 return 0;
739}
740
741static int assess_ambient_capabilities(
742 const struct security_assessor *a,
1624114d 743 const SecurityInfo *info,
ec16f3b6
LP
744 const void *data,
745 uint64_t *ret_badness,
746 char **ret_description) {
747
748 assert(ret_badness);
749 assert(ret_description);
750
751 *ret_badness = info->ambient_capabilities != 0;
752 *ret_description = NULL;
753
754 return 0;
755}
756
757static const struct security_assessor security_assessor_table[] = {
758 {
759 .id = "User=/DynamicUser=",
3838d22c 760 .json_field = "UserOrDynamicUser",
ec16f3b6
LP
761 .description_bad = "Service runs as root user",
762 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#User=",
763 .weight = 2000,
764 .range = 10,
765 .assess = assess_user,
766 },
767 {
768 .id = "SupplementaryGroups=",
3838d22c 769 .json_field = "SupplementaryGroups",
ec16f3b6
LP
770 .description_good = "Service has no supplementary groups",
771 .description_bad = "Service runs with supplementary groups",
772 .description_na = "Service runs as root, option does not matter",
773 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SupplementaryGroups=",
774 .weight = 200,
775 .range = 1,
776 .assess = assess_supplementary_groups,
777 },
778 {
779 .id = "PrivateDevices=",
3838d22c 780 .json_field = "PrivateDevices",
ec16f3b6
LP
781 .description_good = "Service has no access to hardware devices",
782 .description_bad = "Service potentially has access to hardware devices",
783 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateDevices=",
784 .weight = 1000,
785 .range = 1,
786 .assess = assess_bool,
1624114d 787 .offset = offsetof(SecurityInfo, private_devices),
ec16f3b6
LP
788 },
789 {
790 .id = "PrivateMounts=",
3838d22c 791 .json_field = "PrivateMounts",
ec16f3b6
LP
792 .description_good = "Service cannot install system mounts",
793 .description_bad = "Service may install system mounts",
794 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateMounts=",
795 .weight = 1000,
796 .range = 1,
797 .assess = assess_bool,
1624114d 798 .offset = offsetof(SecurityInfo, private_mounts),
ec16f3b6
LP
799 },
800 {
801 .id = "PrivateNetwork=",
3838d22c 802 .json_field = "PrivateNetwork",
ec16f3b6
LP
803 .description_good = "Service has no access to the host's network",
804 .description_bad = "Service has access to the host's network",
805 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateNetwork=",
806 .weight = 2500,
807 .range = 1,
808 .assess = assess_bool,
1624114d 809 .offset = offsetof(SecurityInfo, private_network),
ec16f3b6
LP
810 },
811 {
812 .id = "PrivateTmp=",
3838d22c 813 .json_field = "PrivateTmp",
ec16f3b6
LP
814 .description_good = "Service has no access to other software's temporary files",
815 .description_bad = "Service has access to other software's temporary files",
816 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateTmp=",
817 .weight = 1000,
818 .range = 1,
819 .assess = assess_bool,
1624114d 820 .offset = offsetof(SecurityInfo, private_tmp),
ec16f3b6
LP
821 .default_dependencies_only = true,
822 },
823 {
824 .id = "PrivateUsers=",
3838d22c 825 .json_field = "PrivateUsers",
ec16f3b6
LP
826 .description_good = "Service does not have access to other users",
827 .description_bad = "Service has access to other users",
828 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateUsers=",
829 .weight = 1000,
830 .range = 1,
831 .assess = assess_bool,
1624114d 832 .offset = offsetof(SecurityInfo, private_users),
ec16f3b6
LP
833 },
834 {
835 .id = "ProtectControlGroups=",
3838d22c 836 .json_field = "ProtectControlGroups",
ec16f3b6 837 .description_good = "Service cannot modify the control group file system",
287cf2d8 838 .description_bad = "Service may modify the control group file system",
ec16f3b6
LP
839 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=",
840 .weight = 1000,
841 .range = 1,
842 .assess = assess_bool,
1624114d 843 .offset = offsetof(SecurityInfo, protect_control_groups),
ec16f3b6
LP
844 },
845 {
846 .id = "ProtectKernelModules=",
3838d22c 847 .json_field = "ProtectKernelModules",
ec16f3b6
LP
848 .description_good = "Service cannot load or read kernel modules",
849 .description_bad = "Service may load or read kernel modules",
850 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelModules=",
851 .weight = 1000,
852 .range = 1,
853 .assess = assess_bool,
1624114d 854 .offset = offsetof(SecurityInfo, protect_kernel_modules),
ec16f3b6
LP
855 },
856 {
857 .id = "ProtectKernelTunables=",
3838d22c 858 .json_field = "ProtectKernelTunables",
ec16f3b6
LP
859 .description_good = "Service cannot alter kernel tunables (/proc/sys, …)",
860 .description_bad = "Service may alter kernel tunables",
861 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelTunables=",
862 .weight = 1000,
863 .range = 1,
864 .assess = assess_bool,
1624114d 865 .offset = offsetof(SecurityInfo, protect_kernel_tunables),
ec16f3b6 866 },
82dce83b
KK
867 {
868 .id = "ProtectKernelLogs=",
3838d22c 869 .json_field = "ProtectKernelLogs",
82dce83b
KK
870 .description_good = "Service cannot read from or write to the kernel log ring buffer",
871 .description_bad = "Service may read from or write to the kernel log ring buffer",
872 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelLogs=",
873 .weight = 1000,
874 .range = 1,
875 .assess = assess_bool,
1624114d 876 .offset = offsetof(SecurityInfo, protect_kernel_logs),
82dce83b 877 },
9f37272a
KK
878 {
879 .id = "ProtectClock=",
3838d22c 880 .json_field = "ProtectClock",
9f37272a
KK
881 .description_good = "Service cannot write to the hardware clock or system clock",
882 .description_bad = "Service may write to the hardware clock or system clock",
883 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectClock=",
884 .weight = 1000,
885 .range = 1,
886 .assess = assess_bool,
1624114d 887 .offset = offsetof(SecurityInfo, protect_clock),
9f37272a 888 },
ec16f3b6
LP
889 {
890 .id = "ProtectHome=",
3838d22c 891 .json_field = "ProtectHome",
ec16f3b6
LP
892 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
893 .weight = 1000,
894 .range = 10,
895 .assess = assess_protect_home,
896 .default_dependencies_only = true,
897 },
527bd7f1
TM
898 {
899 .id = "ProtectHostname=",
3838d22c 900 .json_field = "ProtectHostname",
527bd7f1
TM
901 .description_good = "Service cannot change system host/domainname",
902 .description_bad = "Service may change system host/domainname",
903 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHostname=",
904 .weight = 50,
905 .range = 1,
906 .assess = assess_bool,
1624114d 907 .offset = offsetof(SecurityInfo, protect_hostname),
527bd7f1 908 },
ec16f3b6
LP
909 {
910 .id = "ProtectSystem=",
3838d22c 911 .json_field = "ProtectSystem",
ec16f3b6
LP
912 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=",
913 .weight = 1000,
914 .range = 10,
915 .assess = assess_protect_system,
916 .default_dependencies_only = true,
917 },
918 {
919 .id = "RootDirectory=/RootImage=",
3838d22c 920 .json_field = "RootDirectoryOrRootImage",
ec16f3b6
LP
921 .description_good = "Service has its own root directory/image",
922 .description_bad = "Service runs within the host's root directory",
923 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RootDirectory=",
924 .weight = 200,
925 .range = 1,
926 .assess = assess_root_directory,
927 .default_dependencies_only = true,
928 },
929 {
930 .id = "LockPersonality=",
3838d22c 931 .json_field = "LockPersonality",
ec16f3b6
LP
932 .description_good = "Service cannot change ABI personality",
933 .description_bad = "Service may change ABI personality",
934 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LockPersonality=",
935 .weight = 100,
936 .range = 1,
937 .assess = assess_bool,
1624114d 938 .offset = offsetof(SecurityInfo, lock_personality),
ec16f3b6
LP
939 },
940 {
941 .id = "MemoryDenyWriteExecute=",
3838d22c 942 .json_field = "MemoryDenyWriteExecute",
ec16f3b6
LP
943 .description_good = "Service cannot create writable executable memory mappings",
944 .description_bad = "Service may create writable executable memory mappings",
945 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#MemoryDenyWriteExecute=",
946 .weight = 100,
947 .range = 1,
948 .assess = assess_bool,
1624114d 949 .offset = offsetof(SecurityInfo, memory_deny_write_execute),
ec16f3b6
LP
950 },
951 {
952 .id = "NoNewPrivileges=",
3838d22c 953 .json_field = "NoNewPrivileges",
ec16f3b6
LP
954 .description_good = "Service processes cannot acquire new privileges",
955 .description_bad = "Service processes may acquire new privileges",
956 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NoNewPrivileges=",
957 .weight = 1000,
958 .range = 1,
959 .assess = assess_bool,
1624114d 960 .offset = offsetof(SecurityInfo, no_new_privileges),
ec16f3b6
LP
961 },
962 {
963 .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN",
3838d22c 964 .json_field = "CapabilityBoundingSet_CAP_SYS_ADMIN",
ec16f3b6
LP
965 .description_good = "Service has no administrator privileges",
966 .description_bad = "Service has administrator privileges",
967 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
968 .weight = 1500,
969 .range = 1,
970 .assess = assess_capability_bounding_set,
971 .parameter = UINT64_C(1) << CAP_SYS_ADMIN,
972 },
973 {
974 .id = "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
3838d22c 975 .json_field = "CapabilityBoundingSet_CAP_SET_UID_GID_PCAP",
ec16f3b6
LP
976 .description_good = "Service cannot change UID/GID identities/capabilities",
977 .description_bad = "Service may change UID/GID identities/capabilities",
978 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
979 .weight = 1500,
980 .range = 1,
981 .assess = assess_capability_bounding_set,
982 .parameter = (UINT64_C(1) << CAP_SETUID)|
983 (UINT64_C(1) << CAP_SETGID)|
984 (UINT64_C(1) << CAP_SETPCAP),
985 },
986 {
987 .id = "CapabilityBoundingSet=~CAP_SYS_PTRACE",
3838d22c 988 .json_field = "CapabilityBoundingSet_CAP_SYS_PTRACE",
ec16f3b6
LP
989 .description_good = "Service has no ptrace() debugging abilities",
990 .description_bad = "Service has ptrace() debugging abilities",
991 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
992 .weight = 1500,
993 .range = 1,
994 .assess = assess_capability_bounding_set,
995 .parameter = (UINT64_C(1) << CAP_SYS_PTRACE),
996 },
997 {
998 .id = "CapabilityBoundingSet=~CAP_SYS_TIME",
3838d22c 999 .json_field = "CapabilityBoundingSet_CAP_SYS_TIME",
ec16f3b6
LP
1000 .description_good = "Service processes cannot change the system clock",
1001 .description_bad = "Service processes may change the system clock",
1002 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1003 .weight = 1000,
1004 .range = 1,
1005 .assess = assess_capability_bounding_set,
1006 .parameter = UINT64_C(1) << CAP_SYS_TIME,
1007 },
1008 {
1009 .id = "CapabilityBoundingSet=~CAP_NET_ADMIN",
3838d22c 1010 .json_field = "CapabilityBoundingSet_CAP_NET_ADMIN",
ec16f3b6
LP
1011 .description_good = "Service has no network configuration privileges",
1012 .description_bad = "Service has network configuration privileges",
1013 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1014 .weight = 1000,
1015 .range = 1,
1016 .assess = assess_capability_bounding_set,
1017 .parameter = (UINT64_C(1) << CAP_NET_ADMIN),
1018 },
1019 {
b5ef6610 1020 .id = "CapabilityBoundingSet=~CAP_SYS_RAWIO",
3838d22c 1021 .json_field = "CapabilityBoundingSet_CAP_SYS_RAWIO",
ec16f3b6
LP
1022 .description_good = "Service has no raw I/O access",
1023 .description_bad = "Service has raw I/O access",
1024 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1025 .weight = 1000,
1026 .range = 1,
1027 .assess = assess_capability_bounding_set,
1028 .parameter = (UINT64_C(1) << CAP_SYS_RAWIO),
1029 },
1030 {
1031 .id = "CapabilityBoundingSet=~CAP_SYS_MODULE",
3838d22c 1032 .json_field = "CapabilityBoundingSet_CAP_SYS_MODULE",
ec16f3b6
LP
1033 .description_good = "Service cannot load kernel modules",
1034 .description_bad = "Service may load kernel modules",
1035 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1036 .weight = 1000,
1037 .range = 1,
1038 .assess = assess_capability_bounding_set,
1039 .parameter = (UINT64_C(1) << CAP_SYS_MODULE),
1040 },
1041 {
1042 .id = "CapabilityBoundingSet=~CAP_AUDIT_*",
3838d22c 1043 .json_field = "CapabilityBoundingSet_CAP_AUDIT",
ec16f3b6
LP
1044 .description_good = "Service has no audit subsystem access",
1045 .description_bad = "Service has audit subsystem access",
1046 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1047 .weight = 500,
1048 .range = 1,
1049 .assess = assess_capability_bounding_set,
1050 .parameter = (UINT64_C(1) << CAP_AUDIT_CONTROL) |
1051 (UINT64_C(1) << CAP_AUDIT_READ) |
1052 (UINT64_C(1) << CAP_AUDIT_WRITE),
1053 },
1054 {
1055 .id = "CapabilityBoundingSet=~CAP_SYSLOG",
3838d22c 1056 .json_field = "CapabilityBoundingSet_CAP_SYSLOG",
ec16f3b6
LP
1057 .description_good = "Service has no access to kernel logging",
1058 .description_bad = "Service has access to kernel logging",
1059 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1060 .weight = 500,
1061 .range = 1,
1062 .assess = assess_capability_bounding_set,
1063 .parameter = (UINT64_C(1) << CAP_SYSLOG),
1064 },
1065 {
1066 .id = "CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)",
3838d22c 1067 .json_field = "CapabilityBoundingSet_CAP_SYS_NICE_RESOURCE",
ec16f3b6
LP
1068 .description_good = "Service has no privileges to change resource use parameters",
1069 .description_bad = "Service has privileges to change resource use parameters",
1070 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1071 .weight = 500,
1072 .range = 1,
1073 .assess = assess_capability_bounding_set,
1074 .parameter = (UINT64_C(1) << CAP_SYS_NICE) |
1075 (UINT64_C(1) << CAP_SYS_RESOURCE),
1076 },
1077 {
1078 .id = "CapabilityBoundingSet=~CAP_MKNOD",
3838d22c 1079 .json_field = "CapabilityBoundingSet_CAP_MKNOD",
ec16f3b6
LP
1080 .description_good = "Service cannot create device nodes",
1081 .description_bad = "Service may create device nodes",
1082 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1083 .weight = 500,
1084 .range = 1,
1085 .assess = assess_capability_bounding_set,
1086 .parameter = (UINT64_C(1) << CAP_MKNOD),
1087 },
1088 {
1089 .id = "CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)",
3838d22c 1090 .json_field = "CapabilityBoundingSet_CAP_CHOWN_FSETID_SETFCAP",
ec16f3b6
LP
1091 .description_good = "Service cannot change file ownership/access mode/capabilities",
1092 .description_bad = "Service may change file ownership/access mode/capabilities unrestricted",
1093 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1094 .weight = 1000,
1095 .range = 1,
1096 .assess = assess_capability_bounding_set,
1097 .parameter = (UINT64_C(1) << CAP_CHOWN) |
1098 (UINT64_C(1) << CAP_FSETID) |
1099 (UINT64_C(1) << CAP_SETFCAP),
1100 },
1101 {
1102 .id = "CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)",
3838d22c 1103 .json_field = "CapabilityBoundingSet_CAP_DAC_FOWNER_IPC_OWNER",
ec16f3b6
LP
1104 .description_good = "Service cannot override UNIX file/IPC permission checks",
1105 .description_bad = "Service may override UNIX file/IPC permission checks",
1106 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1107 .weight = 1000,
1108 .range = 1,
1109 .assess = assess_capability_bounding_set,
1110 .parameter = (UINT64_C(1) << CAP_DAC_OVERRIDE) |
1111 (UINT64_C(1) << CAP_DAC_READ_SEARCH) |
1112 (UINT64_C(1) << CAP_FOWNER) |
1113 (UINT64_C(1) << CAP_IPC_OWNER),
1114 },
1115 {
1116 .id = "CapabilityBoundingSet=~CAP_KILL",
3838d22c 1117 .json_field = "CapabilityBoundingSet_CAP_KILL",
ec16f3b6
LP
1118 .description_good = "Service cannot send UNIX signals to arbitrary processes",
1119 .description_bad = "Service may send UNIX signals to arbitrary processes",
1120 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1121 .weight = 500,
1122 .range = 1,
1123 .assess = assess_capability_bounding_set,
1124 .parameter = (UINT64_C(1) << CAP_KILL),
1125 },
1126 {
1127 .id = "CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)",
3838d22c 1128 .json_field = "CapabilityBoundingSet_CAP_NET_BIND_SERVICE_BROADCAST_RAW)",
ec16f3b6
LP
1129 .description_good = "Service has no elevated networking privileges",
1130 .description_bad = "Service has elevated networking privileges",
1131 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1132 .weight = 500,
1133 .range = 1,
1134 .assess = assess_capability_bounding_set,
1135 .parameter = (UINT64_C(1) << CAP_NET_BIND_SERVICE) |
1136 (UINT64_C(1) << CAP_NET_BROADCAST) |
1137 (UINT64_C(1) << CAP_NET_RAW),
1138 },
1139 {
1140 .id = "CapabilityBoundingSet=~CAP_SYS_BOOT",
3838d22c 1141 .json_field = "CapabilityBoundingSet_CAP_SYS_BOOT",
ec16f3b6
LP
1142 .description_good = "Service cannot issue reboot()",
1143 .description_bad = "Service may issue reboot()",
1144 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1145 .weight = 100,
1146 .range = 1,
1147 .assess = assess_capability_bounding_set,
1148 .parameter = (UINT64_C(1) << CAP_SYS_BOOT),
1149 },
1150 {
1151 .id = "CapabilityBoundingSet=~CAP_MAC_*",
3838d22c 1152 .json_field = "CapabilityBoundingSet_CAP_MAC",
ec16f3b6
LP
1153 .description_good = "Service cannot adjust SMACK MAC",
1154 .description_bad = "Service may adjust SMACK MAC",
1155 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1156 .weight = 100,
1157 .range = 1,
1158 .assess = assess_capability_bounding_set,
1159 .parameter = (UINT64_C(1) << CAP_MAC_ADMIN)|
1160 (UINT64_C(1) << CAP_MAC_OVERRIDE),
1161 },
1162 {
1163 .id = "CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE",
3838d22c 1164 .json_field = "CapabilityBoundingSet_CAP_LINUX_IMMUTABLE",
ec16f3b6
LP
1165 .description_good = "Service cannot mark files immutable",
1166 .description_bad = "Service may mark files immutable",
1167 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1168 .weight = 75,
1169 .range = 1,
1170 .assess = assess_capability_bounding_set,
1171 .parameter = (UINT64_C(1) << CAP_LINUX_IMMUTABLE),
1172 },
1173 {
1174 .id = "CapabilityBoundingSet=~CAP_IPC_LOCK",
3838d22c 1175 .json_field = "CapabilityBoundingSet_CAP_IPC_LOCK",
ec16f3b6
LP
1176 .description_good = "Service cannot lock memory into RAM",
1177 .description_bad = "Service may lock memory into RAM",
1178 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1179 .weight = 50,
1180 .range = 1,
1181 .assess = assess_capability_bounding_set,
1182 .parameter = (UINT64_C(1) << CAP_IPC_LOCK),
1183 },
1184 {
1185 .id = "CapabilityBoundingSet=~CAP_SYS_CHROOT",
3838d22c 1186 .json_field = "CapabilityBoundingSet_CAP_SYS_CHROOT",
ec16f3b6
LP
1187 .description_good = "Service cannot issue chroot()",
1188 .description_bad = "Service may issue chroot()",
1189 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1190 .weight = 50,
1191 .range = 1,
1192 .assess = assess_capability_bounding_set,
1193 .parameter = (UINT64_C(1) << CAP_SYS_CHROOT),
1194 },
1195 {
1196 .id = "CapabilityBoundingSet=~CAP_BLOCK_SUSPEND",
3838d22c 1197 .json_field = "CapabilityBoundingSet_CAP_BLOCK_SUSPEND",
ec16f3b6
LP
1198 .description_good = "Service cannot establish wake locks",
1199 .description_bad = "Service may establish wake locks",
1200 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1201 .weight = 25,
1202 .range = 1,
1203 .assess = assess_capability_bounding_set,
1204 .parameter = (UINT64_C(1) << CAP_BLOCK_SUSPEND),
1205 },
1206 {
1207 .id = "CapabilityBoundingSet=~CAP_WAKE_ALARM",
3838d22c 1208 .json_field = "CapabilityBoundingSet_CAP_WAKE_ALARM",
ec16f3b6
LP
1209 .description_good = "Service cannot program timers that wake up the system",
1210 .description_bad = "Service may program timers that wake up the system",
1211 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1212 .weight = 25,
1213 .range = 1,
1214 .assess = assess_capability_bounding_set,
1215 .parameter = (UINT64_C(1) << CAP_WAKE_ALARM),
1216 },
1217 {
1218 .id = "CapabilityBoundingSet=~CAP_LEASE",
3838d22c 1219 .json_field = "CapabilityBoundingSet_CAP_LEASE",
ec16f3b6
LP
1220 .description_good = "Service cannot create file leases",
1221 .description_bad = "Service may create file leases",
1222 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1223 .weight = 25,
1224 .range = 1,
1225 .assess = assess_capability_bounding_set,
1226 .parameter = (UINT64_C(1) << CAP_LEASE),
1227 },
1228 {
1229 .id = "CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG",
3838d22c 1230 .json_field = "CapabilityBoundingSet_CAP_SYS_TTY_CONFIG",
ec16f3b6
LP
1231 .description_good = "Service cannot issue vhangup()",
1232 .description_bad = "Service may issue vhangup()",
1233 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1234 .weight = 25,
1235 .range = 1,
1236 .assess = assess_capability_bounding_set,
1237 .parameter = (UINT64_C(1) << CAP_SYS_TTY_CONFIG),
1238 },
1239 {
1240 .id = "CapabilityBoundingSet=~CAP_SYS_PACCT",
3838d22c 1241 .json_field = "CapabilityBoundingSet_CAP_SYS_PACCT",
ec16f3b6
LP
1242 .description_good = "Service cannot use acct()",
1243 .description_bad = "Service may use acct()",
1244 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
1245 .weight = 25,
1246 .range = 1,
1247 .assess = assess_capability_bounding_set,
1248 .parameter = (UINT64_C(1) << CAP_SYS_PACCT),
1249 },
1250 {
1251 .id = "UMask=",
3838d22c 1252 .json_field = "UMask",
ec16f3b6
LP
1253 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#UMask=",
1254 .weight = 100,
1255 .range = 10,
1256 .assess = assess_umask,
1257 },
1258 {
1259 .id = "KeyringMode=",
3838d22c 1260 .json_field = "KeyringMode",
ec16f3b6
LP
1261 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#KeyringMode=",
1262 .description_good = "Service doesn't share key material with other services",
1263 .description_bad = "Service shares key material with other service",
1264 .weight = 1000,
1265 .range = 1,
1266 .assess = assess_keyring_mode,
1267 },
ed125c93
LP
1268 {
1269 .id = "ProtectProc=",
3838d22c 1270 .json_field = "ProtectProc",
ed125c93
LP
1271 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectProc=",
1272 .description_good = "Service has restricted access to process tree (/proc hidepid=)",
1273 .description_bad = "Service has full access to process tree (/proc hidepid=)",
1274 .weight = 1000,
1275 .range = 3,
1276 .assess = assess_protect_proc,
1277 },
1278 {
1279 .id = "ProcSubset=",
3838d22c 1280 .json_field = "ProcSubset",
ed125c93
LP
1281 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProcSubset=",
1282 .description_good = "Service has no access to non-process /proc files (/proc subset=)",
1283 .description_bad = "Service has full access to non-process /proc files (/proc subset=)",
1284 .weight = 10,
1285 .range = 1,
1286 .assess = assess_proc_subset,
1287 },
ec16f3b6
LP
1288 {
1289 .id = "NotifyAccess=",
3838d22c 1290 .json_field = "NotifyAccess",
ec16f3b6
LP
1291 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NotifyAccess=",
1292 .description_good = "Service child processes cannot alter service state",
1293 .description_bad = "Service child processes may alter service state",
1294 .weight = 1000,
1295 .range = 1,
1296 .assess = assess_notify_access,
1297 },
1298 {
1299 .id = "RemoveIPC=",
3838d22c 1300 .json_field = "RemoveIPC",
ec16f3b6
LP
1301 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RemoveIPC=",
1302 .description_good = "Service user cannot leave SysV IPC objects around",
1303 .description_bad = "Service user may leave SysV IPC objects around",
1304 .description_na = "Service runs as root, option does not apply",
1305 .weight = 100,
1306 .range = 1,
1307 .assess = assess_remove_ipc,
1624114d 1308 .offset = offsetof(SecurityInfo, remove_ipc),
ec16f3b6
LP
1309 },
1310 {
1311 .id = "Delegate=",
3838d22c 1312 .json_field = "Delegate",
ec16f3b6
LP
1313 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Delegate=",
1314 .description_good = "Service does not maintain its own delegated control group subtree",
1315 .description_bad = "Service maintains its own delegated control group subtree",
1316 .weight = 100,
1317 .range = 1,
1318 .assess = assess_bool,
1624114d 1319 .offset = offsetof(SecurityInfo, delegate),
ec16f3b6
LP
1320 .parameter = true, /* invert! */
1321 },
1322 {
1323 .id = "RestrictRealtime=",
3838d22c 1324 .json_field = "RestrictRealtime",
ec16f3b6
LP
1325 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictRealtime=",
1326 .description_good = "Service realtime scheduling access is restricted",
1327 .description_bad = "Service may acquire realtime scheduling",
1328 .weight = 500,
1329 .range = 1,
1330 .assess = assess_bool,
1624114d 1331 .offset = offsetof(SecurityInfo, restrict_realtime),
ec16f3b6 1332 },
9d880b70
LP
1333 {
1334 .id = "RestrictSUIDSGID=",
3838d22c 1335 .json_field = "RestrictSUIDSGID",
9d880b70
LP
1336 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictSUIDSGID=",
1337 .description_good = "SUID/SGID file creation by service is restricted",
1338 .description_bad = "Service may create SUID/SGID files",
1339 .weight = 1000,
1340 .range = 1,
1341 .assess = assess_bool,
1624114d 1342 .offset = offsetof(SecurityInfo, restrict_suid_sgid),
9d880b70 1343 },
ec16f3b6 1344 {
c1e6f215
KL
1345 .id = "RestrictNamespaces=~user",
1346 .json_field = "RestrictNamespaces_user",
ec16f3b6
LP
1347 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1348 .description_good = "Service cannot create user namespaces",
1349 .description_bad = "Service may create user namespaces",
1350 .weight = 1500,
1351 .range = 1,
1352 .assess = assess_restrict_namespaces,
1353 .parameter = CLONE_NEWUSER,
1354 },
1355 {
c1e6f215
KL
1356 .id = "RestrictNamespaces=~mnt",
1357 .json_field = "RestrictNamespaces_mnt",
ec16f3b6
LP
1358 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1359 .description_good = "Service cannot create file system namespaces",
1360 .description_bad = "Service may create file system namespaces",
1361 .weight = 500,
1362 .range = 1,
1363 .assess = assess_restrict_namespaces,
1364 .parameter = CLONE_NEWNS,
1365 },
1366 {
c1e6f215
KL
1367 .id = "RestrictNamespaces=~ipc",
1368 .json_field = "RestrictNamespaces_ipc",
ec16f3b6
LP
1369 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1370 .description_good = "Service cannot create IPC namespaces",
1371 .description_bad = "Service may create IPC namespaces",
1372 .weight = 500,
1373 .range = 1,
1374 .assess = assess_restrict_namespaces,
1375 .parameter = CLONE_NEWIPC,
1376 },
1377 {
c1e6f215
KL
1378 .id = "RestrictNamespaces=~pid",
1379 .json_field = "RestrictNamespaces_pid",
ec16f3b6
LP
1380 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1381 .description_good = "Service cannot create process namespaces",
1382 .description_bad = "Service may create process namespaces",
1383 .weight = 500,
1384 .range = 1,
1385 .assess = assess_restrict_namespaces,
1386 .parameter = CLONE_NEWPID,
1387 },
1388 {
c1e6f215
KL
1389 .id = "RestrictNamespaces=~cgroup",
1390 .json_field = "RestrictNamespaces_cgroup",
ec16f3b6
LP
1391 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1392 .description_good = "Service cannot create cgroup namespaces",
1393 .description_bad = "Service may create cgroup namespaces",
1394 .weight = 500,
1395 .range = 1,
1396 .assess = assess_restrict_namespaces,
1397 .parameter = CLONE_NEWCGROUP,
1398 },
1399 {
c1e6f215
KL
1400 .id = "RestrictNamespaces=~net",
1401 .json_field = "RestrictNamespaces_net",
ec16f3b6
LP
1402 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1403 .description_good = "Service cannot create network namespaces",
1404 .description_bad = "Service may create network namespaces",
1405 .weight = 500,
1406 .range = 1,
1407 .assess = assess_restrict_namespaces,
1408 .parameter = CLONE_NEWNET,
1409 },
1410 {
c1e6f215
KL
1411 .id = "RestrictNamespaces=~uts",
1412 .json_field = "RestrictNamespaces_uts",
ec16f3b6
LP
1413 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
1414 .description_good = "Service cannot create hostname namespaces",
1415 .description_bad = "Service may create hostname namespaces",
1416 .weight = 100,
1417 .range = 1,
1418 .assess = assess_restrict_namespaces,
1419 .parameter = CLONE_NEWUTS,
1420 },
1421 {
1422 .id = "RestrictAddressFamilies=~AF_(INET|INET6)",
3838d22c 1423 .json_field = "RestrictAddressFamilies_AF_INET_INET6",
ec16f3b6
LP
1424 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1425 .description_good = "Service cannot allocate Internet sockets",
1426 .description_bad = "Service may allocate Internet sockets",
1427 .weight = 1500,
1428 .range = 1,
1429 .assess = assess_bool,
1624114d 1430 .offset = offsetof(SecurityInfo, restrict_address_family_inet),
ec16f3b6
LP
1431 },
1432 {
1433 .id = "RestrictAddressFamilies=~AF_UNIX",
3838d22c 1434 .json_field = "RestrictAddressFamilies_AF_UNIX",
ec16f3b6
LP
1435 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1436 .description_good = "Service cannot allocate local sockets",
1437 .description_bad = "Service may allocate local sockets",
1438 .weight = 25,
1439 .range = 1,
1440 .assess = assess_bool,
1624114d 1441 .offset = offsetof(SecurityInfo, restrict_address_family_unix),
ec16f3b6
LP
1442 },
1443 {
1444 .id = "RestrictAddressFamilies=~AF_NETLINK",
3838d22c 1445 .json_field = "RestrictAddressFamilies_AF_NETLINK",
ec16f3b6
LP
1446 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1447 .description_good = "Service cannot allocate netlink sockets",
1448 .description_bad = "Service may allocate netlink sockets",
1449 .weight = 200,
1450 .range = 1,
1451 .assess = assess_bool,
1624114d 1452 .offset = offsetof(SecurityInfo, restrict_address_family_netlink),
ec16f3b6
LP
1453 },
1454 {
1455 .id = "RestrictAddressFamilies=~AF_PACKET",
3838d22c 1456 .json_field = "RestrictAddressFamilies_AF_PACKET",
ec16f3b6
LP
1457 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1458 .description_good = "Service cannot allocate packet sockets",
1459 .description_bad = "Service may allocate packet sockets",
1460 .weight = 1000,
1461 .range = 1,
1462 .assess = assess_bool,
1624114d 1463 .offset = offsetof(SecurityInfo, restrict_address_family_packet),
ec16f3b6
LP
1464 },
1465 {
1466 .id = "RestrictAddressFamilies=~…",
3838d22c 1467 .json_field = "RestrictAddressFamilies_OTHER",
ec16f3b6
LP
1468 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
1469 .description_good = "Service cannot allocate exotic sockets",
1470 .description_bad = "Service may allocate exotic sockets",
1471 .weight = 1250,
1472 .range = 1,
1473 .assess = assess_bool,
1624114d 1474 .offset = offsetof(SecurityInfo, restrict_address_family_other),
ec16f3b6
LP
1475 },
1476 {
1477 .id = "SystemCallArchitectures=",
3838d22c 1478 .json_field = "SystemCallArchitectures",
ec16f3b6
LP
1479 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=",
1480 .weight = 1000,
1481 .range = 10,
1482 .assess = assess_system_call_architectures,
1483 },
3a5d89fa 1484#if HAVE_SECCOMP
ec16f3b6
LP
1485 {
1486 .id = "SystemCallFilter=~@swap",
3838d22c 1487 .json_field = "SystemCallFilter_swap",
ec16f3b6
LP
1488 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1489 .weight = 1000,
1490 .range = 10,
1491 .assess = assess_system_call_filter,
1492 .parameter = SYSCALL_FILTER_SET_SWAP,
1493 },
1494 {
1495 .id = "SystemCallFilter=~@obsolete",
3838d22c 1496 .json_field = "SystemCallFilter_obsolete",
ec16f3b6
LP
1497 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1498 .weight = 250,
1499 .range = 10,
1500 .assess = assess_system_call_filter,
1501 .parameter = SYSCALL_FILTER_SET_OBSOLETE,
1502 },
1503 {
1504 .id = "SystemCallFilter=~@clock",
3838d22c 1505 .json_field = "SystemCallFilter_clock",
ec16f3b6
LP
1506 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1507 .weight = 1000,
1508 .range = 10,
1509 .assess = assess_system_call_filter,
1510 .parameter = SYSCALL_FILTER_SET_CLOCK,
1511 },
1512 {
1513 .id = "SystemCallFilter=~@cpu-emulation",
3838d22c 1514 .json_field = "SystemCallFilter_cpu_emulation",
ec16f3b6
LP
1515 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1516 .weight = 250,
1517 .range = 10,
1518 .assess = assess_system_call_filter,
1519 .parameter = SYSCALL_FILTER_SET_CPU_EMULATION,
1520 },
1521 {
1522 .id = "SystemCallFilter=~@debug",
3838d22c 1523 .json_field = "SystemCallFilter_debug",
ec16f3b6
LP
1524 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1525 .weight = 1000,
1526 .range = 10,
1527 .assess = assess_system_call_filter,
1528 .parameter = SYSCALL_FILTER_SET_DEBUG,
1529 },
1530 {
1531 .id = "SystemCallFilter=~@mount",
3838d22c 1532 .json_field = "SystemCallFilter_mount",
ec16f3b6
LP
1533 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1534 .weight = 1000,
1535 .range = 10,
1536 .assess = assess_system_call_filter,
1537 .parameter = SYSCALL_FILTER_SET_MOUNT,
1538 },
1539 {
1540 .id = "SystemCallFilter=~@module",
3838d22c 1541 .json_field = "SystemCallFilter_module",
ec16f3b6
LP
1542 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1543 .weight = 1000,
1544 .range = 10,
1545 .assess = assess_system_call_filter,
1546 .parameter = SYSCALL_FILTER_SET_MODULE,
1547 },
1548 {
1549 .id = "SystemCallFilter=~@raw-io",
3838d22c 1550 .json_field = "SystemCallFilter_raw_io",
ec16f3b6
LP
1551 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1552 .weight = 1000,
1553 .range = 10,
1554 .assess = assess_system_call_filter,
1555 .parameter = SYSCALL_FILTER_SET_RAW_IO,
1556 },
1557 {
1558 .id = "SystemCallFilter=~@reboot",
3838d22c 1559 .json_field = "SystemCallFilter_reboot",
ec16f3b6
LP
1560 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1561 .weight = 1000,
1562 .range = 10,
1563 .assess = assess_system_call_filter,
1564 .parameter = SYSCALL_FILTER_SET_REBOOT,
1565 },
1566 {
1567 .id = "SystemCallFilter=~@privileged",
3838d22c 1568 .json_field = "SystemCallFilter_privileged",
ec16f3b6
LP
1569 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1570 .weight = 700,
1571 .range = 10,
1572 .assess = assess_system_call_filter,
1573 .parameter = SYSCALL_FILTER_SET_PRIVILEGED,
1574 },
1575 {
1576 .id = "SystemCallFilter=~@resources",
3838d22c 1577 .json_field = "SystemCallFilter_resources",
ec16f3b6
LP
1578 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
1579 .weight = 700,
1580 .range = 10,
1581 .assess = assess_system_call_filter,
1582 .parameter = SYSCALL_FILTER_SET_RESOURCES,
1583 },
3a5d89fa 1584#endif
ec16f3b6
LP
1585 {
1586 .id = "IPAddressDeny=",
3838d22c 1587 .json_field = "IPAddressDeny",
ec16f3b6
LP
1588 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#IPAddressDeny=",
1589 .weight = 1000,
1590 .range = 10,
1591 .assess = assess_ip_address_allow,
1592 },
1593 {
1594 .id = "DeviceAllow=",
3838d22c 1595 .json_field = "DeviceAllow",
ec16f3b6
LP
1596 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DeviceAllow=",
1597 .weight = 1000,
1598 .range = 10,
1599 .assess = assess_device_allow,
1600 },
1601 {
1602 .id = "AmbientCapabilities=",
3838d22c 1603 .json_field = "AmbientCapabilities",
ec16f3b6
LP
1604 .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#AmbientCapabilities=",
1605 .description_good = "Service process does not receive ambient capabilities",
1606 .description_bad = "Service process receives ambient capabilities",
1607 .weight = 500,
1608 .range = 1,
1609 .assess = assess_ambient_capabilities,
1610 },
1611};
1612
3838d22c
MG
1613static JsonVariant* security_assessor_find_in_policy(const struct security_assessor *a, JsonVariant *policy, const char *name) {
1614 JsonVariant *item;
1615 assert(a);
1616
1617 if (!policy)
1618 return NULL;
1619 if (!json_variant_is_object(policy)) {
05403363 1620 log_debug("Specified policy is not a JSON object, ignoring.");
3838d22c
MG
1621 return NULL;
1622 }
1623
1624 item = json_variant_by_key(policy, a->json_field);
1625 if (!item)
1626 return NULL;
1627 if (!json_variant_is_object(item)) {
1628 log_debug("Item for '%s' in policy JSON object is not an object, ignoring.", a->id);
1629 return NULL;
1630 }
1631
1632 return name ? json_variant_by_key(item, name) : item;
1633}
1634
1635static uint64_t access_weight(const struct security_assessor *a, JsonVariant *policy) {
1636 JsonVariant *val;
1637
1638 assert(a);
1639
1640 val = security_assessor_find_in_policy(a, policy, "weight");
1641 if (val) {
1642 if (json_variant_is_unsigned(val))
1643 return json_variant_unsigned(val);
1644 log_debug("JSON field 'weight' of policy for %s is not an unsigned integer, ignoring.", a->id);
1645 }
1646
1647 return a->weight;
1648}
1649
1650static uint64_t access_range(const struct security_assessor *a, JsonVariant *policy) {
1651 JsonVariant *val;
1652
1653 assert(a);
1654
1655 val = security_assessor_find_in_policy(a, policy, "range");
1656 if (val) {
1657 if (json_variant_is_unsigned(val))
1658 return json_variant_unsigned(val);
1659 log_debug("JSON field 'range' of policy for %s is not an unsigned integer, ignoring.", a->id);
1660 }
1661
1662 return a->range;
1663}
1664
1665static const char *access_description_na(const struct security_assessor *a, JsonVariant *policy) {
1666 JsonVariant *val;
1667
1668 assert(a);
1669
1670 val = security_assessor_find_in_policy(a, policy, "description_na");
1671 if (val) {
1672 if (json_variant_is_string(val))
1673 return json_variant_string(val);
1674 log_debug("JSON field 'description_na' of policy for %s is not a string, ignoring.", a->id);
1675 }
1676
1677 return a->description_na;
1678}
1679
1680static const char *access_description_good(const struct security_assessor *a, JsonVariant *policy) {
1681 JsonVariant *val;
1682
1683 assert(a);
1684
1685 val = security_assessor_find_in_policy(a, policy, "description_good");
1686 if (val) {
1687 if (json_variant_is_string(val))
1688 return json_variant_string(val);
1689 log_debug("JSON field 'description_good' of policy for %s is not a string, ignoring.", a->id);
1690 }
1691
1692 return a->description_good;
1693}
1694
1695static const char *access_description_bad(const struct security_assessor *a, JsonVariant *policy) {
1696 JsonVariant *val;
1697
1698 assert(a);
1699
1700 val = security_assessor_find_in_policy(a, policy, "description_bad");
1701 if (val) {
1702 if (json_variant_is_string(val))
1703 return json_variant_string(val);
1704 log_debug("JSON field 'description_bad' of policy for %s is not a string, ignoring.", a->id);
1705 }
1706
1707 return a->description_bad;
1708}
1709
1710static int assess(const SecurityInfo *info,
1711 Table *overview_table,
1712 AnalyzeSecurityFlags flags,
1713 unsigned threshold,
4b4a8ef7
MG
1714 JsonVariant *policy,
1715 PagerFlags pager_flags,
1716 JsonFormatFlags json_format_flags) {
3838d22c 1717
ec16f3b6
LP
1718 static const struct {
1719 uint64_t exposure;
1720 const char *name;
1721 const char *color;
1722 SpecialGlyph smiley;
1723 } badness_table[] = {
9a6f746f
LP
1724 { 100, "DANGEROUS", ANSI_HIGHLIGHT_RED, SPECIAL_GLYPH_DEPRESSED_SMILEY },
1725 { 90, "UNSAFE", ANSI_HIGHLIGHT_RED, SPECIAL_GLYPH_UNHAPPY_SMILEY },
1726 { 75, "EXPOSED", ANSI_HIGHLIGHT_YELLOW, SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY },
1727 { 50, "MEDIUM", NULL, SPECIAL_GLYPH_NEUTRAL_SMILEY },
1728 { 10, "OK", ANSI_HIGHLIGHT_GREEN, SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY },
1729 { 1, "SAFE", ANSI_HIGHLIGHT_GREEN, SPECIAL_GLYPH_HAPPY_SMILEY },
1730 { 0, "PERFECT", ANSI_HIGHLIGHT_GREEN, SPECIAL_GLYPH_ECSTATIC_SMILEY },
ec16f3b6
LP
1731 };
1732
1733 uint64_t badness_sum = 0, weight_sum = 0, exposure;
1734 _cleanup_(table_unrefp) Table *details_table = NULL;
1735 size_t i;
1736 int r;
1737
1738 if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
4b4a8ef7 1739 details_table = table_new(" ", "name", "json_field", "description", "weight", "badness", "range", "exposure");
ec16f3b6
LP
1740 if (!details_table)
1741 return log_oom();
1742
4b4a8ef7
MG
1743 r = table_set_json_field_name(details_table, 0, "set");
1744 if (r < 0)
1745 return log_error_errno(r, "Failed to set JSON field name of column 0: %m");
1746
ef1e0b9a 1747 (void) table_set_sort(details_table, (size_t) 3, (size_t) 1);
ec16f3b6
LP
1748 (void) table_set_reverse(details_table, 3, true);
1749
1750 if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
4b4a8ef7 1751 (void) table_set_display(details_table, (size_t) 0, (size_t) 1, (size_t) 2, (size_t) 3, (size_t) 7);
ec16f3b6
LP
1752 }
1753
1754 for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
1755 const struct security_assessor *a = security_assessor_table + i;
1756 _cleanup_free_ char *d = NULL;
1757 uint64_t badness;
1758 void *data;
3838d22c
MG
1759 uint64_t weight = access_weight(a, policy);
1760 uint64_t range = access_range(a, policy);
ec16f3b6 1761
1ace223c 1762 data = (uint8_t *) info + a->offset;
ec16f3b6
LP
1763
1764 if (a->default_dependencies_only && !info->default_dependencies) {
1765 badness = UINT64_MAX;
77552b95 1766 d = strdup("Service runs in special boot phase, option is not appropriate");
ec16f3b6 1767 if (!d)
82100ef4
LB
1768 return log_oom();
1769 } else if (weight == 0) {
1770 badness = UINT64_MAX;
1771 d = strdup("Option excluded by policy, skipping");
1772 if (!d)
ec16f3b6
LP
1773 return log_oom();
1774 } else {
1775 r = a->assess(a, info, data, &badness, &d);
1776 if (r < 0)
1777 return r;
1778 }
1779
3838d22c 1780 assert(range > 0);
ec16f3b6
LP
1781
1782 if (badness != UINT64_MAX) {
3838d22c 1783 assert(badness <= range);
ec16f3b6 1784
3838d22c
MG
1785 badness_sum += DIV_ROUND_UP(badness * weight, range);
1786 weight_sum += weight;
ec16f3b6
LP
1787 }
1788
1789 if (details_table) {
4b4a8ef7
MG
1790 const char *description, *color = NULL;
1791 int checkmark;
ec16f3b6
LP
1792
1793 if (badness == UINT64_MAX) {
4b4a8ef7 1794 checkmark = -1;
3838d22c 1795 description = access_description_na(a, policy);
ec16f3b6
LP
1796 color = NULL;
1797 } else if (badness == a->range) {
4b4a8ef7 1798 checkmark = 0;
3838d22c 1799 description = access_description_bad(a, policy);
ec16f3b6
LP
1800 color = ansi_highlight_red();
1801 } else if (badness == 0) {
4b4a8ef7 1802 checkmark = 1;
3838d22c 1803 description = access_description_good(a, policy);
ec16f3b6
LP
1804 color = ansi_highlight_green();
1805 } else {
4b4a8ef7 1806 checkmark = 0;
ec16f3b6
LP
1807 description = NULL;
1808 color = ansi_highlight_red();
1809 }
1810
1811 if (d)
1812 description = d;
1813
4b4a8ef7
MG
1814 if (checkmark < 0) {
1815 r = table_add_many(details_table, TABLE_EMPTY);
1816 if (r < 0)
1817 return table_log_add_error(r);
1818 } else {
1819 r = table_add_many(details_table,
1820 TABLE_BOOLEAN_CHECKMARK, checkmark > 0,
1821 TABLE_SET_MINIMUM_WIDTH, 1,
1822 TABLE_SET_MAXIMUM_WIDTH, 1,
1823 TABLE_SET_ELLIPSIZE_PERCENT, 0,
1824 TABLE_SET_COLOR, color);
1825 if (r < 0)
1826 return table_log_add_error(r);
1827 }
3838d22c 1828
2cb434cf 1829 r = table_add_many(details_table,
4b4a8ef7
MG
1830 TABLE_STRING, a->id, TABLE_SET_URL, a->url,
1831 TABLE_STRING, a->json_field,
2cb434cf 1832 TABLE_STRING, description,
3838d22c 1833 TABLE_UINT64, weight, TABLE_SET_ALIGN_PERCENT, 100,
2cb434cf 1834 TABLE_UINT64, badness, TABLE_SET_ALIGN_PERCENT, 100,
3838d22c 1835 TABLE_UINT64, range, TABLE_SET_ALIGN_PERCENT, 100,
2cb434cf 1836 TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100);
ec16f3b6 1837 if (r < 0)
9c46b437 1838 return table_log_add_error(r);
ec16f3b6
LP
1839 }
1840 }
1841
28a06f5a
JS
1842 assert(weight_sum > 0);
1843
ec16f3b6
LP
1844 if (details_table) {
1845 size_t row;
1846
1847 for (row = 1; row < table_get_rows(details_table); row++) {
1848 char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
1849 const uint64_t *weight, *badness, *range;
1850 TableCell *cell;
1851 uint64_t x;
1852
4b4a8ef7
MG
1853 assert_se(weight = table_get_at(details_table, row, 4));
1854 assert_se(badness = table_get_at(details_table, row, 5));
1855 assert_se(range = table_get_at(details_table, row, 6));
ec16f3b6
LP
1856
1857 if (*badness == UINT64_MAX || *badness == 0)
1858 continue;
1859
4b4a8ef7 1860 assert_se(cell = table_get_cell(details_table, row, 7));
ec16f3b6
LP
1861
1862 x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
1863 xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
1864
1865 r = table_update(details_table, cell, TABLE_STRING, buf);
1866 if (r < 0)
1867 return log_error_errno(r, "Failed to update cell in table: %m");
1868 }
1869
4b4a8ef7
MG
1870 if (json_format_flags & JSON_FORMAT_OFF) {
1871 r = table_hide_column_from_display(details_table, (size_t) 2);
1872 if (r < 0)
1873 return log_error_errno(r, "Failed to set columns to display: %m");
1874 }
1875
1876 r = table_print_with_pager(details_table, json_format_flags, pager_flags, /* show_header= */true);
ec16f3b6
LP
1877 if (r < 0)
1878 return log_error_errno(r, "Failed to output table: %m");
1879 }
1880
1881 exposure = DIV_ROUND_UP(badness_sum * 100U, weight_sum);
1882
1883 for (i = 0; i < ELEMENTSOF(badness_table); i++)
1884 if (exposure >= badness_table[i].exposure)
1885 break;
1886
1887 assert(i < ELEMENTSOF(badness_table));
1888
4b4a8ef7 1889 if (details_table && (json_format_flags & JSON_FORMAT_OFF)) {
ec16f3b6
LP
1890 _cleanup_free_ char *clickable = NULL;
1891 const char *name;
1892
1893 /* If we shall output the details table, also print the brief summary underneath */
1894
1895 if (info->fragment_path) {
1896 r = terminal_urlify_path(info->fragment_path, info->id, &clickable);
1897 if (r < 0)
1898 return log_oom();
1899
1900 name = clickable;
1901 } else
1902 name = info->id;
1903
1904 printf("\n%s %sOverall exposure level for %s%s: %s%" PRIu64 ".%" PRIu64 " %s%s %s\n",
9a6f746f 1905 special_glyph(SPECIAL_GLYPH_ARROW),
ec16f3b6
LP
1906 ansi_highlight(),
1907 name,
1908 ansi_normal(),
1909 colors_enabled() ? strempty(badness_table[i].color) : "",
1910 exposure / 10, exposure % 10,
1911 badness_table[i].name,
1912 ansi_normal(),
1913 special_glyph(badness_table[i].smiley));
1914 }
1915
1916 fflush(stdout);
1917
1918 if (overview_table) {
1919 char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
9c46b437 1920 _cleanup_free_ char *url = NULL;
ec16f3b6 1921
ec16f3b6 1922 if (info->fragment_path) {
ec16f3b6
LP
1923 r = file_url_from_path(info->fragment_path, &url);
1924 if (r < 0)
1925 return log_error_errno(r, "Failed to generate URL from path: %m");
ec16f3b6
LP
1926 }
1927
1928 xsprintf(buf, "%" PRIu64 ".%" PRIu64, exposure / 10, exposure % 10);
ec16f3b6 1929
9c46b437
YW
1930 r = table_add_many(overview_table,
1931 TABLE_STRING, info->id,
1932 TABLE_SET_URL, url,
1933 TABLE_STRING, buf,
1934 TABLE_SET_ALIGN_PERCENT, 100,
1935 TABLE_STRING, badness_table[i].name,
1936 TABLE_SET_COLOR, strempty(badness_table[i].color),
1937 TABLE_STRING, special_glyph(badness_table[i].smiley));
ec16f3b6 1938 if (r < 0)
9c46b437 1939 return table_log_add_error(r);
ec16f3b6
LP
1940 }
1941
dfbda879
MG
1942 /* Return error when overall exposure level is over threshold */
1943 if (exposure > threshold)
1944 return -EINVAL;
1945
ec16f3b6
LP
1946 return 0;
1947}
1948
1624114d
MG
1949static int property_read_restrict_namespaces(
1950 sd_bus *bus,
1951 const char *member,
1952 sd_bus_message *m,
1953 sd_bus_error *error,
1954 void *userdata) {
1955
1956 SecurityInfo *info = userdata;
1957 int r;
1958 uint64_t namespaces;
1959
1960 assert(bus);
1961 assert(member);
1962 assert(m);
1963 assert(info);
1964
1965 r = sd_bus_message_read(m, "t", &namespaces);
1966 if (r < 0)
1967 return r;
1968
1969 info->restrict_namespaces = (unsigned long long) namespaces;
1970
1971 return 0;
1972}
1973
1974static int property_read_umask(
1975 sd_bus *bus,
1976 const char *member,
1977 sd_bus_message *m,
1978 sd_bus_error *error,
1979 void *userdata) {
1980
1981 SecurityInfo *info = userdata;
1982 int r;
1983 uint32_t umask;
1984
1985 assert(bus);
1986 assert(member);
1987 assert(m);
1988 assert(info);
1989
1990 r = sd_bus_message_read(m, "u", &umask);
1991 if (r < 0)
1992 return r;
1993
1994 info->_umask = (mode_t) umask;
1995
1996 return 0;
1997}
1998
ec16f3b6
LP
1999static int property_read_restrict_address_families(
2000 sd_bus *bus,
2001 const char *member,
2002 sd_bus_message *m,
2003 sd_bus_error *error,
2004 void *userdata) {
2005
1624114d 2006 SecurityInfo *info = userdata;
6b000af4 2007 int allow_list, r;
ec16f3b6
LP
2008
2009 assert(bus);
2010 assert(member);
2011 assert(m);
2012
2013 r = sd_bus_message_enter_container(m, 'r', "bas");
2014 if (r < 0)
2015 return r;
2016
6b000af4 2017 r = sd_bus_message_read(m, "b", &allow_list);
ec16f3b6
LP
2018 if (r < 0)
2019 return r;
2020
2021 info->restrict_address_family_inet =
2022 info->restrict_address_family_unix =
2023 info->restrict_address_family_netlink =
2024 info->restrict_address_family_packet =
6b000af4 2025 info->restrict_address_family_other = allow_list;
ec16f3b6
LP
2026
2027 r = sd_bus_message_enter_container(m, 'a', "s");
2028 if (r < 0)
2029 return r;
2030
2031 for (;;) {
2032 const char *name;
2033
2034 r = sd_bus_message_read(m, "s", &name);
2035 if (r < 0)
2036 return r;
2037 if (r == 0)
2038 break;
2039
2040 if (STR_IN_SET(name, "AF_INET", "AF_INET6"))
6b000af4 2041 info->restrict_address_family_inet = !allow_list;
ec16f3b6 2042 else if (streq(name, "AF_UNIX"))
6b000af4 2043 info->restrict_address_family_unix = !allow_list;
ec16f3b6 2044 else if (streq(name, "AF_NETLINK"))
6b000af4 2045 info->restrict_address_family_netlink = !allow_list;
ec16f3b6 2046 else if (streq(name, "AF_PACKET"))
6b000af4 2047 info->restrict_address_family_packet = !allow_list;
ec16f3b6 2048 else
6b000af4 2049 info->restrict_address_family_other = !allow_list;
ec16f3b6
LP
2050 }
2051
2052 r = sd_bus_message_exit_container(m);
2053 if (r < 0)
2054 return r;
2055
2056 return sd_bus_message_exit_container(m);
2057}
2058
1624114d
MG
2059static int property_read_syscall_archs(
2060 sd_bus *bus,
2061 const char *member,
2062 sd_bus_message *m,
2063 sd_bus_error *error,
2064 void *userdata) {
2065
2066 SecurityInfo *info = userdata;
2067 int r;
2068
2069 assert(bus);
2070 assert(member);
2071 assert(m);
2072 assert(info);
2073
2074 r = sd_bus_message_enter_container(m, 'a', "s");
2075 if (r < 0)
2076 return r;
2077
2078 for (;;) {
2079 const char *name;
2080
2081 r = sd_bus_message_read(m, "s", &name);
2082 if (r < 0)
2083 return r;
2084 if (r == 0)
2085 break;
2086
2087 r = set_put_strdup(&info->system_call_architectures, name);
2088 if (r < 0)
2089 return r;
2090 }
2091
2092 return sd_bus_message_exit_container(m);
2093}
2094
ec16f3b6
LP
2095static int property_read_system_call_filter(
2096 sd_bus *bus,
2097 const char *member,
2098 sd_bus_message *m,
2099 sd_bus_error *error,
2100 void *userdata) {
2101
1624114d 2102 SecurityInfo *info = userdata;
6b000af4 2103 int allow_list, r;
ec16f3b6
LP
2104
2105 assert(bus);
2106 assert(member);
2107 assert(m);
2108
2109 r = sd_bus_message_enter_container(m, 'r', "bas");
2110 if (r < 0)
2111 return r;
2112
6b000af4 2113 r = sd_bus_message_read(m, "b", &allow_list);
ec16f3b6
LP
2114 if (r < 0)
2115 return r;
2116
6b000af4 2117 info->system_call_filter_allow_list = allow_list;
ec16f3b6
LP
2118
2119 r = sd_bus_message_enter_container(m, 'a', "s");
2120 if (r < 0)
2121 return r;
2122
2123 for (;;) {
2124 const char *name;
2125
2126 r = sd_bus_message_read(m, "s", &name);
2127 if (r < 0)
2128 return r;
2129 if (r == 0)
2130 break;
2131
1624114d
MG
2132 /* The actual ExecContext stores the system call id as the map value, which we don't
2133 * need. So we assign NULL to all values here. */
2134 r = hashmap_put_strdup(&info->system_call_filter, name, NULL);
ec16f3b6
LP
2135 if (r < 0)
2136 return r;
2137 }
2138
2139 r = sd_bus_message_exit_container(m);
2140 if (r < 0)
2141 return r;
2142
2143 return sd_bus_message_exit_container(m);
2144}
2145
2146static int property_read_ip_address_allow(
2147 sd_bus *bus,
2148 const char *member,
2149 sd_bus_message *m,
2150 sd_bus_error *error,
2151 void *userdata) {
2152
1624114d 2153 SecurityInfo *info = userdata;
ec16f3b6
LP
2154 bool deny_ipv4 = false, deny_ipv6 = false;
2155 int r;
2156
2157 assert(bus);
2158 assert(member);
2159 assert(m);
2160
2161 r = sd_bus_message_enter_container(m, 'a', "(iayu)");
2162 if (r < 0)
2163 return r;
2164
2165 for (;;) {
2166 const void *data;
2167 size_t size;
2168 int32_t family;
2169 uint32_t prefixlen;
2170
2171 r = sd_bus_message_enter_container(m, 'r', "iayu");
2172 if (r < 0)
2173 return r;
2174 if (r == 0)
2175 break;
2176
2177 r = sd_bus_message_read(m, "i", &family);
2178 if (r < 0)
2179 return r;
2180
2181 r = sd_bus_message_read_array(m, 'y', &data, &size);
2182 if (r < 0)
2183 return r;
2184
2185 r = sd_bus_message_read(m, "u", &prefixlen);
2186 if (r < 0)
2187 return r;
2188
2189 r = sd_bus_message_exit_container(m);
2190 if (r < 0)
2191 return r;
2192
2193 if (streq(member, "IPAddressAllow")) {
2194 union in_addr_union u;
2195
2196 if (family == AF_INET && size == 4 && prefixlen == 8)
2197 memcpy(&u.in, data, size);
2198 else if (family == AF_INET6 && size == 16 && prefixlen == 128)
2199 memcpy(&u.in6, data, size);
2200 else {
2201 info->ip_address_allow_other = true;
2202 continue;
2203 }
2204
2205 if (in_addr_is_localhost(family, &u))
2206 info->ip_address_allow_localhost = true;
2207 else
2208 info->ip_address_allow_other = true;
2209 } else {
2210 assert(streq(member, "IPAddressDeny"));
2211
2212 if (family == AF_INET && size == 4 && prefixlen == 0)
2213 deny_ipv4 = true;
2214 else if (family == AF_INET6 && size == 16 && prefixlen == 0)
2215 deny_ipv6 = true;
2216 }
2217 }
2218
2219 info->ip_address_deny_all = deny_ipv4 && deny_ipv6;
2220
2221 return sd_bus_message_exit_container(m);
2222}
2223
fab34748
KL
2224static int property_read_ip_filters(
2225 sd_bus *bus,
2226 const char *member,
2227 sd_bus_message *m,
2228 sd_bus_error *error,
2229 void *userdata) {
2230
1624114d 2231 SecurityInfo *info = userdata;
fab34748
KL
2232 _cleanup_(strv_freep) char **l = NULL;
2233 int r;
2234
2235 assert(bus);
2236 assert(member);
2237 assert(m);
2238
2239 r = sd_bus_message_read_strv(m, &l);
2240 if (r < 0)
2241 return r;
2242
2243 if (streq(member, "IPIngressFilterPath"))
2244 info->ip_filters_custom_ingress = !strv_isempty(l);
2245 else if (streq(member, "IPEgressFilterPath"))
3da57008 2246 info->ip_filters_custom_egress = !strv_isempty(l);
fab34748
KL
2247
2248 return 0;
2249}
2250
ec16f3b6
LP
2251static int property_read_device_allow(
2252 sd_bus *bus,
2253 const char *member,
2254 sd_bus_message *m,
2255 sd_bus_error *error,
2256 void *userdata) {
2257
1624114d 2258 SecurityInfo *info = userdata;
ec16f3b6
LP
2259 size_t n = 0;
2260 int r;
2261
2262 assert(bus);
2263 assert(member);
2264 assert(m);
2265
2266 r = sd_bus_message_enter_container(m, 'a', "(ss)");
2267 if (r < 0)
2268 return r;
2269
2270 for (;;) {
2271 const char *name, *policy;
2272
2273 r = sd_bus_message_read(m, "(ss)", &name, &policy);
2274 if (r < 0)
2275 return r;
2276 if (r == 0)
2277 break;
2278
2279 n++;
2280 }
2281
2282 info->device_allow_non_empty = n > 0;
2283
2284 return sd_bus_message_exit_container(m);
2285}
2286
1624114d 2287static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *info, AnalyzeSecurityFlags flags) {
ec16f3b6
LP
2288
2289 static const struct bus_properties_map security_map[] = {
1624114d
MG
2290 { "AmbientCapabilities", "t", NULL, offsetof(SecurityInfo, ambient_capabilities) },
2291 { "CapabilityBoundingSet", "t", NULL, offsetof(SecurityInfo, capability_bounding_set) },
2292 { "DefaultDependencies", "b", NULL, offsetof(SecurityInfo, default_dependencies) },
2293 { "Delegate", "b", NULL, offsetof(SecurityInfo, delegate) },
2294 { "DeviceAllow", "a(ss)", property_read_device_allow, 0 },
2295 { "DevicePolicy", "s", NULL, offsetof(SecurityInfo, device_policy) },
2296 { "DynamicUser", "b", NULL, offsetof(SecurityInfo, dynamic_user) },
2297 { "FragmentPath", "s", NULL, offsetof(SecurityInfo, fragment_path) },
2298 { "IPAddressAllow", "a(iayu)", property_read_ip_address_allow, 0 },
2299 { "IPAddressDeny", "a(iayu)", property_read_ip_address_allow, 0 },
2300 { "IPIngressFilterPath", "as", property_read_ip_filters, 0 },
2301 { "IPEgressFilterPath", "as", property_read_ip_filters, 0 },
2302 { "Id", "s", NULL, offsetof(SecurityInfo, id) },
2303 { "KeyringMode", "s", NULL, offsetof(SecurityInfo, keyring_mode) },
2304 { "ProtectProc", "s", NULL, offsetof(SecurityInfo, protect_proc) },
2305 { "ProcSubset", "s", NULL, offsetof(SecurityInfo, proc_subset) },
2306 { "LoadState", "s", NULL, offsetof(SecurityInfo, load_state) },
2307 { "LockPersonality", "b", NULL, offsetof(SecurityInfo, lock_personality) },
2308 { "MemoryDenyWriteExecute", "b", NULL, offsetof(SecurityInfo, memory_deny_write_execute) },
2309 { "NoNewPrivileges", "b", NULL, offsetof(SecurityInfo, no_new_privileges) },
2310 { "NotifyAccess", "s", NULL, offsetof(SecurityInfo, notify_access) },
2311 { "PrivateDevices", "b", NULL, offsetof(SecurityInfo, private_devices) },
2312 { "PrivateMounts", "b", NULL, offsetof(SecurityInfo, private_mounts) },
2313 { "PrivateNetwork", "b", NULL, offsetof(SecurityInfo, private_network) },
2314 { "PrivateTmp", "b", NULL, offsetof(SecurityInfo, private_tmp) },
2315 { "PrivateUsers", "b", NULL, offsetof(SecurityInfo, private_users) },
2316 { "ProtectControlGroups", "b", NULL, offsetof(SecurityInfo, protect_control_groups) },
2317 { "ProtectHome", "s", NULL, offsetof(SecurityInfo, protect_home) },
2318 { "ProtectHostname", "b", NULL, offsetof(SecurityInfo, protect_hostname) },
2319 { "ProtectKernelModules", "b", NULL, offsetof(SecurityInfo, protect_kernel_modules) },
2320 { "ProtectKernelTunables", "b", NULL, offsetof(SecurityInfo, protect_kernel_tunables) },
2321 { "ProtectKernelLogs", "b", NULL, offsetof(SecurityInfo, protect_kernel_logs) },
2322 { "ProtectClock", "b", NULL, offsetof(SecurityInfo, protect_clock) },
2323 { "ProtectSystem", "s", NULL, offsetof(SecurityInfo, protect_system) },
2324 { "RemoveIPC", "b", NULL, offsetof(SecurityInfo, remove_ipc) },
2325 { "RestrictAddressFamilies", "(bas)", property_read_restrict_address_families, 0 },
2326 { "RestrictNamespaces", "t", property_read_restrict_namespaces, 0 },
2327 { "RestrictRealtime", "b", NULL, offsetof(SecurityInfo, restrict_realtime) },
2328 { "RestrictSUIDSGID", "b", NULL, offsetof(SecurityInfo, restrict_suid_sgid) },
2329 { "RootDirectory", "s", NULL, offsetof(SecurityInfo, root_directory) },
2330 { "RootImage", "s", NULL, offsetof(SecurityInfo, root_image) },
2331 { "SupplementaryGroups", "as", NULL, offsetof(SecurityInfo, supplementary_groups) },
2332 { "SystemCallArchitectures", "as", property_read_syscall_archs, 0 },
2333 { "SystemCallFilter", "(as)", property_read_system_call_filter, 0 },
2334 { "Type", "s", NULL, offsetof(SecurityInfo, type) },
2335 { "UMask", "u", property_read_umask, 0 },
2336 { "User", "s", NULL, offsetof(SecurityInfo, user) },
ec16f3b6
LP
2337 {}
2338 };
2339
2340 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2341 _cleanup_free_ char *path = NULL;
2342 int r;
2343
2344 /* Note: this mangles *info on failure! */
2345
2346 assert(bus);
2347 assert(name);
2348 assert(info);
2349
2350 path = unit_dbus_path_from_name(name);
2351 if (!path)
2352 return log_oom();
2353
1ace223c
SJ
2354 r = bus_map_all_properties(
2355 bus,
2356 "org.freedesktop.systemd1",
2357 path,
2358 security_map,
2359 BUS_MAP_STRDUP | BUS_MAP_BOOLEAN_AS_BOOL,
2360 &error,
2361 NULL,
2362 info);
ec16f3b6
LP
2363 if (r < 0)
2364 return log_error_errno(r, "Failed to get unit properties: %s", bus_error_message(&error, r));
2365
2366 if (!streq_ptr(info->load_state, "loaded")) {
2367
2368 if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LOADED))
2369 return -EMEDIUMTYPE;
2370
2371 if (streq_ptr(info->load_state, "not-found"))
2372 log_error("Unit %s not found, cannot analyze.", name);
2373 else if (streq_ptr(info->load_state, "masked"))
2374 log_error("Unit %s is masked, cannot analyze.", name);
2375 else
2376 log_error("Unit %s not loaded properly, cannot analyze.", name);
2377
2378 return -EINVAL;
2379 }
2380
2381 if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LONG_RUNNING) && streq_ptr(info->type, "oneshot"))
2382 return -EMEDIUMTYPE;
2383
2384 if (info->private_devices ||
2385 info->private_tmp ||
2386 info->protect_control_groups ||
2387 info->protect_kernel_tunables ||
2388 info->protect_kernel_modules ||
2389 !streq_ptr(info->protect_home, "no") ||
2390 !streq_ptr(info->protect_system, "no") ||
2391 info->root_image)
2392 info->private_mounts = true;
2393
2394 if (info->protect_kernel_modules)
2395 info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYS_MODULE);
2396
82dce83b
KK
2397 if (info->protect_kernel_logs)
2398 info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYSLOG);
2399
9f37272a
KK
2400 if (info->protect_clock)
2401 info->capability_bounding_set &= ~((UINT64_C(1) << CAP_SYS_TIME) |
2402 (UINT64_C(1) << CAP_WAKE_ALARM));
2403
ec16f3b6
LP
2404 if (info->private_devices)
2405 info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) |
2406 (UINT64_C(1) << CAP_SYS_RAWIO));
2407
2408 return 0;
2409}
2410
3838d22c
MG
2411static int analyze_security_one(sd_bus *bus,
2412 const char *name,
2413 Table *overview_table,
2414 AnalyzeSecurityFlags flags,
2415 unsigned threshold,
4b4a8ef7
MG
2416 JsonVariant *policy,
2417 PagerFlags pager_flags,
2418 JsonFormatFlags json_format_flags) {
dfbda879 2419
1624114d
MG
2420 _cleanup_(security_info_freep) SecurityInfo *info = security_info_new();
2421 if (!info)
2422 return log_oom();
2423
ec16f3b6
LP
2424 int r;
2425
2426 assert(bus);
2427 assert(name);
2428
1624114d 2429 r = acquire_security_info(bus, name, info, flags);
ec16f3b6
LP
2430 if (r == -EMEDIUMTYPE) /* Ignore this one because not loaded or Type is oneshot */
2431 return 0;
2432 if (r < 0)
2433 return r;
2434
4b4a8ef7 2435 r = assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
ec16f3b6
LP
2436 if (r < 0)
2437 return r;
2438
2439 return 0;
2440}
2441
1624114d
MG
2442/* Refactoring SecurityInfo so that it can make use of existing struct variables instead of reading from dbus */
2443static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, SecurityInfo **ret_info) {
2444 assert(ret_info);
2445
2446 _cleanup_(security_info_freep) SecurityInfo *info = security_info_new();
2447 if (!info)
2448 return log_oom();
2449
2450 if (u) {
2451 if (u->id) {
2452 info->id = strdup(u->id);
2453 if (!info->id)
2454 return log_oom();
2455 }
2456 if (unit_type_to_string(u->type)) {
2457 info->type = strdup(unit_type_to_string(u->type));
2458 if (!info->type)
2459 return log_oom();
2460 }
2461 if (unit_load_state_to_string(u->load_state)) {
2462 info->load_state = strdup(unit_load_state_to_string(u->load_state));
2463 if (!info->load_state)
2464 return log_oom();
2465 }
2466 if (u->fragment_path) {
2467 info->fragment_path = strdup(u->fragment_path);
2468 if (!info->fragment_path)
2469 return log_oom();
2470 }
2471 info->default_dependencies = u->default_dependencies;
2472 if (u->type == UNIT_SERVICE && notify_access_to_string(SERVICE(u)->notify_access)) {
2473 info->notify_access = strdup(notify_access_to_string(SERVICE(u)->notify_access));
2474 if (!info->notify_access)
2475 return log_oom();
2476 }
2477 }
2478
2479 if (c) {
2480 info->ambient_capabilities = c->capability_ambient_set;
2481 info->capability_bounding_set = c->capability_bounding_set;
2482 if (c->user) {
2483 info->user = strdup(c->user);
2484 if (!info->user)
2485 return log_oom();
2486 }
2487 if (c->supplementary_groups) {
2488 info->supplementary_groups = strv_copy(c->supplementary_groups);
2489 if (!info->supplementary_groups)
2490 return log_oom();
2491 }
2492 info->dynamic_user = c->dynamic_user;
2493 if (exec_keyring_mode_to_string(c->keyring_mode)) {
2494 info->keyring_mode = strdup(exec_keyring_mode_to_string(c->keyring_mode));
2495 if (!info->keyring_mode)
2496 return log_oom();
2497 }
2498 if (protect_proc_to_string(c->protect_proc)) {
2499 info->protect_proc = strdup(protect_proc_to_string(c->protect_proc));
2500 if (!info->protect_proc)
2501 return log_oom();
2502 }
2503 if (proc_subset_to_string(c->proc_subset)) {
2504 info->proc_subset = strdup(proc_subset_to_string(c->proc_subset));
2505 if (!info->proc_subset)
2506 return log_oom();
2507 }
2508 info->lock_personality = c->lock_personality;
2509 info->memory_deny_write_execute = c->memory_deny_write_execute;
2510 info->no_new_privileges = c->no_new_privileges;
2511 info->protect_hostname = c->protect_hostname;
2512 info->private_devices = c->private_devices;
2513 info->private_mounts = c->private_mounts;
2514 info->private_network = c->private_network;
2515 info->private_tmp = c->private_tmp;
2516 info->private_users = c->private_users;
2517 info->protect_control_groups = c->protect_control_groups;
2518 info->protect_kernel_modules = c->protect_kernel_modules;
2519 info->protect_kernel_tunables = c->protect_kernel_tunables;
2520 info->protect_kernel_logs = c->protect_kernel_logs;
2521 info->protect_clock = c->protect_clock;
2522 if (protect_home_to_string(c->protect_home)) {
2523 info->protect_home = strdup(protect_home_to_string(c->protect_home));
2524 if (!info->protect_home)
2525 return log_oom();
2526 }
2527 if (protect_system_to_string(c->protect_system)) {
2528 info->protect_system = strdup(protect_system_to_string(c->protect_system));
2529 if (!info->protect_system)
2530 return log_oom();
2531 }
2532 info->remove_ipc = c->remove_ipc;
2533 info->restrict_address_family_inet =
2534 info->restrict_address_family_unix =
2535 info->restrict_address_family_netlink =
2536 info->restrict_address_family_packet =
2537 info->restrict_address_family_other =
2538 c->address_families_allow_list;
2539
2540 void *key;
2541 SET_FOREACH(key, c->address_families) {
2542 int family = PTR_TO_INT(key);
2543 if (family == 0)
2544 continue;
2545 if (IN_SET(family, AF_INET, AF_INET6))
2546 info->restrict_address_family_inet = !c->address_families_allow_list;
2547 else if (family == AF_UNIX)
2548 info->restrict_address_family_unix = !c->address_families_allow_list;
2549 else if (family == AF_NETLINK)
2550 info->restrict_address_family_netlink = !c->address_families_allow_list;
2551 else if (family == AF_PACKET)
2552 info->restrict_address_family_packet = !c->address_families_allow_list;
2553 else
2554 info->restrict_address_family_other = !c->address_families_allow_list;
2555 }
2556
2557 info->restrict_namespaces = c->restrict_namespaces;
2558 info->restrict_realtime = c->restrict_realtime;
2559 info->restrict_suid_sgid = c->restrict_suid_sgid;
2560 if (c->root_directory) {
2561 info->root_directory = strdup(c->root_directory);
2562 if (!info->root_directory)
2563 return log_oom();
2564 }
2565 if (c->root_image) {
2566 info->root_image = strdup(c->root_image);
2567 if (!info->root_image)
2568 return log_oom();
2569 }
2570 info->_umask = c->umask;
2571 if (c->syscall_archs) {
2572 info->system_call_architectures = set_copy(c->syscall_archs);
2573 if (!info->system_call_architectures)
2574 return log_oom();
2575 }
2576 info->system_call_filter_allow_list = c->syscall_allow_list;
2577 if (c->syscall_filter) {
2578 info->system_call_filter = hashmap_copy(c->syscall_filter);
2579 if (!info->system_call_filter)
2580 return log_oom();
2581 }
2582 }
2583
2584 if (g) {
2585 info->delegate = g->delegate;
2586 if (cgroup_device_policy_to_string(g->device_policy)) {
2587 info->device_policy = strdup(cgroup_device_policy_to_string(g->device_policy));
2588 if (!info->device_policy)
2589 return log_oom();
2590 }
2591
84ebe6f0 2592 struct in_addr_prefix *i;
1624114d
MG
2593 bool deny_ipv4 = false, deny_ipv6 = false;
2594
84ebe6f0 2595 SET_FOREACH(i, g->ip_address_deny) {
1624114d
MG
2596 if (i->family == AF_INET && i->prefixlen == 0)
2597 deny_ipv4 = true;
2598 else if (i->family == AF_INET6 && i->prefixlen == 0)
2599 deny_ipv6 = true;
2600 }
2601 info->ip_address_deny_all = deny_ipv4 && deny_ipv6;
2602
2603 info->ip_address_allow_localhost = info->ip_address_allow_other = false;
84ebe6f0 2604 SET_FOREACH(i, g->ip_address_allow) {
1624114d
MG
2605 if (in_addr_is_localhost(i->family, &i->address))
2606 info->ip_address_allow_localhost = true;
2607 else
2608 info->ip_address_allow_other = true;
2609 }
2610
2611 info->ip_filters_custom_ingress = !strv_isempty(g->ip_filters_ingress);
2612 info->ip_filters_custom_egress = !strv_isempty(g->ip_filters_egress);
2613 info->device_allow_non_empty = !LIST_IS_EMPTY(g->device_allow);
2614 }
2615
2616 *ret_info = TAKE_PTR(info);
2617
2618 return 0;
2619}
2620
4b4a8ef7
MG
2621static int offline_security_check(Unit *u,
2622 unsigned threshold,
2623 JsonVariant *policy,
2624 PagerFlags pager_flags,
2625 JsonFormatFlags json_format_flags) {
2626
bb43d853
MG
2627 _cleanup_(table_unrefp) Table *overview_table = NULL;
2628 AnalyzeSecurityFlags flags = 0;
2629 _cleanup_(security_info_freep) SecurityInfo *info = NULL;
2630 int r;
2631
2632 assert(u);
2633
2634 if (DEBUG_LOGGING)
2635 unit_dump(u, stdout, "\t");
2636
2637 r = get_security_info(u, unit_get_exec_context(u), unit_get_cgroup_context(u), &info);
2638 if (r < 0)
2639 return r;
2640
4b4a8ef7 2641 return assess(info, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
bb43d853
MG
2642}
2643
3838d22c
MG
2644static int offline_security_checks(char **filenames,
2645 JsonVariant *policy,
2646 UnitFileScope scope,
2647 bool check_man,
2648 bool run_generators,
2649 unsigned threshold,
4b4a8ef7 2650 const char *root,
04469211 2651 const char *profile,
4b4a8ef7
MG
2652 PagerFlags pager_flags,
2653 JsonFormatFlags json_format_flags) {
3838d22c 2654
bb43d853
MG
2655 const ManagerTestRunFlags flags =
2656 MANAGER_TEST_RUN_MINIMAL |
2657 MANAGER_TEST_RUN_ENV_GENERATORS |
3f1487f5 2658 MANAGER_TEST_RUN_IGNORE_DEPENDENCIES |
bb43d853
MG
2659 run_generators * MANAGER_TEST_RUN_GENERATORS;
2660
2661 _cleanup_(manager_freep) Manager *m = NULL;
2662 Unit *units[strv_length(filenames)];
2663 _cleanup_free_ char *var = NULL;
2664 int r, k;
2665 size_t count = 0;
2666 char **filename;
2667
2668 if (strv_isempty(filenames))
2669 return 0;
2670
2671 /* set the path */
2672 r = verify_generate_path(&var, filenames);
2673 if (r < 0)
2674 return log_error_errno(r, "Failed to generate unit load path: %m");
2675
2676 assert_se(set_unit_path(var) >= 0);
2677
2678 r = manager_new(scope, flags, &m);
2679 if (r < 0)
2680 return log_error_errno(r, "Failed to initialize manager: %m");
2681
2682 log_debug("Starting manager...");
2683
2684 r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root);
2685 if (r < 0)
2686 return r;
2687
04469211
LB
2688 if (profile) {
2689 /* Ensure the temporary directory is in the search path, so that we can add drop-ins. */
2690 r = strv_extend(&m->lookup_paths.search_path, m->lookup_paths.temporary_dir);
2691 if (r < 0)
2692 return log_oom();
2693 }
2694
bb43d853
MG
2695 log_debug("Loading remaining units from the command line...");
2696
2697 STRV_FOREACH(filename, filenames) {
2698 _cleanup_free_ char *prepared = NULL;
2699
2700 log_debug("Handling %s...", *filename);
2701
2702 k = verify_prepare_filename(*filename, &prepared);
2703 if (k < 0) {
2704 log_warning_errno(k, "Failed to prepare filename %s: %m", *filename);
2705 if (r == 0)
2706 r = k;
2707 continue;
2708 }
2709
04469211
LB
2710 /* When a portable image is analyzed, the profile is what provides a good chunk of
2711 * the security-related settings, but they are obviously not shipped with the image.
2712 * This allows to take them in consideration. */
2713 if (profile) {
2714 _cleanup_free_ char *unit_name = NULL, *dropin = NULL, *profile_path = NULL;
2715
2716 r = path_extract_filename(prepared, &unit_name);
2717 if (r < 0)
2718 return log_oom();
2719
2720 dropin = strjoin(m->lookup_paths.temporary_dir, "/", unit_name, ".d/profile.conf");
2721 if (!dropin)
2722 return log_oom();
2723 (void) mkdir_parents(dropin, 0755);
2724
2725 if (!is_path(profile)) {
2726 r = find_portable_profile(profile, unit_name, &profile_path);
2727 if (r < 0)
2728 return log_error_errno(r, "Failed to find portable profile %s: %m", profile);
2729 profile = profile_path;
2730 }
2731
2732 r = copy_file(profile, dropin, 0, 0644, 0, 0, 0);
2733 if (r < 0)
2734 return log_error_errno(r, "Failed to copy: %m");
2735 }
2736
bb43d853
MG
2737 k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]);
2738 if (k < 0) {
2739 if (r == 0)
2740 r = k;
2741 continue;
2742 }
2743
2744 count++;
2745 }
2746
2747 for (size_t i = 0; i < count; i++) {
4b4a8ef7 2748 k = offline_security_check(units[i], threshold, policy, pager_flags, json_format_flags);
bb43d853
MG
2749 if (k < 0 && r == 0)
2750 r = k;
2751 }
2752
2753 return r;
2754}
2755
ecfd082b
MG
2756int analyze_security(sd_bus *bus,
2757 char **units,
2758 JsonVariant *policy,
2759 UnitFileScope scope,
2760 bool check_man,
2761 bool run_generators,
2762 bool offline,
2763 unsigned threshold,
2764 const char *root,
04469211 2765 const char *profile,
4b4a8ef7
MG
2766 JsonFormatFlags json_format_flags,
2767 PagerFlags pager_flags,
ecfd082b 2768 AnalyzeSecurityFlags flags) {
dfbda879 2769
ec16f3b6
LP
2770 _cleanup_(table_unrefp) Table *overview_table = NULL;
2771 int ret = 0, r;
2772
741c4c8d 2773 assert(!!bus != offline);
ec16f3b6 2774
bb43d853 2775 if (offline)
04469211 2776 return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root, profile, pager_flags, json_format_flags);
bb43d853 2777
ec16f3b6 2778 if (strv_length(units) != 1) {
9969b542 2779 overview_table = table_new("unit", "exposure", "predicate", "happy");
ec16f3b6
LP
2780 if (!overview_table)
2781 return log_oom();
2782 }
2783
2784 if (strv_isempty(units)) {
2785 _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
2786 _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
2787 _cleanup_strv_free_ char **list = NULL;
319a4f4b 2788 size_t n = 0;
ec16f3b6
LP
2789 char **i;
2790
2791 r = sd_bus_call_method(
2792 bus,
2793 "org.freedesktop.systemd1",
2794 "/org/freedesktop/systemd1",
2795 "org.freedesktop.systemd1.Manager",
2796 "ListUnits",
1ace223c
SJ
2797 &error,
2798 &reply,
ec16f3b6
LP
2799 NULL);
2800 if (r < 0)
2801 return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
2802
2803 r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
2804 if (r < 0)
2805 return bus_log_parse_error(r);
2806
2807 for (;;) {
2808 UnitInfo info;
2809 char *copy = NULL;
2810
2811 r = bus_parse_unit_info(reply, &info);
2812 if (r < 0)
2813 return bus_log_parse_error(r);
2814 if (r == 0)
2815 break;
2816
2817 if (!endswith(info.id, ".service"))
2818 continue;
2819
319a4f4b 2820 if (!GREEDY_REALLOC(list, n + 2))
ec16f3b6
LP
2821 return log_oom();
2822
2823 copy = strdup(info.id);
2824 if (!copy)
2825 return log_oom();
2826
2827 list[n++] = copy;
2828 list[n] = NULL;
2829 }
2830
2831 strv_sort(list);
2832
2833 flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
2834
2835 STRV_FOREACH(i, list) {
4b4a8ef7 2836 r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
ec16f3b6
LP
2837 if (r < 0 && ret >= 0)
2838 ret = r;
2839 }
2840
2841 } else {
2842 char **i;
2843
2844 STRV_FOREACH(i, units) {
2845 _cleanup_free_ char *mangled = NULL, *instance = NULL;
2846 const char *name;
2847
2848 if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT) && i != units) {
2849 putc('\n', stdout);
2850 fflush(stdout);
2851 }
2852
df7c4eb6 2853 r = unit_name_mangle(*i, 0, &mangled);
ec16f3b6
LP
2854 if (r < 0)
2855 return log_error_errno(r, "Failed to mangle unit name '%s': %m", *i);
2856
d7a0f1f4
FS
2857 if (!endswith(mangled, ".service"))
2858 return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
2859 "Unit %s is not a service unit, refusing.",
2860 *i);
ec16f3b6
LP
2861
2862 if (unit_name_is_valid(mangled, UNIT_NAME_TEMPLATE)) {
2863 r = unit_name_replace_instance(mangled, "test-instance", &instance);
2864 if (r < 0)
2865 return log_oom();
2866
2867 name = instance;
2868 } else
2869 name = mangled;
2870
4b4a8ef7 2871 r = analyze_security_one(bus, name, overview_table, flags, threshold, policy, pager_flags, json_format_flags);
ec16f3b6
LP
2872 if (r < 0 && ret >= 0)
2873 ret = r;
2874 }
2875 }
2876
2877 if (overview_table) {
2878 if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
2879 putc('\n', stdout);
2880 fflush(stdout);
2881 }
2882
4b4a8ef7 2883 r = table_print_with_pager(overview_table, json_format_flags, pager_flags, /* show_header= */true);
ec16f3b6
LP
2884 if (r < 0)
2885 return log_error_errno(r, "Failed to output table: %m");
2886 }
ec16f3b6
LP
2887 return ret;
2888}