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