4bff0a
From f5bd75fb574b9be80879fda5a889ddfd2b706248 Mon Sep 17 00:00:00 2001
4bff0a
From: Lennart Poettering <lennart@poettering.net>
4bff0a
Date: Thu, 8 Nov 2018 09:32:17 +0100
4bff0a
Subject: [PATCH] analyze: add new security verb
4bff0a
4bff0a
(cherry picked from commit ec16f3b6dd8b03e3ce6eff1fa9f21432208ef42b)
4bff0a
4bff0a
Conflicts:
4bff0a
	src/analyze/analyze.c
4bff0a
4bff0a
Resolves: #1689832
4bff0a
---
4bff0a
 src/analyze/analyze-security.c | 2078 ++++++++++++++++++++++++++++++++
4bff0a
 src/analyze/analyze-security.h |   12 +
4bff0a
 src/analyze/analyze.c          |   16 +
4bff0a
 src/analyze/meson.build        |    2 +
4bff0a
 src/basic/format-table.c       |    1 -
4bff0a
 src/basic/macro.h              |   13 +-
4bff0a
 src/basic/terminal-util.c      |   53 +-
4bff0a
 src/basic/terminal-util.h      |    1 +
4bff0a
 8 files changed, 2150 insertions(+), 26 deletions(-)
4bff0a
 create mode 100644 src/analyze/analyze-security.c
4bff0a
 create mode 100644 src/analyze/analyze-security.h
4bff0a
4bff0a
diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c
4bff0a
new file mode 100644
4bff0a
index 0000000000..541fc0d97a
4bff0a
--- /dev/null
4bff0a
+++ b/src/analyze/analyze-security.c
4bff0a
@@ -0,0 +1,2078 @@
4bff0a
+/* SPDX-License-Identifier: LGPL-2.1+ */
4bff0a
+
4bff0a
+#include <sched.h>
4bff0a
+#include <sys/utsname.h>
4bff0a
+
4bff0a
+#include "analyze-security.h"
4bff0a
+#include "bus-error.h"
4bff0a
+#include "bus-unit-util.h"
4bff0a
+#include "bus-util.h"
4bff0a
+#include "env-util.h"
4bff0a
+#include "format-table.h"
4bff0a
+#include "in-addr-util.h"
4bff0a
+#include "locale-util.h"
4bff0a
+#include "macro.h"
4bff0a
+#include "parse-util.h"
4bff0a
+#include "path-util.h"
4bff0a
+#include "seccomp-util.h"
4bff0a
+#include "set.h"
4bff0a
+#include "stdio-util.h"
4bff0a
+#include "strv.h"
4bff0a
+#include "terminal-util.h"
4bff0a
+#include "unit-def.h"
4bff0a
+#include "unit-name.h"
4bff0a
+
4bff0a
+struct security_info {
4bff0a
+        char *id;
4bff0a
+        char *type;
4bff0a
+        char *load_state;
4bff0a
+        char *fragment_path;
4bff0a
+        bool default_dependencies;
4bff0a
+
4bff0a
+        uint64_t ambient_capabilities;
4bff0a
+        uint64_t capability_bounding_set;
4bff0a
+
4bff0a
+        char *user;
4bff0a
+        char **supplementary_groups;
4bff0a
+        bool dynamic_user;
4bff0a
+
4bff0a
+        bool ip_address_deny_all;
4bff0a
+        bool ip_address_allow_localhost;
4bff0a
+        bool ip_address_allow_other;
4bff0a
+
4bff0a
+        char *keyring_mode;
4bff0a
+        bool lock_personality;
4bff0a
+        bool memory_deny_write_execute;
4bff0a
+        bool no_new_privileges;
4bff0a
+        char *notify_access;
4bff0a
+
4bff0a
+        bool private_devices;
4bff0a
+        bool private_mounts;
4bff0a
+        bool private_network;
4bff0a
+        bool private_tmp;
4bff0a
+        bool private_users;
4bff0a
+
4bff0a
+        bool protect_control_groups;
4bff0a
+        bool protect_kernel_modules;
4bff0a
+        bool protect_kernel_tunables;
4bff0a
+
4bff0a
+        char *protect_home;
4bff0a
+        char *protect_system;
4bff0a
+
4bff0a
+        bool remove_ipc;
4bff0a
+
4bff0a
+        bool restrict_address_family_inet;
4bff0a
+        bool restrict_address_family_unix;
4bff0a
+        bool restrict_address_family_netlink;
4bff0a
+        bool restrict_address_family_packet;
4bff0a
+        bool restrict_address_family_other;
4bff0a
+
4bff0a
+        uint64_t restrict_namespaces;
4bff0a
+        bool restrict_realtime;
4bff0a
+
4bff0a
+        char *root_directory;
4bff0a
+        char *root_image;
4bff0a
+
4bff0a
+        bool delegate;
4bff0a
+        char *device_policy;
4bff0a
+        bool device_allow_non_empty;
4bff0a
+
4bff0a
+        char **system_call_architectures;
4bff0a
+
4bff0a
+        bool system_call_filter_whitelist;
4bff0a
+        Set *system_call_filter;
4bff0a
+
4bff0a
+        uint32_t _umask;
4bff0a
+};
4bff0a
+
4bff0a
+struct security_assessor {
4bff0a
+        const char *id;
4bff0a
+        const char *description_good;
4bff0a
+        const char *description_bad;
4bff0a
+        const char *description_na;
4bff0a
+        const char *url;
4bff0a
+        uint64_t weight;
4bff0a
+        uint64_t range;
4bff0a
+        int (*assess)(const struct security_assessor *a, const struct security_info *info, const void *data, uint64_t *ret_badness, char **ret_description);
4bff0a
+        size_t offset;
4bff0a
+        uint64_t parameter;
4bff0a
+        bool default_dependencies_only;
4bff0a
+};
4bff0a
+
4bff0a
+static void security_info_free(struct security_info *i) {
4bff0a
+        if (!i)
4bff0a
+                return;
4bff0a
+
4bff0a
+        free(i->id);
4bff0a
+        free(i->type);
4bff0a
+        free(i->load_state);
4bff0a
+        free(i->fragment_path);
4bff0a
+
4bff0a
+        free(i->user);
4bff0a
+
4bff0a
+        free(i->protect_home);
4bff0a
+        free(i->protect_system);
4bff0a
+
4bff0a
+        free(i->root_directory);
4bff0a
+        free(i->root_image);
4bff0a
+
4bff0a
+        free(i->keyring_mode);
4bff0a
+        free(i->notify_access);
4bff0a
+
4bff0a
+        free(i->device_policy);
4bff0a
+
4bff0a
+        strv_free(i->supplementary_groups);
4bff0a
+        strv_free(i->system_call_architectures);
4bff0a
+
4bff0a
+        set_free_free(i->system_call_filter);
4bff0a
+}
4bff0a
+
4bff0a
+static bool security_info_runs_privileged(const struct security_info *i)  {
4bff0a
+        assert(i);
4bff0a
+
4bff0a
+        if (STRPTR_IN_SET(i->user, "0", "root"))
4bff0a
+                return true;
4bff0a
+
4bff0a
+        if (i->dynamic_user)
4bff0a
+                return false;
4bff0a
+
4bff0a
+        return isempty(i->user);
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_bool(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        const bool *b = data;
4bff0a
+
4bff0a
+        assert(b);
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness = a->parameter ? *b : !*b;
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_user(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        _cleanup_free_ char *d = NULL;
4bff0a
+        uint64_t b;
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (streq_ptr(info->user, NOBODY_USER_NAME)) {
4bff0a
+                d = strdup("Service runs under as '" NOBODY_USER_NAME "' user, which should not be used for services");
4bff0a
+                b = 9;
4bff0a
+        } else if (info->dynamic_user && !STR_IN_SET(info->user, "0", "root")) {
4bff0a
+                d = strdup("Service runs under a transient non-root user identity");
4bff0a
+                b = 0;
4bff0a
+        } else if (info->user && !STR_IN_SET(info->user, "0", "root", "")) {
4bff0a
+                d = strdup("Service runs under a static non-root user identity");
4bff0a
+                b = 0;
4bff0a
+        } else {
4bff0a
+                *ret_badness = 10;
4bff0a
+                *ret_description = NULL;
4bff0a
+                return 0;
4bff0a
+        }
4bff0a
+
4bff0a
+        if (!d)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = b;
4bff0a
+        *ret_description = TAKE_PTR(d);
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_protect_home(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        const char *description;
4bff0a
+        uint64_t badness;
4bff0a
+        char *copy;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        badness = 10;
4bff0a
+        description = "Service has full access to home directories";
4bff0a
+
4bff0a
+        r = parse_boolean(info->protect_home);
4bff0a
+        if (r < 0) {
4bff0a
+                if (streq_ptr(info->protect_home, "read-only")) {
4bff0a
+                        badness = 5;
4bff0a
+                        description = "Service has read-only access to home directories";
4bff0a
+                } else if (streq_ptr(info->protect_home, "tmpfs")) {
4bff0a
+                        badness = 1;
4bff0a
+                        description = "Service has access to fake empty home directories";
4bff0a
+                }
4bff0a
+        } else if (r > 0) {
4bff0a
+                badness = 0;
4bff0a
+                description = "Service has no access to home directories";
4bff0a
+        }
4bff0a
+
4bff0a
+        copy = strdup(description);
4bff0a
+        if (!copy)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = badness;
4bff0a
+        *ret_description = copy;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_protect_system(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        const char *description;
4bff0a
+        uint64_t badness;
4bff0a
+        char *copy;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        badness = 10;
4bff0a
+        description = "Service has full access the OS file hierarchy";
4bff0a
+
4bff0a
+        r = parse_boolean(info->protect_system);
4bff0a
+        if (r < 0) {
4bff0a
+                if (streq_ptr(info->protect_system, "full")) {
4bff0a
+                        badness = 3;
4bff0a
+                        description = "Service has very limited write access to OS file hierarchy";
4bff0a
+                } else if (streq_ptr(info->protect_system, "strict")) {
4bff0a
+                        badness = 0;
4bff0a
+                        description = "Service has strict read-only access to the OS file hierarchy";
4bff0a
+                }
4bff0a
+        } else if (r > 0) {
4bff0a
+                badness = 5;
4bff0a
+                description = "Service has limited write access to the OS file hierarchy";
4bff0a
+        }
4bff0a
+
4bff0a
+        copy = strdup(description);
4bff0a
+        if (!copy)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = badness;
4bff0a
+        *ret_description = copy;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_root_directory(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness =
4bff0a
+                (isempty(info->root_directory) ||
4bff0a
+                 path_equal(info->root_directory, "/")) &&
4bff0a
+                (isempty(info->root_image) ||
4bff0a
+                 path_equal(info->root_image, "/"));
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_capability_bounding_set(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness = !!(info->capability_bounding_set & a->parameter);
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_umask(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        char *copy = NULL;
4bff0a
+        const char *d;
4bff0a
+        uint64_t b;
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (!FLAGS_SET(info->_umask, 0002)) {
4bff0a
+                d = "Files created by service are world-writable by default";
4bff0a
+                b = 10;
4bff0a
+        } else if (!FLAGS_SET(info->_umask, 0004)) {
4bff0a
+                d = "Files created by service are world-readable by default";
4bff0a
+                b = 5;
4bff0a
+        } else if (!FLAGS_SET(info->_umask, 0020)) {
4bff0a
+                d = "Files created by service are group-writable by default";
4bff0a
+                b = 2;
4bff0a
+        } else if (!FLAGS_SET(info->_umask, 0040)) {
4bff0a
+                d = "Files created by service are group-readable by default";
4bff0a
+                b = 1;
4bff0a
+        } else {
4bff0a
+                d = "Files created by service are accessible only by service's own user by default";
4bff0a
+                b = 0;
4bff0a
+        }
4bff0a
+
4bff0a
+        copy = strdup(d);
4bff0a
+        if (!copy)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = b;
4bff0a
+        *ret_description = copy;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_keyring_mode(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness = !streq_ptr(info->keyring_mode, "private");
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_notify_access(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness = streq_ptr(info->notify_access, "all");
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_remove_ipc(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (security_info_runs_privileged(info))
4bff0a
+                *ret_badness = UINT64_MAX;
4bff0a
+        else
4bff0a
+                *ret_badness = !info->remove_ipc;
4bff0a
+
4bff0a
+        *ret_description = NULL;
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_supplementary_groups(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (security_info_runs_privileged(info))
4bff0a
+                *ret_badness = UINT64_MAX;
4bff0a
+        else
4bff0a
+                *ret_badness = !strv_isempty(info->supplementary_groups);
4bff0a
+
4bff0a
+        *ret_description = NULL;
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_restrict_namespaces(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness = !!(info->restrict_namespaces & a->parameter);
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_system_call_architectures(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        char *d;
4bff0a
+        uint64_t b;
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (strv_isempty(info->system_call_architectures)) {
4bff0a
+                b = 10;
4bff0a
+                d = strdup("Service may execute system calls with all ABIs");
4bff0a
+        } else if (strv_equal(info->system_call_architectures, STRV_MAKE("native"))) {
4bff0a
+                b = 0;
4bff0a
+                d = strdup("Service may execute system calls only with native ABI");
4bff0a
+        } else {
4bff0a
+                b = 8;
4bff0a
+                d = strdup("Service may execute system calls with multiple ABIs");
4bff0a
+        }
4bff0a
+
4bff0a
+        if (!d)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = b;
4bff0a
+        *ret_description = d;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static bool syscall_names_in_filter(Set *s, bool whitelist, const SyscallFilterSet *f) {
4bff0a
+        const char *syscall;
4bff0a
+
4bff0a
+        NULSTR_FOREACH(syscall, f->value) {
4bff0a
+                bool b;
4bff0a
+
4bff0a
+                if (syscall[0] == '@') {
4bff0a
+                        const SyscallFilterSet *g;
4bff0a
+                        assert_se(g = syscall_filter_set_find(syscall));
4bff0a
+                        b = syscall_names_in_filter(s, whitelist, g);
4bff0a
+                } else {
4bff0a
+#if HAVE_SECCOMP
4bff0a
+                        int id;
4bff0a
+
4bff0a
+                        /* Let's see if the system call actually exists on this platform, before complaining */
4bff0a
+                        id = seccomp_syscall_resolve_name(syscall);
4bff0a
+                        if (id < 0)
4bff0a
+                                continue;
4bff0a
+#endif
4bff0a
+
4bff0a
+                        b = set_contains(s, syscall);
4bff0a
+                }
4bff0a
+
4bff0a
+                if (whitelist == b) {
4bff0a
+                        log_debug("Offending syscall filter item: %s", syscall);
4bff0a
+                        return true; /* bad! */
4bff0a
+                }
4bff0a
+        }
4bff0a
+
4bff0a
+        return false;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_system_call_filter(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        const SyscallFilterSet *f;
4bff0a
+        char *d = NULL;
4bff0a
+        uint64_t b;
4bff0a
+
4bff0a
+        assert(a);
4bff0a
+        assert(info);
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        assert(a->parameter < _SYSCALL_FILTER_SET_MAX);
4bff0a
+        f = syscall_filter_sets + a->parameter;
4bff0a
+
4bff0a
+        if (!info->system_call_filter_whitelist && set_isempty(info->system_call_filter)) {
4bff0a
+                d = strdup("Service does not filter system calls");
4bff0a
+                b = 10;
4bff0a
+        } else {
4bff0a
+                bool bad;
4bff0a
+
4bff0a
+                log_debug("Analyzing system call filter, checking against: %s", f->name);
4bff0a
+                bad = syscall_names_in_filter(info->system_call_filter, info->system_call_filter_whitelist, f);
4bff0a
+                log_debug("Result: %s", bad ? "bad" : "good");
4bff0a
+
4bff0a
+                if (info->system_call_filter_whitelist) {
4bff0a
+                        if (bad) {
4bff0a
+                                (void) asprintf(&d, "System call whitelist defined for service, and %s is included", f->name);
4bff0a
+                                b = 9;
4bff0a
+                        } else {
4bff0a
+                                (void) asprintf(&d, "System call whitelist defined for service, and %s is not included", f->name);
4bff0a
+                                b = 0;
4bff0a
+                        }
4bff0a
+                } else {
4bff0a
+                        if (bad) {
4bff0a
+                                (void) asprintf(&d, "System call blacklist defined for service, and %s is not included", f->name);
4bff0a
+                                b = 10;
4bff0a
+                        } else {
4bff0a
+                                (void) asprintf(&d, "System call blacklist defined for service, and %s is included", f->name);
4bff0a
+                                b = 5;
4bff0a
+                        }
4bff0a
+                }
4bff0a
+        }
4bff0a
+
4bff0a
+        if (!d)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = b;
4bff0a
+        *ret_description = d;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_ip_address_allow(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        char *d = NULL;
4bff0a
+        uint64_t b;
4bff0a
+
4bff0a
+        assert(info);
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (!info->ip_address_deny_all) {
4bff0a
+                d = strdup("Service does not define an IP address whitelist");
4bff0a
+                b = 10;
4bff0a
+        } else if (info->ip_address_allow_other) {
4bff0a
+                d = strdup("Service defines IP address whitelist with non-localhost entries");
4bff0a
+                b = 5;
4bff0a
+        } else if (info->ip_address_allow_localhost) {
4bff0a
+                d = strdup("Service defines IP address whitelits with only localhost entries");
4bff0a
+                b = 2;
4bff0a
+        } else {
4bff0a
+                d = strdup("Service blocks all IP address ranges");
4bff0a
+                b = 0;
4bff0a
+        }
4bff0a
+
4bff0a
+        if (!d)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = b;
4bff0a
+        *ret_description = d;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_device_allow(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        char *d = NULL;
4bff0a
+        uint64_t b;
4bff0a
+
4bff0a
+        assert(info);
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        if (STRPTR_IN_SET(info->device_policy, "strict", "closed")) {
4bff0a
+
4bff0a
+                if (info->device_allow_non_empty) {
4bff0a
+                        d = strdup("Service has a device ACL with some special devices");
4bff0a
+                        b = 5;
4bff0a
+                } else {
4bff0a
+                        d = strdup("Service has a minimal device ACL");
4bff0a
+                        b = 0;
4bff0a
+                }
4bff0a
+        } else {
4bff0a
+                d = strdup("Service has no device ACL");
4bff0a
+                b = 10;
4bff0a
+        }
4bff0a
+
4bff0a
+        if (!d)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        *ret_badness = b;
4bff0a
+        *ret_description = d;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int assess_ambient_capabilities(
4bff0a
+                const struct security_assessor *a,
4bff0a
+                const struct security_info *info,
4bff0a
+                const void *data,
4bff0a
+                uint64_t *ret_badness,
4bff0a
+                char **ret_description) {
4bff0a
+
4bff0a
+        assert(ret_badness);
4bff0a
+        assert(ret_description);
4bff0a
+
4bff0a
+        *ret_badness = info->ambient_capabilities != 0;
4bff0a
+        *ret_description = NULL;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static const struct security_assessor security_assessor_table[] = {
4bff0a
+        {
4bff0a
+                .id = "User=/DynamicUser=",
4bff0a
+                .description_bad = "Service runs as root user",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#User=",
4bff0a
+                .weight = 2000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_user,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SupplementaryGroups=",
4bff0a
+                .description_good = "Service has no supplementary groups",
4bff0a
+                .description_bad = "Service runs with supplementary groups",
4bff0a
+                .description_na = "Service runs as root, option does not matter",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SupplementaryGroups=",
4bff0a
+                .weight = 200,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_supplementary_groups,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "PrivateDevices=",
4bff0a
+                .description_good = "Service has no access to hardware devices",
4bff0a
+                .description_bad = "Service potentially has access to hardware devices",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateDevices=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, private_devices),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "PrivateMounts=",
4bff0a
+                .description_good = "Service cannot install system mounts",
4bff0a
+                .description_bad = "Service may install system mounts",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateMounts=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, private_mounts),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "PrivateNetwork=",
4bff0a
+                .description_good = "Service has no access to the host's network",
4bff0a
+                .description_bad = "Service has access to the host's network",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateNetwork=",
4bff0a
+                .weight = 2500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, private_network),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "PrivateTmp=",
4bff0a
+                .description_good = "Service has no access to other software's temporary files",
4bff0a
+                .description_bad = "Service has access to other software's temporary files",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateTmp=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, private_tmp),
4bff0a
+                .default_dependencies_only = true,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "PrivateUsers=",
4bff0a
+                .description_good = "Service does not have access to other users",
4bff0a
+                .description_bad = "Service has access to other users",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateUsers=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, private_users),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "ProtectControlGroups=",
4bff0a
+                .description_good = "Service cannot modify the control group file system",
4bff0a
+                .description_bad = "Service may modify to the control group file system",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, protect_control_groups),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "ProtectKernelModules=",
4bff0a
+                .description_good = "Service cannot load or read kernel modules",
4bff0a
+                .description_bad = "Service may load or read kernel modules",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelModules=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, protect_kernel_modules),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "ProtectKernelTunables=",
4bff0a
+                .description_good = "Service cannot alter kernel tunables (/proc/sys, …)",
4bff0a
+                .description_bad = "Service may alter kernel tunables",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelTunables=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, protect_kernel_tunables),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "ProtectHome=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_protect_home,
4bff0a
+                .default_dependencies_only = true,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "ProtectSystem=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_protect_system,
4bff0a
+                .default_dependencies_only = true,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RootDirectory=/RootImage=",
4bff0a
+                .description_good = "Service has its own root directory/image",
4bff0a
+                .description_bad = "Service runs within the host's root directory",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RootDirectory=",
4bff0a
+                .weight = 200,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_root_directory,
4bff0a
+                .default_dependencies_only = true,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "LockPersonality=",
4bff0a
+                .description_good = "Service cannot change ABI personality",
4bff0a
+                .description_bad = "Service may change ABI personality",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LockPersonality=",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, lock_personality),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "MemoryDenyWriteExecute=",
4bff0a
+                .description_good = "Service cannot create writable executable memory mappings",
4bff0a
+                .description_bad = "Service may create writable executable memory mappings",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#MemoryDenyWriteExecute=",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, memory_deny_write_execute),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "NoNewPrivileges=",
4bff0a
+                .description_good = "Service processes cannot acquire new privileges",
4bff0a
+                .description_bad = "Service processes may acquire new privileges",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NoNewPrivileges=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, no_new_privileges),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN",
4bff0a
+                .description_good = "Service has no administrator privileges",
4bff0a
+                .description_bad = "Service has administrator privileges",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = UINT64_C(1) << CAP_SYS_ADMIN,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
4bff0a
+                .description_good = "Service cannot change UID/GID identities/capabilities",
4bff0a
+                .description_bad = "Service may change UID/GID identities/capabilities",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SETUID)|
4bff0a
+                             (UINT64_C(1) << CAP_SETGID)|
4bff0a
+                             (UINT64_C(1) << CAP_SETPCAP),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_PTRACE",
4bff0a
+                .description_good = "Service has no ptrace() debugging abilities",
4bff0a
+                .description_bad = "Service has ptrace() debugging abilities",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_PTRACE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_TIME",
4bff0a
+                .description_good = "Service processes cannot change the system clock",
4bff0a
+                .description_bad = "Service processes may change the system clock",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = UINT64_C(1) << CAP_SYS_TIME,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_NET_ADMIN",
4bff0a
+                .description_good = "Service has no network configuration privileges",
4bff0a
+                .description_bad = "Service has network configuration privileges",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_NET_ADMIN),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_RAWIO",
4bff0a
+                .description_good = "Service has no raw I/O access",
4bff0a
+                .description_bad = "Service has raw I/O access",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_RAWIO),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_MODULE",
4bff0a
+                .description_good = "Service cannot load kernel modules",
4bff0a
+                .description_bad = "Service may load kernel modules",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_MODULE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_AUDIT_*",
4bff0a
+                .description_good = "Service has no audit subsystem access",
4bff0a
+                .description_bad = "Service has audit subsystem access",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_AUDIT_CONTROL) |
4bff0a
+                             (UINT64_C(1) << CAP_AUDIT_READ) |
4bff0a
+                             (UINT64_C(1) << CAP_AUDIT_WRITE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYSLOG",
4bff0a
+                .description_good = "Service has no access to kernel logging",
4bff0a
+                .description_bad = "Service has access to kernel logging",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYSLOG),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)",
4bff0a
+                .description_good = "Service has no privileges to change resource use parameters",
4bff0a
+                .description_bad = "Service has privileges to change resource use parameters",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_NICE) |
4bff0a
+                             (UINT64_C(1) << CAP_SYS_RESOURCE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_MKNOD",
4bff0a
+                .description_good = "Service cannot create device nodes",
4bff0a
+                .description_bad = "Service may create device nodes",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_MKNOD),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)",
4bff0a
+                .description_good = "Service cannot change file ownership/access mode/capabilities",
4bff0a
+                .description_bad = "Service may change file ownership/access mode/capabilities unrestricted",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_CHOWN) |
4bff0a
+                             (UINT64_C(1) << CAP_FSETID) |
4bff0a
+                             (UINT64_C(1) << CAP_SETFCAP),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)",
4bff0a
+                .description_good = "Service cannot override UNIX file/IPC permission checks",
4bff0a
+                .description_bad = "Service may override UNIX file/IPC permission checks",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_DAC_OVERRIDE) |
4bff0a
+                             (UINT64_C(1) << CAP_DAC_READ_SEARCH) |
4bff0a
+                             (UINT64_C(1) << CAP_FOWNER) |
4bff0a
+                             (UINT64_C(1) << CAP_IPC_OWNER),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_KILL",
4bff0a
+                .description_good = "Service cannot send UNIX signals to arbitrary processes",
4bff0a
+                .description_bad = "Service may send UNIX signals to arbitrary processes",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_KILL),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)",
4bff0a
+                .description_good = "Service has no elevated networking privileges",
4bff0a
+                .description_bad = "Service has elevated networking privileges",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_NET_BIND_SERVICE) |
4bff0a
+                             (UINT64_C(1) << CAP_NET_BROADCAST) |
4bff0a
+                             (UINT64_C(1) << CAP_NET_RAW),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_BOOT",
4bff0a
+                .description_good = "Service cannot issue reboot()",
4bff0a
+                .description_bad = "Service may issue reboot()",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_BOOT),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_MAC_*",
4bff0a
+                .description_good = "Service cannot adjust SMACK MAC",
4bff0a
+                .description_bad = "Service may adjust SMACK MAC",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_MAC_ADMIN)|
4bff0a
+                             (UINT64_C(1) << CAP_MAC_OVERRIDE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE",
4bff0a
+                .description_good = "Service cannot mark files immutable",
4bff0a
+                .description_bad = "Service may mark files immutable",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 75,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_LINUX_IMMUTABLE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_IPC_LOCK",
4bff0a
+                .description_good = "Service cannot lock memory into RAM",
4bff0a
+                .description_bad = "Service may lock memory into RAM",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 50,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_IPC_LOCK),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_CHROOT",
4bff0a
+                .description_good = "Service cannot issue chroot()",
4bff0a
+                .description_bad = "Service may issue chroot()",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 50,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_CHROOT),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_BLOCK_SUSPEND",
4bff0a
+                .description_good = "Service cannot establish wake locks",
4bff0a
+                .description_bad = "Service may establish wake locks",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 25,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_BLOCK_SUSPEND),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_WAKE_ALARM",
4bff0a
+                .description_good = "Service cannot program timers that wake up the system",
4bff0a
+                .description_bad = "Service may program timers that wake up the system",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 25,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_WAKE_ALARM),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_LEASE",
4bff0a
+                .description_good = "Service cannot create file leases",
4bff0a
+                .description_bad = "Service may create file leases",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 25,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_LEASE),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG",
4bff0a
+                .description_good = "Service cannot issue vhangup()",
4bff0a
+                .description_bad = "Service may issue vhangup()",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 25,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_TTY_CONFIG),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "CapabilityBoundingSet=~CAP_SYS_PACCT",
4bff0a
+                .description_good = "Service cannot use acct()",
4bff0a
+                .description_bad = "Service may use acct()",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
4bff0a
+                .weight = 25,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_capability_bounding_set,
4bff0a
+                .parameter = (UINT64_C(1) << CAP_SYS_PACCT),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "UMask=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#UMask=",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_umask,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "KeyringMode=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#KeyringMode=",
4bff0a
+                .description_good = "Service doesn't share key material with other services",
4bff0a
+                .description_bad = "Service shares key material with other service",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_keyring_mode,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "NotifyAccess=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NotifyAccess=",
4bff0a
+                .description_good = "Service child processes cannot alter service state",
4bff0a
+                .description_bad = "Service child processes may alter service state",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_notify_access,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RemoveIPC=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RemoveIPC=",
4bff0a
+                .description_good = "Service user cannot leave SysV IPC objects around",
4bff0a
+                .description_bad = "Service user may leave SysV IPC objects around",
4bff0a
+                .description_na = "Service runs as root, option does not apply",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_remove_ipc,
4bff0a
+                .offset = offsetof(struct security_info, remove_ipc),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "Delegate=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Delegate=",
4bff0a
+                .description_good = "Service does not maintain its own delegated control group subtree",
4bff0a
+                .description_bad = "Service maintains its own delegated control group subtree",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, delegate),
4bff0a
+                .parameter = true, /* invert! */
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictRealtime=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictRealtime=",
4bff0a
+                .description_good = "Service realtime scheduling access is restricted",
4bff0a
+                .description_bad = "Service may acquire realtime scheduling",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, restrict_realtime),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWUSER",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create user namespaces",
4bff0a
+                .description_bad = "Service may create user namespaces",
4bff0a
+                .weight = 1500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWUSER,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWNS",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create file system namespaces",
4bff0a
+                .description_bad = "Service may create file system namespaces",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWNS,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWIPC",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create IPC namespaces",
4bff0a
+                .description_bad = "Service may create IPC namespaces",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWIPC,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWPID",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create process namespaces",
4bff0a
+                .description_bad = "Service may create process namespaces",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWPID,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWCGROUP",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create cgroup namespaces",
4bff0a
+                .description_bad = "Service may create cgroup namespaces",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWCGROUP,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWNET",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create network namespaces",
4bff0a
+                .description_bad = "Service may create network namespaces",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWNET,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictNamespaces=~CLONE_NEWUTS",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
4bff0a
+                .description_good = "Service cannot create hostname namespaces",
4bff0a
+                .description_bad = "Service may create hostname namespaces",
4bff0a
+                .weight = 100,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_restrict_namespaces,
4bff0a
+                .parameter = CLONE_NEWUTS,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictAddressFamilies=~AF_(INET|INET6)",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
4bff0a
+                .description_good = "Service cannot allocate Internet sockets",
4bff0a
+                .description_bad = "Service may allocate Internet sockets",
4bff0a
+                .weight = 1500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, restrict_address_family_inet),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictAddressFamilies=~AF_UNIX",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
4bff0a
+                .description_good = "Service cannot allocate local sockets",
4bff0a
+                .description_bad = "Service may allocate local sockets",
4bff0a
+                .weight = 25,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, restrict_address_family_unix),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictAddressFamilies=~AF_NETLINK",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
4bff0a
+                .description_good = "Service cannot allocate netlink sockets",
4bff0a
+                .description_bad = "Service may allocate netlink sockets",
4bff0a
+                .weight = 200,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, restrict_address_family_netlink),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictAddressFamilies=~AF_PACKET",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
4bff0a
+                .description_good = "Service cannot allocate packet sockets",
4bff0a
+                .description_bad = "Service may allocate packet sockets",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, restrict_address_family_packet),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "RestrictAddressFamilies=~…",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
4bff0a
+                .description_good = "Service cannot allocate exotic sockets",
4bff0a
+                .description_bad = "Service may allocate exotic sockets",
4bff0a
+                .weight = 1250,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_bool,
4bff0a
+                .offset = offsetof(struct security_info, restrict_address_family_other),
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallArchitectures=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_architectures,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@swap",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_SWAP,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@obsolete",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 250,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_OBSOLETE,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@clock",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_CLOCK,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@cpu-emulation",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 250,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_CPU_EMULATION,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@debug",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_DEBUG,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@mount",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_MOUNT,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@module",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_MODULE,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@raw-io",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_RAW_IO,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@reboot",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_REBOOT,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@privileged",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 700,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_PRIVILEGED,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "SystemCallFilter=~@resources",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
4bff0a
+                .weight = 700,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_system_call_filter,
4bff0a
+                .parameter = SYSCALL_FILTER_SET_RESOURCES,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "IPAddressDeny=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#IPAddressDeny=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_ip_address_allow,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "DeviceAllow=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DeviceAllow=",
4bff0a
+                .weight = 1000,
4bff0a
+                .range = 10,
4bff0a
+                .assess = assess_device_allow,
4bff0a
+        },
4bff0a
+        {
4bff0a
+                .id = "AmbientCapabilities=",
4bff0a
+                .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#AmbientCapabilities=",
4bff0a
+                .description_good = "Service process does not receive ambient capabilities",
4bff0a
+                .description_bad = "Service process receives ambient capabilities",
4bff0a
+                .weight = 500,
4bff0a
+                .range = 1,
4bff0a
+                .assess = assess_ambient_capabilities,
4bff0a
+        },
4bff0a
+};
4bff0a
+
4bff0a
+static int assess(const struct security_info *info, Table *overview_table, AnalyzeSecurityFlags flags) {
4bff0a
+        static const struct {
4bff0a
+                uint64_t exposure;
4bff0a
+                const char *name;
4bff0a
+                const char *color;
4bff0a
+                SpecialGlyph smiley;
4bff0a
+        } badness_table[] = {
4bff0a
+                { 100, "DANGEROUS", ANSI_HIGHLIGHT_RED,    DEPRESSED_SMILEY        },
4bff0a
+                { 90,  "UNSAFE",    ANSI_HIGHLIGHT_RED,    UNHAPPY_SMILEY          },
4bff0a
+                { 75,  "EXPOSED",   ANSI_HIGHLIGHT_YELLOW, SLIGHTLY_UNHAPPY_SMILEY },
4bff0a
+                { 50,  "MEDIUM",    NULL,                  NEUTRAL_SMILEY          },
4bff0a
+                { 10,  "OK",        ANSI_HIGHLIGHT_GREEN,  SLIGHTLY_HAPPY_SMILEY   },
4bff0a
+                { 1,   "SAFE",      ANSI_HIGHLIGHT_GREEN,  HAPPY_SMILEY            },
4bff0a
+                { 0,   "PERFECT",   ANSI_HIGHLIGHT_GREEN,  ECSTATIC_SMILEY         },
4bff0a
+        };
4bff0a
+
4bff0a
+        uint64_t badness_sum = 0, weight_sum = 0, exposure;
4bff0a
+        _cleanup_(table_unrefp) Table *details_table = NULL;
4bff0a
+        size_t i;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
4bff0a
+                details_table = table_new("", "NAME", "DESCRIPTION", "WEIGHT", "BADNESS", "RANGE", "EXPOSURE");
4bff0a
+                if (!details_table)
4bff0a
+                        return log_oom();
4bff0a
+
4bff0a
+                (void) table_set_sort(details_table, 3, 1, (size_t) -1);
4bff0a
+                (void) table_set_reverse(details_table, 3, true);
4bff0a
+
4bff0a
+                if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
4bff0a
+                        (void) table_set_display(details_table, 0, 1, 2, 6, (size_t) -1);
4bff0a
+        }
4bff0a
+
4bff0a
+        for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
4bff0a
+                const struct security_assessor *a = security_assessor_table + i;
4bff0a
+                _cleanup_free_ char *d = NULL;
4bff0a
+                uint64_t badness;
4bff0a
+                void *data;
4bff0a
+
4bff0a
+                data = (uint8_t*) info + a->offset;
4bff0a
+
4bff0a
+                if (a->default_dependencies_only && !info->default_dependencies) {
4bff0a
+                        badness = UINT64_MAX;
4bff0a
+                        d = strdup("Service runs in special boot phase, option does not apply");
4bff0a
+                        if (!d)
4bff0a
+                                return log_oom();
4bff0a
+                } else {
4bff0a
+                        r = a->assess(a, info, data, &badness, &d);
4bff0a
+                        if (r < 0)
4bff0a
+                                return r;
4bff0a
+                }
4bff0a
+
4bff0a
+                assert(a->range > 0);
4bff0a
+
4bff0a
+                if (badness != UINT64_MAX) {
4bff0a
+                        assert(badness <= a->range);
4bff0a
+
4bff0a
+                        badness_sum += DIV_ROUND_UP(badness * a->weight, a->range);
4bff0a
+                        weight_sum += a->weight;
4bff0a
+                }
4bff0a
+
4bff0a
+                if (details_table) {
4bff0a
+                        const char *checkmark, *description, *color = NULL;
4bff0a
+                        TableCell *cell;
4bff0a
+
4bff0a
+                        if (badness == UINT64_MAX) {
4bff0a
+                                checkmark = " ";
4bff0a
+                                description = a->description_na;
4bff0a
+                                color = NULL;
4bff0a
+                        } else if (badness == a->range) {
4bff0a
+                                checkmark = special_glyph(CROSS_MARK);
4bff0a
+                                description = a->description_bad;
4bff0a
+                                color = ansi_highlight_red();
4bff0a
+                        } else if (badness == 0) {
4bff0a
+                                checkmark = special_glyph(CHECK_MARK);
4bff0a
+                                description = a->description_good;
4bff0a
+                                color = ansi_highlight_green();
4bff0a
+                        } else {
4bff0a
+                                checkmark = special_glyph(CROSS_MARK);
4bff0a
+                                description = NULL;
4bff0a
+                                color = ansi_highlight_red();
4bff0a
+                        }
4bff0a
+
4bff0a
+                        if (d)
4bff0a
+                                description = d;
4bff0a
+
4bff0a
+                        r = table_add_cell_full(details_table, &cell, TABLE_STRING, checkmark, 1, 1, 0, 0, 0);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                        if (color)
4bff0a
+                                (void) table_set_color(details_table, cell, color);
4bff0a
+
4bff0a
+                        r = table_add_cell(details_table, &cell, TABLE_STRING, a->id);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                        if (a->url)
4bff0a
+                                (void) table_set_url(details_table, cell, a->url);
4bff0a
+
4bff0a
+                        r = table_add_cell(details_table, NULL, TABLE_STRING, description);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+
4bff0a
+                        r = table_add_cell(details_table, &cell, TABLE_UINT64, &a->weight);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                        (void) table_set_align_percent(details_table, cell, 100);
4bff0a
+
4bff0a
+                        r = table_add_cell(details_table, &cell, TABLE_UINT64, &badness);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                        (void) table_set_align_percent(details_table, cell, 100);
4bff0a
+
4bff0a
+                        r = table_add_cell(details_table, &cell, TABLE_UINT64, &a->range);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                        (void) table_set_align_percent(details_table, cell, 100);
4bff0a
+
4bff0a
+                        r = table_add_cell(details_table, &cell, TABLE_EMPTY, NULL);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                        (void) table_set_align_percent(details_table, cell, 100);
4bff0a
+                }
4bff0a
+        }
4bff0a
+
4bff0a
+        if (details_table) {
4bff0a
+                size_t row;
4bff0a
+
4bff0a
+                for (row = 1; row < table_get_rows(details_table); row++) {
4bff0a
+                        char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
4bff0a
+                        const uint64_t *weight, *badness, *range;
4bff0a
+                        TableCell *cell;
4bff0a
+                        uint64_t x;
4bff0a
+
4bff0a
+                        assert_se(weight = table_get_at(details_table, row, 3));
4bff0a
+                        assert_se(badness = table_get_at(details_table, row, 4));
4bff0a
+                        assert_se(range = table_get_at(details_table, row, 5));
4bff0a
+
4bff0a
+                        if (*badness == UINT64_MAX || *badness == 0)
4bff0a
+                                continue;
4bff0a
+
4bff0a
+                        assert_se(cell = table_get_cell(details_table, row, 6));
4bff0a
+
4bff0a
+                        x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
4bff0a
+                        xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
4bff0a
+
4bff0a
+                        r = table_update(details_table, cell, TABLE_STRING, buf);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to update cell in table: %m");
4bff0a
+                }
4bff0a
+
4bff0a
+                r = table_print(details_table, stdout);
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to output table: %m");
4bff0a
+        }
4bff0a
+
4bff0a
+        exposure = DIV_ROUND_UP(badness_sum * 100U, weight_sum);
4bff0a
+
4bff0a
+        for (i = 0; i < ELEMENTSOF(badness_table); i++)
4bff0a
+                if (exposure >= badness_table[i].exposure)
4bff0a
+                        break;
4bff0a
+
4bff0a
+        assert(i < ELEMENTSOF(badness_table));
4bff0a
+
4bff0a
+        if (details_table) {
4bff0a
+                _cleanup_free_ char *clickable = NULL;
4bff0a
+                const char *name;
4bff0a
+
4bff0a
+                /* If we shall output the details table, also print the brief summary underneath */
4bff0a
+
4bff0a
+                if (info->fragment_path) {
4bff0a
+                        r = terminal_urlify_path(info->fragment_path, info->id, &clickable);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_oom();
4bff0a
+
4bff0a
+                        name = clickable;
4bff0a
+                } else
4bff0a
+                        name = info->id;
4bff0a
+
4bff0a
+                printf("\n%s %sOverall exposure level for %s%s: %s%" PRIu64 ".%" PRIu64 " %s%s %s\n",
4bff0a
+                       special_glyph(ARROW),
4bff0a
+                       ansi_highlight(),
4bff0a
+                       name,
4bff0a
+                       ansi_normal(),
4bff0a
+                       colors_enabled() ? strempty(badness_table[i].color) : "",
4bff0a
+                       exposure / 10, exposure % 10,
4bff0a
+                       badness_table[i].name,
4bff0a
+                       ansi_normal(),
4bff0a
+                       special_glyph(badness_table[i].smiley));
4bff0a
+        }
4bff0a
+
4bff0a
+        fflush(stdout);
4bff0a
+
4bff0a
+        if (overview_table) {
4bff0a
+                char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
4bff0a
+                TableCell *cell;
4bff0a
+
4bff0a
+                r = table_add_cell(overview_table, &cell, TABLE_STRING, info->id);
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                if (info->fragment_path) {
4bff0a
+                        _cleanup_free_ char *url = NULL;
4bff0a
+
4bff0a
+                        r = file_url_from_path(info->fragment_path, &url;;
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to generate URL from path: %m");
4bff0a
+
4bff0a
+                        (void) table_set_url(overview_table, cell, url);
4bff0a
+                }
4bff0a
+
4bff0a
+                xsprintf(buf, "%" PRIu64 ".%" PRIu64, exposure / 10, exposure % 10);
4bff0a
+                r = table_add_cell(overview_table, &cell, TABLE_STRING, buf);
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                (void) table_set_align_percent(overview_table, cell, 100);
4bff0a
+
4bff0a
+                r = table_add_cell(overview_table, &cell, TABLE_STRING, badness_table[i].name);
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+                (void) table_set_color(overview_table, cell, strempty(badness_table[i].color));
4bff0a
+
4bff0a
+                r = table_add_cell(overview_table, NULL, TABLE_STRING, special_glyph(badness_table[i].smiley));
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to add cell to table: %m");
4bff0a
+        }
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int property_read_restrict_address_families(
4bff0a
+                sd_bus *bus,
4bff0a
+                const char *member,
4bff0a
+                sd_bus_message *m,
4bff0a
+                sd_bus_error *error,
4bff0a
+                void *userdata) {
4bff0a
+
4bff0a
+        struct security_info *info = userdata;
4bff0a
+        int whitelist, r;
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+        assert(member);
4bff0a
+        assert(m);
4bff0a
+
4bff0a
+        r = sd_bus_message_enter_container(m, 'r', "bas");
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        r = sd_bus_message_read(m, "b", &whitelist);
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        info->restrict_address_family_inet =
4bff0a
+                info->restrict_address_family_unix =
4bff0a
+                info->restrict_address_family_netlink =
4bff0a
+                info->restrict_address_family_packet =
4bff0a
+                info->restrict_address_family_other = whitelist;
4bff0a
+
4bff0a
+        r = sd_bus_message_enter_container(m, 'a', "s");
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        for (;;) {
4bff0a
+                const char *name;
4bff0a
+
4bff0a
+                r = sd_bus_message_read(m, "s", &name);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+                if (r == 0)
4bff0a
+                        break;
4bff0a
+
4bff0a
+                if (STR_IN_SET(name, "AF_INET", "AF_INET6"))
4bff0a
+                        info->restrict_address_family_inet = !whitelist;
4bff0a
+                else if (streq(name, "AF_UNIX"))
4bff0a
+                        info->restrict_address_family_unix = !whitelist;
4bff0a
+                else if (streq(name, "AF_NETLINK"))
4bff0a
+                        info->restrict_address_family_netlink = !whitelist;
4bff0a
+                else if (streq(name, "AF_PACKET"))
4bff0a
+                        info->restrict_address_family_packet = !whitelist;
4bff0a
+                else
4bff0a
+                        info->restrict_address_family_other = !whitelist;
4bff0a
+        }
4bff0a
+
4bff0a
+        r = sd_bus_message_exit_container(m);
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        return sd_bus_message_exit_container(m);
4bff0a
+}
4bff0a
+
4bff0a
+static int property_read_system_call_filter(
4bff0a
+                sd_bus *bus,
4bff0a
+                const char *member,
4bff0a
+                sd_bus_message *m,
4bff0a
+                sd_bus_error *error,
4bff0a
+                void *userdata) {
4bff0a
+
4bff0a
+        struct security_info *info = userdata;
4bff0a
+        int whitelist, r;
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+        assert(member);
4bff0a
+        assert(m);
4bff0a
+
4bff0a
+        r = sd_bus_message_enter_container(m, 'r', "bas");
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        r = sd_bus_message_read(m, "b", &whitelist);
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        info->system_call_filter_whitelist = whitelist;
4bff0a
+
4bff0a
+        r = sd_bus_message_enter_container(m, 'a', "s");
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        for (;;) {
4bff0a
+                const char *name;
4bff0a
+
4bff0a
+                r = sd_bus_message_read(m, "s", &name);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+                if (r == 0)
4bff0a
+                        break;
4bff0a
+
4bff0a
+                r = set_ensure_allocated(&info->system_call_filter, &string_hash_ops);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+
4bff0a
+                r = set_put_strdup(info->system_call_filter, name);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+        }
4bff0a
+
4bff0a
+        r = sd_bus_message_exit_container(m);
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        return sd_bus_message_exit_container(m);
4bff0a
+}
4bff0a
+
4bff0a
+static int property_read_ip_address_allow(
4bff0a
+                sd_bus *bus,
4bff0a
+                const char *member,
4bff0a
+                sd_bus_message *m,
4bff0a
+                sd_bus_error *error,
4bff0a
+                void *userdata) {
4bff0a
+
4bff0a
+        struct security_info *info = userdata;
4bff0a
+        bool deny_ipv4 = false, deny_ipv6 = false;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+        assert(member);
4bff0a
+        assert(m);
4bff0a
+
4bff0a
+        r = sd_bus_message_enter_container(m, 'a', "(iayu)");
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        for (;;) {
4bff0a
+                const void *data;
4bff0a
+                size_t size;
4bff0a
+                int32_t family;
4bff0a
+                uint32_t prefixlen;
4bff0a
+
4bff0a
+                r = sd_bus_message_enter_container(m, 'r', "iayu");
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+                if (r == 0)
4bff0a
+                        break;
4bff0a
+
4bff0a
+                r = sd_bus_message_read(m, "i", &family);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+
4bff0a
+                r = sd_bus_message_read_array(m, 'y', &data, &size);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+
4bff0a
+                r = sd_bus_message_read(m, "u", &prefixlen);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+
4bff0a
+                r = sd_bus_message_exit_container(m);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+
4bff0a
+                if (streq(member, "IPAddressAllow")) {
4bff0a
+                        union in_addr_union u;
4bff0a
+
4bff0a
+                        if (family == AF_INET && size == 4 && prefixlen == 8)
4bff0a
+                                memcpy(&u.in, data, size);
4bff0a
+                        else if (family == AF_INET6 && size == 16 && prefixlen == 128)
4bff0a
+                                memcpy(&u.in6, data, size);
4bff0a
+                        else {
4bff0a
+                                info->ip_address_allow_other = true;
4bff0a
+                                continue;
4bff0a
+                        }
4bff0a
+
4bff0a
+                        if (in_addr_is_localhost(family, &u))
4bff0a
+                                info->ip_address_allow_localhost = true;
4bff0a
+                        else
4bff0a
+                                info->ip_address_allow_other = true;
4bff0a
+                } else {
4bff0a
+                        assert(streq(member, "IPAddressDeny"));
4bff0a
+
4bff0a
+                        if (family == AF_INET && size == 4 && prefixlen == 0)
4bff0a
+                                deny_ipv4 = true;
4bff0a
+                        else if (family == AF_INET6 && size == 16 && prefixlen == 0)
4bff0a
+                                deny_ipv6 = true;
4bff0a
+                }
4bff0a
+        }
4bff0a
+
4bff0a
+        info->ip_address_deny_all = deny_ipv4 && deny_ipv6;
4bff0a
+
4bff0a
+        return sd_bus_message_exit_container(m);
4bff0a
+}
4bff0a
+
4bff0a
+static int property_read_device_allow(
4bff0a
+                sd_bus *bus,
4bff0a
+                const char *member,
4bff0a
+                sd_bus_message *m,
4bff0a
+                sd_bus_error *error,
4bff0a
+                void *userdata) {
4bff0a
+
4bff0a
+        struct security_info *info = userdata;
4bff0a
+        size_t n = 0;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+        assert(member);
4bff0a
+        assert(m);
4bff0a
+
4bff0a
+        r = sd_bus_message_enter_container(m, 'a', "(ss)");
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        for (;;) {
4bff0a
+                const char *name, *policy;
4bff0a
+
4bff0a
+                r = sd_bus_message_read(m, "(ss)", &name, &policy);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+                if (r == 0)
4bff0a
+                        break;
4bff0a
+
4bff0a
+                n++;
4bff0a
+        }
4bff0a
+
4bff0a
+        info->device_allow_non_empty = n > 0;
4bff0a
+
4bff0a
+        return sd_bus_message_exit_container(m);
4bff0a
+}
4bff0a
+
4bff0a
+static int acquire_security_info(sd_bus *bus, const char *name, struct security_info *info, AnalyzeSecurityFlags flags) {
4bff0a
+
4bff0a
+        static const struct bus_properties_map security_map[] = {
4bff0a
+                { "AmbientCapabilities",     "t",       NULL,                                    offsetof(struct security_info, ambient_capabilities)      },
4bff0a
+                { "CapabilityBoundingSet",   "t",       NULL,                                    offsetof(struct security_info, capability_bounding_set)   },
4bff0a
+                { "DefaultDependencies",     "b",       NULL,                                    offsetof(struct security_info, default_dependencies)      },
4bff0a
+                { "Delegate",                "b",       NULL,                                    offsetof(struct security_info, delegate)                  },
4bff0a
+                { "DeviceAllow",             "a(ss)",   property_read_device_allow,              0                                                         },
4bff0a
+                { "DevicePolicy",            "s",       NULL,                                    offsetof(struct security_info, device_policy)             },
4bff0a
+                { "DynamicUser",             "b",       NULL,                                    offsetof(struct security_info, dynamic_user)              },
4bff0a
+                { "FragmentPath",            "s",       NULL,                                    offsetof(struct security_info, fragment_path)             },
4bff0a
+                { "IPAddressAllow",          "a(iayu)", property_read_ip_address_allow,          0                                                         },
4bff0a
+                { "IPAddressDeny",           "a(iayu)", property_read_ip_address_allow,          0                                                         },
4bff0a
+                { "Id",                      "s",       NULL,                                    offsetof(struct security_info, id)                        },
4bff0a
+                { "KeyringMode",             "s",       NULL,                                    offsetof(struct security_info, keyring_mode)              },
4bff0a
+                { "LoadState",               "s",       NULL,                                    offsetof(struct security_info, load_state)                },
4bff0a
+                { "LockPersonality",         "b",       NULL,                                    offsetof(struct security_info, lock_personality)          },
4bff0a
+                { "MemoryDenyWriteExecute",  "b",       NULL,                                    offsetof(struct security_info, memory_deny_write_execute) },
4bff0a
+                { "NoNewPrivileges",         "b",       NULL,                                    offsetof(struct security_info, no_new_privileges)         },
4bff0a
+                { "NotifyAccess",            "s",       NULL,                                    offsetof(struct security_info, notify_access)             },
4bff0a
+                { "PrivateDevices",          "b",       NULL,                                    offsetof(struct security_info, private_devices)           },
4bff0a
+                { "PrivateMounts",           "b",       NULL,                                    offsetof(struct security_info, private_mounts)            },
4bff0a
+                { "PrivateNetwork",          "b",       NULL,                                    offsetof(struct security_info, private_network)           },
4bff0a
+                { "PrivateTmp",              "b",       NULL,                                    offsetof(struct security_info, private_tmp)               },
4bff0a
+                { "PrivateUsers",            "b",       NULL,                                    offsetof(struct security_info, private_users)             },
4bff0a
+                { "PrivateUsers",            "b",       NULL,                                    offsetof(struct security_info, private_users)             },
4bff0a
+                { "ProtectControlGroups",    "b",       NULL,                                    offsetof(struct security_info, protect_control_groups)    },
4bff0a
+                { "ProtectHome",             "s",       NULL,                                    offsetof(struct security_info, protect_home)              },
4bff0a
+                { "ProtectKernelModules",    "b",       NULL,                                    offsetof(struct security_info, protect_kernel_modules)    },
4bff0a
+                { "ProtectKernelTunables",   "b",       NULL,                                    offsetof(struct security_info, protect_kernel_tunables)   },
4bff0a
+                { "ProtectSystem",           "s",       NULL,                                    offsetof(struct security_info, protect_system)            },
4bff0a
+                { "RemoveIPC",               "b",       NULL,                                    offsetof(struct security_info, remove_ipc)                },
4bff0a
+                { "RestrictAddressFamilies", "(bas)",   property_read_restrict_address_families, 0                                                         },
4bff0a
+                { "RestrictNamespaces",      "t",       NULL,                                    offsetof(struct security_info, restrict_namespaces)       },
4bff0a
+                { "RestrictRealtime",        "b",       NULL,                                    offsetof(struct security_info, restrict_realtime)         },
4bff0a
+                { "RootDirectory",           "s",       NULL,                                    offsetof(struct security_info, root_directory)            },
4bff0a
+                { "RootImage",               "s",       NULL,                                    offsetof(struct security_info, root_image)                },
4bff0a
+                { "SupplementaryGroups",     "as",      NULL,                                    offsetof(struct security_info, supplementary_groups)      },
4bff0a
+                { "SystemCallArchitectures", "as",      NULL,                                    offsetof(struct security_info, system_call_architectures) },
4bff0a
+                { "SystemCallFilter",        "(as)",    property_read_system_call_filter,        0                                                         },
4bff0a
+                { "Type",                    "s",       NULL,                                    offsetof(struct security_info, type)                      },
4bff0a
+                { "UMask",                   "u",       NULL,                                    offsetof(struct security_info, _umask)                    },
4bff0a
+                { "User",                    "s",       NULL,                                    offsetof(struct security_info, user)                      },
4bff0a
+                {}
4bff0a
+        };
4bff0a
+
4bff0a
+        _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
4bff0a
+        _cleanup_free_ char *path = NULL;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        /* Note: this mangles *info on failure! */
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+        assert(name);
4bff0a
+        assert(info);
4bff0a
+
4bff0a
+        path = unit_dbus_path_from_name(name);
4bff0a
+        if (!path)
4bff0a
+                return log_oom();
4bff0a
+
4bff0a
+        r = bus_map_all_properties(bus,
4bff0a
+                                   "org.freedesktop.systemd1",
4bff0a
+                                   path,
4bff0a
+                                   security_map,
4bff0a
+                                   BUS_MAP_STRDUP|BUS_MAP_BOOLEAN_AS_BOOL,
4bff0a
+                                   &error,
4bff0a
+                                   NULL,
4bff0a
+                                   info);
4bff0a
+        if (r < 0)
4bff0a
+                return log_error_errno(r, "Failed to get unit properties: %s", bus_error_message(&error, r));
4bff0a
+
4bff0a
+        if (!streq_ptr(info->load_state, "loaded")) {
4bff0a
+
4bff0a
+                if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LOADED))
4bff0a
+                        return -EMEDIUMTYPE;
4bff0a
+
4bff0a
+                if (streq_ptr(info->load_state, "not-found"))
4bff0a
+                        log_error("Unit %s not found, cannot analyze.", name);
4bff0a
+                else if (streq_ptr(info->load_state, "masked"))
4bff0a
+                        log_error("Unit %s is masked, cannot analyze.", name);
4bff0a
+                else
4bff0a
+                        log_error("Unit %s not loaded properly, cannot analyze.", name);
4bff0a
+
4bff0a
+                return -EINVAL;
4bff0a
+        }
4bff0a
+
4bff0a
+        if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LONG_RUNNING) && streq_ptr(info->type, "oneshot"))
4bff0a
+                return -EMEDIUMTYPE;
4bff0a
+
4bff0a
+        if (info->private_devices ||
4bff0a
+            info->private_tmp ||
4bff0a
+            info->protect_control_groups ||
4bff0a
+            info->protect_kernel_tunables ||
4bff0a
+            info->protect_kernel_modules ||
4bff0a
+            !streq_ptr(info->protect_home, "no") ||
4bff0a
+            !streq_ptr(info->protect_system, "no") ||
4bff0a
+            info->root_image)
4bff0a
+                info->private_mounts = true;
4bff0a
+
4bff0a
+        if (info->protect_kernel_modules)
4bff0a
+                info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYS_MODULE);
4bff0a
+
4bff0a
+        if (info->private_devices)
4bff0a
+                info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) |
4bff0a
+                                                   (UINT64_C(1) << CAP_SYS_RAWIO));
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+static int analyze_security_one(sd_bus *bus, const char *name, Table* overview_table, AnalyzeSecurityFlags flags) {
4bff0a
+        _cleanup_(security_info_free) struct security_info info = {
4bff0a
+                .default_dependencies = true,
4bff0a
+                .capability_bounding_set = UINT64_MAX,
4bff0a
+                .restrict_namespaces = UINT64_MAX,
4bff0a
+                ._umask = 0002,
4bff0a
+        };
4bff0a
+        int r;
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+        assert(name);
4bff0a
+
4bff0a
+        r = acquire_security_info(bus, name, &info, flags);
4bff0a
+        if (r == -EMEDIUMTYPE) /* Ignore this one because not loaded or Type is oneshot */
4bff0a
+                return 0;
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        r = assess(&info, overview_table, flags);
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
+
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) {
4bff0a
+        _cleanup_(table_unrefp) Table *overview_table = NULL;
4bff0a
+        int ret = 0, r;
4bff0a
+
4bff0a
+        assert(bus);
4bff0a
+
4bff0a
+        if (strv_length(units) != 1) {
4bff0a
+                overview_table = table_new("UNIT", "EXPOSURE", "PREDICATE", "HAPPY");
4bff0a
+                if (!overview_table)
4bff0a
+                        return log_oom();
4bff0a
+        }
4bff0a
+
4bff0a
+        if (strv_isempty(units)) {
4bff0a
+                _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
4bff0a
+                _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
4bff0a
+                _cleanup_strv_free_ char **list = NULL;
4bff0a
+                size_t allocated = 0, n = 0;
4bff0a
+                char **i;
4bff0a
+
4bff0a
+                r = sd_bus_call_method(
4bff0a
+                                bus,
4bff0a
+                                "org.freedesktop.systemd1",
4bff0a
+                                "/org/freedesktop/systemd1",
4bff0a
+                                "org.freedesktop.systemd1.Manager",
4bff0a
+                                "ListUnits",
4bff0a
+                                &error, &reply,
4bff0a
+                                NULL);
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
4bff0a
+
4bff0a
+                r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
4bff0a
+                if (r < 0)
4bff0a
+                        return bus_log_parse_error(r);
4bff0a
+
4bff0a
+                for (;;) {
4bff0a
+                        UnitInfo info;
4bff0a
+                        char *copy = NULL;
4bff0a
+
4bff0a
+                        r = bus_parse_unit_info(reply, &info;;
4bff0a
+                        if (r < 0)
4bff0a
+                                return bus_log_parse_error(r);
4bff0a
+                        if (r == 0)
4bff0a
+                                break;
4bff0a
+
4bff0a
+                        if (!endswith(info.id, ".service"))
4bff0a
+                                continue;
4bff0a
+
4bff0a
+                        if (!GREEDY_REALLOC(list, allocated, n+2))
4bff0a
+                                return log_oom();
4bff0a
+
4bff0a
+                        copy = strdup(info.id);
4bff0a
+                        if (!copy)
4bff0a
+                                return log_oom();
4bff0a
+
4bff0a
+                        list[n++] = copy;
4bff0a
+                        list[n] = NULL;
4bff0a
+                }
4bff0a
+
4bff0a
+                strv_sort(list);
4bff0a
+
4bff0a
+                flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
4bff0a
+
4bff0a
+                STRV_FOREACH(i, list) {
4bff0a
+                        r = analyze_security_one(bus, *i, overview_table, flags);
4bff0a
+                        if (r < 0 && ret >= 0)
4bff0a
+                                ret = r;
4bff0a
+                }
4bff0a
+
4bff0a
+        } else {
4bff0a
+                char **i;
4bff0a
+
4bff0a
+                STRV_FOREACH(i, units) {
4bff0a
+                        _cleanup_free_ char *mangled = NULL, *instance = NULL;
4bff0a
+                        const char *name;
4bff0a
+
4bff0a
+                        if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT) && i != units) {
4bff0a
+                                putc('\n', stdout);
4bff0a
+                                fflush(stdout);
4bff0a
+                        }
4bff0a
+
4bff0a
+                        r = unit_name_mangle_with_suffix(*i, 0, ".service", &mangled);
4bff0a
+                        if (r < 0)
4bff0a
+                                return log_error_errno(r, "Failed to mangle unit name '%s': %m", *i);
4bff0a
+
4bff0a
+                        if (!endswith(mangled, ".service")) {
4bff0a
+                                log_error("Unit %s is not a service unit, refusing.", *i);
4bff0a
+                                return -EINVAL;
4bff0a
+                        }
4bff0a
+
4bff0a
+                        if (unit_name_is_valid(mangled, UNIT_NAME_TEMPLATE)) {
4bff0a
+                                r = unit_name_replace_instance(mangled, "test-instance", &instance);
4bff0a
+                                if (r < 0)
4bff0a
+                                        return log_oom();
4bff0a
+
4bff0a
+                                name = instance;
4bff0a
+                        } else
4bff0a
+                                name = mangled;
4bff0a
+
4bff0a
+                        r = analyze_security_one(bus, name, overview_table, flags);
4bff0a
+                        if (r < 0 && ret >= 0)
4bff0a
+                                ret = r;
4bff0a
+                }
4bff0a
+        }
4bff0a
+
4bff0a
+        if (overview_table) {
4bff0a
+                if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
4bff0a
+                        putc('\n', stdout);
4bff0a
+                        fflush(stdout);
4bff0a
+                }
4bff0a
+
4bff0a
+                r = table_print(overview_table, stdout);
4bff0a
+                if (r < 0)
4bff0a
+                        return log_error_errno(r, "Failed to output table: %m");
4bff0a
+        }
4bff0a
+
4bff0a
+        return ret;
4bff0a
+}
4bff0a
diff --git a/src/analyze/analyze-security.h b/src/analyze/analyze-security.h
4bff0a
new file mode 100644
4bff0a
index 0000000000..c00ae7c80a
4bff0a
--- /dev/null
4bff0a
+++ b/src/analyze/analyze-security.h
4bff0a
@@ -0,0 +1,12 @@
4bff0a
+/* SPDX-License-Identifier: LGPL-2.1+ */
4bff0a
+#pragma once
4bff0a
+
4bff0a
+#include "sd-bus.h"
4bff0a
+
4bff0a
+typedef enum AnalyzeSecurityFlags {
4bff0a
+        ANALYZE_SECURITY_SHORT             = 1 << 0,
4bff0a
+        ANALYZE_SECURITY_ONLY_LOADED       = 1 << 1,
4bff0a
+        ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2,
4bff0a
+} AnalyzeSecurityFlags;
4bff0a
+
4bff0a
+int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags);
4bff0a
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
4bff0a
index dc7d2ab0f6..c30a133fc0 100644
4bff0a
--- a/src/analyze/analyze.c
4bff0a
+++ b/src/analyze/analyze.c
4bff0a
@@ -11,6 +11,7 @@
4bff0a
 #include "sd-bus.h"
4bff0a
 
4bff0a
 #include "alloc-util.h"
4bff0a
+#include "analyze-security.h"
4bff0a
 #include "analyze-verify.h"
4bff0a
 #include "bus-error.h"
4bff0a
 #include "bus-unit-util.h"
4bff0a
@@ -1659,6 +1660,19 @@ static int do_verify(int argc, char *argv[], void *userdata) {
4bff0a
         return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
4bff0a
 }
4bff0a
 
4bff0a
+static int do_security(int argc, char *argv[], void *userdata) {
4bff0a
+        _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        r = acquire_bus(&bus, NULL);
4bff0a
+        if (r < 0)
4bff0a
+                return log_error_errno(r, "Failed to create bus connection: %m");
4bff0a
+
4bff0a
+        (void) pager_open(arg_no_pager, false);
4bff0a
+
4bff0a
+        return analyze_security(bus, strv_skip(argv, 1), 0);
4bff0a
+}
4bff0a
+
4bff0a
 static int help(int argc, char *argv[], void *userdata) {
4bff0a
 
4bff0a
         (void) pager_open(arg_no_pager, false);
4bff0a
@@ -1696,6 +1710,7 @@ static int help(int argc, char *argv[], void *userdata) {
4bff0a
                "  verify FILE...           Check unit files for correctness\n"
4bff0a
                "  calendar SPEC...         Validate repetitive calendar time events\n"
4bff0a
                "  service-watchdogs [BOOL] Get/set service watchdog state\n"
4bff0a
+               "  security [UNIT...]       Analyze security of unit\n"
4bff0a
                , program_invocation_short_name);
4bff0a
 
4bff0a
         /* When updating this list, including descriptions, apply
4bff0a
@@ -1884,6 +1899,7 @@ int main(int argc, char *argv[]) {
4bff0a
                 { "verify",            2,        VERB_ANY, 0,            do_verify              },
4bff0a
                 { "calendar",          2,        VERB_ANY, 0,            test_calendar          },
4bff0a
                 { "service-watchdogs", VERB_ANY, 2,        0,            service_watchdogs      },
4bff0a
+                { "security",          VERB_ANY, VERB_ANY, 0,            do_security            },
4bff0a
                 {}
4bff0a
         };
4bff0a
 
4bff0a
diff --git a/src/analyze/meson.build b/src/analyze/meson.build
4bff0a
index 3a69a259b1..4db4dfa552 100644
4bff0a
--- a/src/analyze/meson.build
4bff0a
+++ b/src/analyze/meson.build
4bff0a
@@ -4,4 +4,6 @@ systemd_analyze_sources = files('''
4bff0a
         analyze.c
4bff0a
         analyze-verify.c
4bff0a
         analyze-verify.h
4bff0a
+        analyze-security.c
4bff0a
+        analyze-security.h
4bff0a
 '''.split())
4bff0a
diff --git a/src/basic/format-table.c b/src/basic/format-table.c
4bff0a
index 844b92f41c..c541e92b3c 100644
4bff0a
--- a/src/basic/format-table.c
4bff0a
+++ b/src/basic/format-table.c
4bff0a
@@ -10,7 +10,6 @@
4bff0a
 #include "gunicode.h"
4bff0a
 #include "pager.h"
4bff0a
 #include "parse-util.h"
4bff0a
-#include "pretty-print.h"
4bff0a
 #include "string-util.h"
4bff0a
 #include "terminal-util.h"
4bff0a
 #include "time-util.h"
4bff0a
diff --git a/src/basic/macro.h b/src/basic/macro.h
4bff0a
index 79ab02b27a..0fe6a62aa8 100644
4bff0a
--- a/src/basic/macro.h
4bff0a
+++ b/src/basic/macro.h
4bff0a
@@ -249,12 +249,13 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
4bff0a
  * computation should be possible in the given type. Therefore, we use
4bff0a
  * [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
4bff0a
  * quotient and the remainder, so both should be equally fast. */
4bff0a
-#define DIV_ROUND_UP(_x, _y)                                            \
4bff0a
-        __extension__ ({                                                \
4bff0a
-                const typeof(_x) __x = (_x);                            \
4bff0a
-                const typeof(_y) __y = (_y);                            \
4bff0a
-                (__x / __y + !!(__x % __y));                            \
4bff0a
-        })
4bff0a
+#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y))
4bff0a
+#define __DIV_ROUND_UP(xq, x, yq, y)                                    \
4bff0a
+        ({                                                              \
4bff0a
+                const typeof(x) UNIQ_T(X, xq) = (x);                    \
4bff0a
+                const typeof(y) UNIQ_T(Y, yq) = (y);                    \
4bff0a
+                (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
4bff0a
+         })
4bff0a
 
4bff0a
 #define assert_message_se(expr, message)                                \
4bff0a
         do {                                                            \
4bff0a
diff --git a/src/basic/terminal-util.c b/src/basic/terminal-util.c
4bff0a
index f4af0e6522..e2bbe8187d 100644
4bff0a
--- a/src/basic/terminal-util.c
4bff0a
+++ b/src/basic/terminal-util.c
4bff0a
@@ -1320,10 +1320,38 @@ int terminal_urlify(const char *url, const char *text, char **ret) {
4bff0a
         return 0;
4bff0a
 }
4bff0a
 
4bff0a
-int terminal_urlify_path(const char *path, const char *text, char **ret) {
4bff0a
+int file_url_from_path(const char *path, char **ret) {
4bff0a
         _cleanup_free_ char *absolute = NULL;
4bff0a
         struct utsname u;
4bff0a
-        const char *url;
4bff0a
+        char *url = NULL;
4bff0a
+        int r;
4bff0a
+
4bff0a
+        if (uname(&u) < 0)
4bff0a
+                return -errno;
4bff0a
+
4bff0a
+        if (!path_is_absolute(path)) {
4bff0a
+                r = path_make_absolute_cwd(path, &absolute);
4bff0a
+                if (r < 0)
4bff0a
+                        return r;
4bff0a
+
4bff0a
+                path = absolute;
4bff0a
+        }
4bff0a
+
4bff0a
+        /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
4bff0a
+         * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
4bff0a
+         * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
4bff0a
+         * careful with validating the strings either. */
4bff0a
+
4bff0a
+        url = strjoin("file://", u.nodename, path);
4bff0a
+        if (!url)
4bff0a
+                return -ENOMEM;
4bff0a
+
4bff0a
+        *ret = url;
4bff0a
+        return 0;
4bff0a
+}
4bff0a
+
4bff0a
+int terminal_urlify_path(const char *path, const char *text, char **ret) {
4bff0a
+        _cleanup_free_ char *url = NULL;
4bff0a
         int r;
4bff0a
 
4bff0a
         assert(path);
4bff0a
@@ -1348,27 +1376,14 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) {
4bff0a
                 return 0;
4bff0a
         }
4bff0a
 
4bff0a
-        if (uname(&u) < 0)
4bff0a
-                return -errno;
4bff0a
-
4bff0a
-        if (!path_is_absolute(path)) {
4bff0a
-                r = path_make_absolute_cwd(path, &absolute);
4bff0a
-                if (r < 0)
4bff0a
-                        return r;
4bff0a
-
4bff0a
-                path = absolute;
4bff0a
-        }
4bff0a
-
4bff0a
-        /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
4bff0a
-         * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
4bff0a
-         * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
4bff0a
-         * careful with validating the strings either. */
4bff0a
-
4bff0a
-        url = strjoina("file://", u.nodename, path);
4bff0a
+        r = file_url_from_path(path, &url;;
4bff0a
+        if (r < 0)
4bff0a
+                return r;
4bff0a
 
4bff0a
         return terminal_urlify(url, text, ret);
4bff0a
 }
4bff0a
 
4bff0a
+
4bff0a
 static int cat_file(const char *filename, bool newline) {
4bff0a
         _cleanup_fclose_ FILE *f = NULL;
4bff0a
         _cleanup_free_ char *urlified = NULL;
4bff0a
diff --git a/src/basic/terminal-util.h b/src/basic/terminal-util.h
4bff0a
index 0055b72343..3f23ecfd3d 100644
4bff0a
--- a/src/basic/terminal-util.h
4bff0a
+++ b/src/basic/terminal-util.h
4bff0a
@@ -154,6 +154,7 @@ int open_terminal_in_namespace(pid_t pid, const char *name, int mode);
4bff0a
 int vt_default_utf8(void);
4bff0a
 int vt_reset_keyboard(int fd);
4bff0a
 
4bff0a
+int file_url_from_path(const char *path, char **ret);
4bff0a
 int terminal_urlify(const char *url, const char *text, char **ret);
4bff0a
 int terminal_urlify_path(const char *path, const char *text, char **ret);
4bff0a