diff --git a/TODO b/TODO
index d82cfbda67..cafd75a01d 100644
--- a/TODO
+++ b/TODO
@@ -25,6 +25,10 @@ Features:
* when we fork off generators and such, lower LIMIT_NOFILE soft limit to 1K
+* rework seccomp/nnp logic that that even if User= is used in combination with
+ a seccomp option we don't have to set NNP. For that, change uid first whil
+ keeping CAP_SYS_ADMIN, then apply seccomp, the drop cap.
+
* add a concept for automatically loading per-unit secrets off disk and
inserting them into the kernel keyring. Maybe SecretsDirectory= similar to
ConfigurationDirectory=.
@@ -49,6 +53,9 @@ Features:
* set memory.oom.group in cgroupsv2 for all leaf cgroups (kernel v4.19+)
+* add a new syscall group "@esoteric" for more esoteric stuff such as bpf() and
+ usefaultd() and make systemd-analyze check for it.
+
* drop umask() calls and suchlike from our generators, pid1 should set things up correctly anyway
* paranoia: whenever we process passwords, call mlock() on the memory
@@ -290,9 +297,6 @@ Features:
* beef up pam_systemd to take unit file settings such as cgroups properties as
parameters
-* a new "systemd-analyze security" tool outputting a checklist of security
- features a service does and does not implement
-
* maybe hook of xfs/ext4 quotactl() with services? i.e. automatically manage
the quota of a the user indicated in User= via unit file settings, like the
other resource management concepts. Would mix nicely with DynamicUser=1. Or
diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md
index 3e61ea51e7..e965cb885a 100644
--- a/docs/ENVIRONMENT.md
+++ b/docs/ENVIRONMENT.md
@@ -46,9 +46,14 @@ All tools:
are understood, too (us, ms, s, min, h, d, w, month, y). If it is not set or set
to 0, then the built-in default is used.
-* `$SYSTEMD_MEMPOOL=0` — if set the internal memory caching logic employed by
+* `$SYSTEMD_MEMPOOL=0` — if set, the internal memory caching logic employed by
hash tables is turned off, and libc malloc() is used for all allocations.
+* `$SYSTEMD_EMOJI=0` — if set, tools such as "systemd-analyze security" will
+ not output graphical smiley emojis, but ASCII alternatives instead. Note that
+ this only controls use of Unicode emoji glyphs, and has no effect on other
+ Unicode glyphs.
+
systemctl:
* `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID1's private D-Bus
diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml
index 15e95b0c8f..7becf0133e 100644
--- a/man/systemd-analyze.xml
+++ b/man/systemd-analyze.xml
@@ -112,6 +112,12 @@
timespan
SPAN
+
+ systemd-analyze
+ OPTIONS
+ security
+ UNIT
+
@@ -263,6 +269,29 @@ NAutoVTs=8
The time span should adhere to the same syntax documented in systemd.time7.
Values without associated magnitudes are parsed as seconds.
+ systemd-analyze security analyzes the security and sandboxing settings of one or more
+ specified service units. If at least one unit name is specified the security settings of the specified service
+ units are inspected and a detailed analysis is shown. If no unit name is specified, all currently loaded,
+ long-running service units are inspected and a terse table with results shown. The command checks for various
+ security-related service settings, assigning each a numeric "exposure level" value, depending on how important a
+ setting is. It then calculates an overall exposure level for the whole unit, which is an estimation in the range
+ 0.0…10.0 indicating how exposed a service is security-wise. High exposure levels indicate very little applied
+ sandboxing. Low exposure levels indicate tight sandboxing and strongest security restrictions. Note that this only
+ analyzes the per-service security features systemd itself implements. This means that any additional security
+ mechanisms applied by the service code itself are not accounted for. The exposure level determined this way should
+ not be misunderstood: a high exposure level neither means that there is no effective sandboxing applied by the
+ service code itself, nor that the service is actually vulnerable to remote or local attacks. High exposure levels
+ do indicate however that most likely the service might benefit from additional settings applied to them. Please
+ note that many of the security and sandboxing settings individually can be circumvented — unless combined with
+ others. For example, if a service retains the privilege to establish or undo mount points many of the sandboxing
+ options can be undone by the service code itself. Due to that is essential that each service uses the most
+ comprehensive and strict sandboxing and security settings possible. The tool will take into account some of these
+ combinations and relationships between the settings, but not all. Also note that the security and sandboxing
+ settings analyzed here only apply to the operations executed by the service code itself. If a service has access to
+ an IPC system (such as D-Bus) it might request operations from other services that are not subject to the same
+ restrictions. Any comprehensive security and sandboxing analysis is hence incomplete if the IPC access policy is
+ not validated too.
+
If no command is passed, systemd-analyze
time is implied.
diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c
new file mode 100644
index 0000000000..ceee83cd83
--- /dev/null
+++ b/src/analyze/analyze-security.c
@@ -0,0 +1,2079 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include
+#include
+
+#include "analyze-security.h"
+#include "bus-error.h"
+#include "bus-unit-util.h"
+#include "bus-util.h"
+#include "env-util.h"
+#include "format-table.h"
+#include "in-addr-util.h"
+#include "locale-util.h"
+#include "macro.h"
+#include "parse-util.h"
+#include "path-util.h"
+#include "pretty-print.h"
+#include "seccomp-util.h"
+#include "set.h"
+#include "stdio-util.h"
+#include "strv.h"
+#include "terminal-util.h"
+#include "unit-def.h"
+#include "unit-name.h"
+
+struct security_info {
+ char *id;
+ char *type;
+ char *load_state;
+ char *fragment_path;
+ bool default_dependencies;
+
+ uint64_t ambient_capabilities;
+ uint64_t capability_bounding_set;
+
+ char *user;
+ char **supplementary_groups;
+ bool dynamic_user;
+
+ bool ip_address_deny_all;
+ bool ip_address_allow_localhost;
+ bool ip_address_allow_other;
+
+ char *keyring_mode;
+ bool lock_personality;
+ bool memory_deny_write_execute;
+ bool no_new_privileges;
+ char *notify_access;
+
+ bool private_devices;
+ bool private_mounts;
+ bool private_network;
+ bool private_tmp;
+ bool private_users;
+
+ bool protect_control_groups;
+ bool protect_kernel_modules;
+ bool protect_kernel_tunables;
+
+ char *protect_home;
+ char *protect_system;
+
+ bool remove_ipc;
+
+ bool restrict_address_family_inet;
+ bool restrict_address_family_unix;
+ bool restrict_address_family_netlink;
+ bool restrict_address_family_packet;
+ bool restrict_address_family_other;
+
+ uint64_t restrict_namespaces;
+ bool restrict_realtime;
+
+ char *root_directory;
+ char *root_image;
+
+ bool delegate;
+ char *device_policy;
+ bool device_allow_non_empty;
+
+ char **system_call_architectures;
+
+ bool system_call_filter_whitelist;
+ Set *system_call_filter;
+
+ uint32_t _umask;
+};
+
+struct security_assessor {
+ const char *id;
+ const char *description_good;
+ const char *description_bad;
+ const char *description_na;
+ const char *url;
+ uint64_t weight;
+ uint64_t range;
+ int (*assess)(const struct security_assessor *a, const struct security_info *info, const void *data, uint64_t *ret_badness, char **ret_description);
+ size_t offset;
+ uint64_t parameter;
+ bool default_dependencies_only;
+};
+
+static void security_info_free(struct security_info *i) {
+ if (!i)
+ return;
+
+ free(i->id);
+ free(i->type);
+ free(i->load_state);
+ free(i->fragment_path);
+
+ free(i->user);
+
+ free(i->protect_home);
+ free(i->protect_system);
+
+ free(i->root_directory);
+ free(i->root_image);
+
+ free(i->keyring_mode);
+ free(i->notify_access);
+
+ free(i->device_policy);
+
+ strv_free(i->supplementary_groups);
+ strv_free(i->system_call_architectures);
+
+ set_free_free(i->system_call_filter);
+}
+
+static bool security_info_runs_privileged(const struct security_info *i) {
+ assert(i);
+
+ if (STRPTR_IN_SET(i->user, "0", "root"))
+ return true;
+
+ if (i->dynamic_user)
+ return false;
+
+ return isempty(i->user);
+}
+
+static int assess_bool(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ const bool *b = data;
+
+ assert(b);
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness = a->parameter ? *b : !*b;
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static int assess_user(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ _cleanup_free_ char *d = NULL;
+ uint64_t b;
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (streq_ptr(info->user, NOBODY_USER_NAME)) {
+ d = strdup("Service runs under as '" NOBODY_USER_NAME "' user, which should not be used for services");
+ b = 9;
+ } else if (info->dynamic_user && !STR_IN_SET(info->user, "0", "root")) {
+ d = strdup("Service runs under a transient non-root user identity");
+ b = 0;
+ } else if (info->user && !STR_IN_SET(info->user, "0", "root", "")) {
+ d = strdup("Service runs under a static non-root user identity");
+ b = 0;
+ } else {
+ *ret_badness = 10;
+ *ret_description = NULL;
+ return 0;
+ }
+
+ if (!d)
+ return log_oom();
+
+ *ret_badness = b;
+ *ret_description = TAKE_PTR(d);
+
+ return 0;
+}
+
+static int assess_protect_home(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ const char *description;
+ uint64_t badness;
+ char *copy;
+ int r;
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ badness = 10;
+ description = "Service has full access to home directories";
+
+ r = parse_boolean(info->protect_home);
+ if (r < 0) {
+ if (streq_ptr(info->protect_home, "read-only")) {
+ badness = 5;
+ description = "Service has read-only access to home directories";
+ } else if (streq_ptr(info->protect_home, "tmpfs")) {
+ badness = 1;
+ description = "Service has access to fake empty home directories";
+ }
+ } else if (r > 0) {
+ badness = 0;
+ description = "Service has no access to home directories";
+ }
+
+ copy = strdup(description);
+ if (!copy)
+ return log_oom();
+
+ *ret_badness = badness;
+ *ret_description = copy;
+
+ return 0;
+}
+
+static int assess_protect_system(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ const char *description;
+ uint64_t badness;
+ char *copy;
+ int r;
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ badness = 10;
+ description = "Service has full access the OS file hierarchy";
+
+ r = parse_boolean(info->protect_system);
+ if (r < 0) {
+ if (streq_ptr(info->protect_system, "full")) {
+ badness = 3;
+ description = "Service has very limited write access to OS file hierarchy";
+ } else if (streq_ptr(info->protect_system, "strict")) {
+ badness = 0;
+ description = "Service has strict read-only access to the OS file hierarchy";
+ }
+ } else if (r > 0) {
+ badness = 5;
+ description = "Service has limited write access to the OS file hierarchy";
+ }
+
+ copy = strdup(description);
+ if (!copy)
+ return log_oom();
+
+ *ret_badness = badness;
+ *ret_description = copy;
+
+ return 0;
+}
+
+static int assess_root_directory(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness =
+ (isempty(info->root_directory) ||
+ path_equal(info->root_directory, "/")) &&
+ (isempty(info->root_image) ||
+ path_equal(info->root_image, "/"));
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static int assess_capability_bounding_set(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness = !!(info->capability_bounding_set & a->parameter);
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static int assess_umask(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ char *copy = NULL;
+ const char *d;
+ uint64_t b;
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (!FLAGS_SET(info->_umask, 0002)) {
+ d = "Files created by service are world-writable by default";
+ b = 10;
+ } else if (!FLAGS_SET(info->_umask, 0004)) {
+ d = "Files created by service are world-readable by default";
+ b = 5;
+ } else if (!FLAGS_SET(info->_umask, 0020)) {
+ d = "Files created by service are group-writable by default";
+ b = 2;
+ } else if (!FLAGS_SET(info->_umask, 0040)) {
+ d = "Files created by service are group-readable by default";
+ b = 1;
+ } else {
+ d = "Files created by service are accessible only by service's own user by default";
+ b = 0;
+ }
+
+ copy = strdup(d);
+ if (!copy)
+ return log_oom();
+
+ *ret_badness = b;
+ *ret_description = copy;
+
+ return 0;
+}
+
+static int assess_keyring_mode(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness = !streq_ptr(info->keyring_mode, "private");
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static int assess_notify_access(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness = streq_ptr(info->notify_access, "all");
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static int assess_remove_ipc(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (security_info_runs_privileged(info))
+ *ret_badness = UINT64_MAX;
+ else
+ *ret_badness = !info->remove_ipc;
+
+ *ret_description = NULL;
+ return 0;
+}
+
+static int assess_supplementary_groups(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (security_info_runs_privileged(info))
+ *ret_badness = UINT64_MAX;
+ else
+ *ret_badness = !strv_isempty(info->supplementary_groups);
+
+ *ret_description = NULL;
+ return 0;
+}
+
+static int assess_restrict_namespaces(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness = !!(info->restrict_namespaces & a->parameter);
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static int assess_system_call_architectures(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ char *d;
+ uint64_t b;
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (strv_isempty(info->system_call_architectures)) {
+ b = 10;
+ d = strdup("Service may execute system calls with all ABIs");
+ } else if (strv_equal(info->system_call_architectures, STRV_MAKE("native"))) {
+ b = 0;
+ d = strdup("Service may execute system calls only with native ABI");
+ } else {
+ b = 8;
+ d = strdup("Service may execute system calls with multiple ABIs");
+ }
+
+ if (!d)
+ return log_oom();
+
+ *ret_badness = b;
+ *ret_description = d;
+
+ return 0;
+}
+
+static bool syscall_names_in_filter(Set *s, bool whitelist, const SyscallFilterSet *f) {
+ const char *syscall;
+
+ NULSTR_FOREACH(syscall, f->value) {
+ bool b;
+
+ if (syscall[0] == '@') {
+ const SyscallFilterSet *g;
+ assert_se(g = syscall_filter_set_find(syscall));
+ b = syscall_names_in_filter(s, whitelist, g);
+ } else {
+#if HAVE_SECCOMP
+ int id;
+
+ /* Let's see if the system call actually exists on this platform, before complaining */
+ id = seccomp_syscall_resolve_name(syscall);
+ if (id < 0)
+ continue;
+#endif
+
+ b = set_contains(s, syscall);
+ }
+
+ if (whitelist == b) {
+ log_debug("Offending syscall filter item: %s", syscall);
+ return true; /* bad! */
+ }
+ }
+
+ return false;
+}
+
+static int assess_system_call_filter(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ const SyscallFilterSet *f;
+ char *d = NULL;
+ uint64_t b;
+
+ assert(a);
+ assert(info);
+ assert(ret_badness);
+ assert(ret_description);
+
+ assert(a->parameter < _SYSCALL_FILTER_SET_MAX);
+ f = syscall_filter_sets + a->parameter;
+
+ if (!info->system_call_filter_whitelist && set_isempty(info->system_call_filter)) {
+ d = strdup("Service does not filter system calls");
+ b = 10;
+ } else {
+ bool bad;
+
+ log_debug("Analyzing system call filter, checking against: %s", f->name);
+ bad = syscall_names_in_filter(info->system_call_filter, info->system_call_filter_whitelist, f);
+ log_debug("Result: %s", bad ? "bad" : "good");
+
+ if (info->system_call_filter_whitelist) {
+ if (bad) {
+ (void) asprintf(&d, "System call whitelist defined for service, and %s is included", f->name);
+ b = 9;
+ } else {
+ (void) asprintf(&d, "System call whitelist defined for service, and %s is not included", f->name);
+ b = 0;
+ }
+ } else {
+ if (bad) {
+ (void) asprintf(&d, "System call blacklist defined for service, and %s is not included", f->name);
+ b = 10;
+ } else {
+ (void) asprintf(&d, "System call blacklist defined for service, and %s is included", f->name);
+ b = 5;
+ }
+ }
+ }
+
+ if (!d)
+ return log_oom();
+
+ *ret_badness = b;
+ *ret_description = d;
+
+ return 0;
+}
+
+static int assess_ip_address_allow(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ char *d = NULL;
+ uint64_t b;
+
+ assert(info);
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (!info->ip_address_deny_all) {
+ d = strdup("Service does not define an IP address whitelist");
+ b = 10;
+ } else if (info->ip_address_allow_other) {
+ d = strdup("Service defines IP address whitelist with non-localhost entries");
+ b = 5;
+ } else if (info->ip_address_allow_localhost) {
+ d = strdup("Service defines IP address whitelits with only localhost entries");
+ b = 2;
+ } else {
+ d = strdup("Service blocks all IP address ranges");
+ b = 0;
+ }
+
+ if (!d)
+ return log_oom();
+
+ *ret_badness = b;
+ *ret_description = d;
+
+ return 0;
+}
+
+static int assess_device_allow(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ char *d = NULL;
+ uint64_t b;
+
+ assert(info);
+ assert(ret_badness);
+ assert(ret_description);
+
+ if (STRPTR_IN_SET(info->device_policy, "strict", "closed")) {
+
+ if (info->device_allow_non_empty) {
+ d = strdup("Service has a device ACL with some special devices");
+ b = 5;
+ } else {
+ d = strdup("Service has a minimal device ACL");
+ b = 0;
+ }
+ } else {
+ d = strdup("Service has no device ACL");
+ b = 10;
+ }
+
+ if (!d)
+ return log_oom();
+
+ *ret_badness = b;
+ *ret_description = d;
+
+ return 0;
+}
+
+static int assess_ambient_capabilities(
+ const struct security_assessor *a,
+ const struct security_info *info,
+ const void *data,
+ uint64_t *ret_badness,
+ char **ret_description) {
+
+ assert(ret_badness);
+ assert(ret_description);
+
+ *ret_badness = info->ambient_capabilities != 0;
+ *ret_description = NULL;
+
+ return 0;
+}
+
+static const struct security_assessor security_assessor_table[] = {
+ {
+ .id = "User=/DynamicUser=",
+ .description_bad = "Service runs as root user",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#User=",
+ .weight = 2000,
+ .range = 10,
+ .assess = assess_user,
+ },
+ {
+ .id = "SupplementaryGroups=",
+ .description_good = "Service has no supplementary groups",
+ .description_bad = "Service runs with supplementary groups",
+ .description_na = "Service runs as root, option does not matter",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SupplementaryGroups=",
+ .weight = 200,
+ .range = 1,
+ .assess = assess_supplementary_groups,
+ },
+ {
+ .id = "PrivateDevices=",
+ .description_good = "Service has no access to hardware devices",
+ .description_bad = "Service potentially has access to hardware devices",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateDevices=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, private_devices),
+ },
+ {
+ .id = "PrivateMounts=",
+ .description_good = "Service cannot install system mounts",
+ .description_bad = "Service may install system mounts",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateMounts=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, private_mounts),
+ },
+ {
+ .id = "PrivateNetwork=",
+ .description_good = "Service has no access to the host's network",
+ .description_bad = "Service has access to the host's network",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateNetwork=",
+ .weight = 2500,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, private_network),
+ },
+ {
+ .id = "PrivateTmp=",
+ .description_good = "Service has no access to other software's temporary files",
+ .description_bad = "Service has access to other software's temporary files",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateTmp=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, private_tmp),
+ .default_dependencies_only = true,
+ },
+ {
+ .id = "PrivateUsers=",
+ .description_good = "Service does not have access to other users",
+ .description_bad = "Service has access to other users",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#PrivateUsers=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, private_users),
+ },
+ {
+ .id = "ProtectControlGroups=",
+ .description_good = "Service cannot modify the control group file system",
+ .description_bad = "Service may modify to the control group file system",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, protect_control_groups),
+ },
+ {
+ .id = "ProtectKernelModules=",
+ .description_good = "Service cannot load or read kernel modules",
+ .description_bad = "Service may load or read kernel modules",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelModules=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, protect_kernel_modules),
+ },
+ {
+ .id = "ProtectKernelTunables=",
+ .description_good = "Service cannot alter kernel tunables (/proc/sys, …)",
+ .description_bad = "Service may alter kernel tunables",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelTunables=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, protect_kernel_tunables),
+ },
+ {
+ .id = "ProtectHome=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_protect_home,
+ .default_dependencies_only = true,
+ },
+ {
+ .id = "ProtectSystem=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_protect_system,
+ .default_dependencies_only = true,
+ },
+ {
+ .id = "RootDirectory=/RootImage=",
+ .description_good = "Service has its own root directory/image",
+ .description_bad = "Service runs within the host's root directory",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RootDirectory=",
+ .weight = 200,
+ .range = 1,
+ .assess = assess_root_directory,
+ .default_dependencies_only = true,
+ },
+ {
+ .id = "LockPersonality=",
+ .description_good = "Service cannot change ABI personality",
+ .description_bad = "Service may change ABI personality",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#LockPersonality=",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, lock_personality),
+ },
+ {
+ .id = "MemoryDenyWriteExecute=",
+ .description_good = "Service cannot create writable executable memory mappings",
+ .description_bad = "Service may create writable executable memory mappings",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#MemoryDenyWriteExecute=",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, memory_deny_write_execute),
+ },
+ {
+ .id = "NoNewPrivileges=",
+ .description_good = "Service processes cannot acquire new privileges",
+ .description_bad = "Service processes may acquire new privileges",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NoNewPrivileges=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, no_new_privileges),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN",
+ .description_good = "Service has no administrator privileges",
+ .description_bad = "Service has administrator privileges",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = UINT64_C(1) << CAP_SYS_ADMIN,
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)",
+ .description_good = "Service cannot change UID/GID identities/capabilities",
+ .description_bad = "Service may change UID/GID identities/capabilities",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SETUID)|
+ (UINT64_C(1) << CAP_SETGID)|
+ (UINT64_C(1) << CAP_SETPCAP),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_PTRACE",
+ .description_good = "Service has no ptrace() debugging abilities",
+ .description_bad = "Service has ptrace() debugging abilities",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_PTRACE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_TIME",
+ .description_good = "Service processes cannot change the system clock",
+ .description_bad = "Service processes may change the system clock",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = UINT64_C(1) << CAP_SYS_TIME,
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_NET_ADMIN",
+ .description_good = "Service has no network configuration privileges",
+ .description_bad = "Service has network configuration privileges",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_NET_ADMIN),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_RAWIO",
+ .description_good = "Service has no raw I/O access",
+ .description_bad = "Service has raw I/O access",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_RAWIO),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_MODULE",
+ .description_good = "Service cannot load kernel modules",
+ .description_bad = "Service may load kernel modules",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_MODULE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_AUDIT_*",
+ .description_good = "Service has no audit subsystem access",
+ .description_bad = "Service has audit subsystem access",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_AUDIT_CONTROL) |
+ (UINT64_C(1) << CAP_AUDIT_READ) |
+ (UINT64_C(1) << CAP_AUDIT_WRITE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYSLOG",
+ .description_good = "Service has no access to kernel logging",
+ .description_bad = "Service has access to kernel logging",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYSLOG),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)",
+ .description_good = "Service has no privileges to change resource use parameters",
+ .description_bad = "Service has privileges to change resource use parameters",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_NICE) |
+ (UINT64_C(1) << CAP_SYS_RESOURCE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_MKNOD",
+ .description_good = "Service cannot create device nodes",
+ .description_bad = "Service may create device nodes",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_MKNOD),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)",
+ .description_good = "Service cannot change file ownership/access mode/capabilities",
+ .description_bad = "Service may change file ownership/access mode/capabilities unrestricted",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_CHOWN) |
+ (UINT64_C(1) << CAP_FSETID) |
+ (UINT64_C(1) << CAP_SETFCAP),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)",
+ .description_good = "Service cannot override UNIX file/IPC permission checks",
+ .description_bad = "Service may override UNIX file/IPC permission checks",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_DAC_OVERRIDE) |
+ (UINT64_C(1) << CAP_DAC_READ_SEARCH) |
+ (UINT64_C(1) << CAP_FOWNER) |
+ (UINT64_C(1) << CAP_IPC_OWNER),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_KILL",
+ .description_good = "Service cannot send UNIX signals to arbitrary processes",
+ .description_bad = "Service may send UNIX signals to arbitrary processes",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_KILL),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)",
+ .description_good = "Service has no elevated networking privileges",
+ .description_bad = "Service has elevated networking privileges",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_NET_BIND_SERVICE) |
+ (UINT64_C(1) << CAP_NET_BROADCAST) |
+ (UINT64_C(1) << CAP_NET_RAW),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_BOOT",
+ .description_good = "Service cannot issue reboot()",
+ .description_bad = "Service may issue reboot()",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_BOOT),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_MAC_*",
+ .description_good = "Service cannot adjust SMACK MAC",
+ .description_bad = "Service may adjust SMACK MAC",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_MAC_ADMIN)|
+ (UINT64_C(1) << CAP_MAC_OVERRIDE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE",
+ .description_good = "Service cannot mark files immutable",
+ .description_bad = "Service may mark files immutable",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 75,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_LINUX_IMMUTABLE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_IPC_LOCK",
+ .description_good = "Service cannot lock memory into RAM",
+ .description_bad = "Service may lock memory into RAM",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 50,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_IPC_LOCK),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_CHROOT",
+ .description_good = "Service cannot issue chroot()",
+ .description_bad = "Service may issue chroot()",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 50,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_CHROOT),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_BLOCK_SUSPEND",
+ .description_good = "Service cannot establish wake locks",
+ .description_bad = "Service may establish wake locks",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 25,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_BLOCK_SUSPEND),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_WAKE_ALARM",
+ .description_good = "Service cannot program timers that wake up the system",
+ .description_bad = "Service may program timers that wake up the system",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 25,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_WAKE_ALARM),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_LEASE",
+ .description_good = "Service cannot create file leases",
+ .description_bad = "Service may create file leases",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 25,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_LEASE),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG",
+ .description_good = "Service cannot issue vhangup()",
+ .description_bad = "Service may issue vhangup()",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 25,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_TTY_CONFIG),
+ },
+ {
+ .id = "CapabilityBoundingSet=~CAP_SYS_PACCT",
+ .description_good = "Service cannot use acct()",
+ .description_bad = "Service may use acct()",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#CapabilityBoundingSet=",
+ .weight = 25,
+ .range = 1,
+ .assess = assess_capability_bounding_set,
+ .parameter = (UINT64_C(1) << CAP_SYS_PACCT),
+ },
+ {
+ .id = "UMask=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#UMask=",
+ .weight = 100,
+ .range = 10,
+ .assess = assess_umask,
+ },
+ {
+ .id = "KeyringMode=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#KeyringMode=",
+ .description_good = "Service doesn't share key material with other services",
+ .description_bad = "Service shares key material with other service",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_keyring_mode,
+ },
+ {
+ .id = "NotifyAccess=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#NotifyAccess=",
+ .description_good = "Service child processes cannot alter service state",
+ .description_bad = "Service child processes may alter service state",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_notify_access,
+ },
+ {
+ .id = "RemoveIPC=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RemoveIPC=",
+ .description_good = "Service user cannot leave SysV IPC objects around",
+ .description_bad = "Service user may leave SysV IPC objects around",
+ .description_na = "Service runs as root, option does not apply",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_remove_ipc,
+ .offset = offsetof(struct security_info, remove_ipc),
+ },
+ {
+ .id = "Delegate=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#Delegate=",
+ .description_good = "Service does not maintain its own delegated control group subtree",
+ .description_bad = "Service maintains its own delegated control group subtree",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, delegate),
+ .parameter = true, /* invert! */
+ },
+ {
+ .id = "RestrictRealtime=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictRealtime=",
+ .description_good = "Service realtime scheduling access is restricted",
+ .description_bad = "Service may acquire realtime scheduling",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, restrict_realtime),
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWUSER",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create user namespaces",
+ .description_bad = "Service may create user namespaces",
+ .weight = 1500,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWUSER,
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWNS",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create file system namespaces",
+ .description_bad = "Service may create file system namespaces",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWNS,
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWIPC",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create IPC namespaces",
+ .description_bad = "Service may create IPC namespaces",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWIPC,
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWPID",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create process namespaces",
+ .description_bad = "Service may create process namespaces",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWPID,
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWCGROUP",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create cgroup namespaces",
+ .description_bad = "Service may create cgroup namespaces",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWCGROUP,
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWNET",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create network namespaces",
+ .description_bad = "Service may create network namespaces",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWNET,
+ },
+ {
+ .id = "RestrictNamespaces=~CLONE_NEWUTS",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictNamespaces=",
+ .description_good = "Service cannot create hostname namespaces",
+ .description_bad = "Service may create hostname namespaces",
+ .weight = 100,
+ .range = 1,
+ .assess = assess_restrict_namespaces,
+ .parameter = CLONE_NEWUTS,
+ },
+ {
+ .id = "RestrictAddressFamilies=~AF_(INET|INET6)",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
+ .description_good = "Service cannot allocate Internet sockets",
+ .description_bad = "Service may allocate Internet sockets",
+ .weight = 1500,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, restrict_address_family_inet),
+ },
+ {
+ .id = "RestrictAddressFamilies=~AF_UNIX",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
+ .description_good = "Service cannot allocate local sockets",
+ .description_bad = "Service may allocate local sockets",
+ .weight = 25,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, restrict_address_family_unix),
+ },
+ {
+ .id = "RestrictAddressFamilies=~AF_NETLINK",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
+ .description_good = "Service cannot allocate netlink sockets",
+ .description_bad = "Service may allocate netlink sockets",
+ .weight = 200,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, restrict_address_family_netlink),
+ },
+ {
+ .id = "RestrictAddressFamilies=~AF_PACKET",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
+ .description_good = "Service cannot allocate packet sockets",
+ .description_bad = "Service may allocate packet sockets",
+ .weight = 1000,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, restrict_address_family_packet),
+ },
+ {
+ .id = "RestrictAddressFamilies=~…",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictAddressFamilies=",
+ .description_good = "Service cannot allocate exotic sockets",
+ .description_bad = "Service may allocate exotic sockets",
+ .weight = 1250,
+ .range = 1,
+ .assess = assess_bool,
+ .offset = offsetof(struct security_info, restrict_address_family_other),
+ },
+ {
+ .id = "SystemCallArchitectures=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_architectures,
+ },
+ {
+ .id = "SystemCallFilter=~@swap",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_SWAP,
+ },
+ {
+ .id = "SystemCallFilter=~@obsolete",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 250,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_OBSOLETE,
+ },
+ {
+ .id = "SystemCallFilter=~@clock",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_CLOCK,
+ },
+ {
+ .id = "SystemCallFilter=~@cpu-emulation",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 250,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_CPU_EMULATION,
+ },
+ {
+ .id = "SystemCallFilter=~@debug",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_DEBUG,
+ },
+ {
+ .id = "SystemCallFilter=~@mount",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_MOUNT,
+ },
+ {
+ .id = "SystemCallFilter=~@module",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_MODULE,
+ },
+ {
+ .id = "SystemCallFilter=~@raw-io",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_RAW_IO,
+ },
+ {
+ .id = "SystemCallFilter=~@reboot",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_REBOOT,
+ },
+ {
+ .id = "SystemCallFilter=~@privileged",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 700,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_PRIVILEGED,
+ },
+ {
+ .id = "SystemCallFilter=~@resources",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=",
+ .weight = 700,
+ .range = 10,
+ .assess = assess_system_call_filter,
+ .parameter = SYSCALL_FILTER_SET_RESOURCES,
+ },
+ {
+ .id = "IPAddressDeny=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#IPAddressDeny=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_ip_address_allow,
+ },
+ {
+ .id = "DeviceAllow=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DeviceAllow=",
+ .weight = 1000,
+ .range = 10,
+ .assess = assess_device_allow,
+ },
+ {
+ .id = "AmbientCapabilities=",
+ .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#AmbientCapabilities=",
+ .description_good = "Service process does not receive ambient capabilities",
+ .description_bad = "Service process receives ambient capabilities",
+ .weight = 500,
+ .range = 1,
+ .assess = assess_ambient_capabilities,
+ },
+};
+
+static int assess(const struct security_info *info, Table *overview_table, AnalyzeSecurityFlags flags) {
+ static const struct {
+ uint64_t exposure;
+ const char *name;
+ const char *color;
+ SpecialGlyph smiley;
+ } badness_table[] = {
+ { 100, "DANGEROUS", ANSI_HIGHLIGHT_RED, DEPRESSED_SMILEY },
+ { 90, "UNSAFE", ANSI_HIGHLIGHT_RED, UNHAPPY_SMILEY },
+ { 75, "EXPOSED", ANSI_HIGHLIGHT_YELLOW, SLIGHTLY_UNHAPPY_SMILEY },
+ { 50, "MEDIUM", NULL, NEUTRAL_SMILEY },
+ { 10, "OK", ANSI_HIGHLIGHT_GREEN, SLIGHTLY_HAPPY_SMILEY },
+ { 1, "SAFE", ANSI_HIGHLIGHT_GREEN, HAPPY_SMILEY },
+ { 0, "PERFECT", ANSI_HIGHLIGHT_GREEN, ECSTATIC_SMILEY },
+ };
+
+ uint64_t badness_sum = 0, weight_sum = 0, exposure;
+ _cleanup_(table_unrefp) Table *details_table = NULL;
+ size_t i;
+ int r;
+
+ if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
+ details_table = table_new("", "NAME", "DESCRIPTION", "WEIGHT", "BADNESS", "RANGE", "EXPOSURE");
+ if (!details_table)
+ return log_oom();
+
+ (void) table_set_sort(details_table, 3, 1, (size_t) -1);
+ (void) table_set_reverse(details_table, 3, true);
+
+ if (getenv_bool("SYSTEMD_ANALYZE_DEBUG") <= 0)
+ (void) table_set_display(details_table, 0, 1, 2, 6, (size_t) -1);
+ }
+
+ for (i = 0; i < ELEMENTSOF(security_assessor_table); i++) {
+ const struct security_assessor *a = security_assessor_table + i;
+ _cleanup_free_ char *d = NULL;
+ uint64_t badness;
+ void *data;
+
+ data = (uint8_t*) info + a->offset;
+
+ if (a->default_dependencies_only && !info->default_dependencies) {
+ badness = UINT64_MAX;
+ d = strdup("Service runs in special boot phase, option does not apply");
+ if (!d)
+ return log_oom();
+ } else {
+ r = a->assess(a, info, data, &badness, &d);
+ if (r < 0)
+ return r;
+ }
+
+ assert(a->range > 0);
+
+ if (badness != UINT64_MAX) {
+ assert(badness <= a->range);
+
+ badness_sum += DIV_ROUND_UP(badness * a->weight, a->range);
+ weight_sum += a->weight;
+ }
+
+ if (details_table) {
+ const char *checkmark, *description, *color = NULL;
+ TableCell *cell;
+
+ if (badness == UINT64_MAX) {
+ checkmark = " ";
+ description = a->description_na;
+ color = NULL;
+ } else if (badness == a->range) {
+ checkmark = special_glyph(CROSS_MARK);
+ description = a->description_bad;
+ color = ansi_highlight_red();
+ } else if (badness == 0) {
+ checkmark = special_glyph(CHECK_MARK);
+ description = a->description_good;
+ color = ansi_highlight_green();
+ } else {
+ checkmark = special_glyph(CROSS_MARK);
+ description = NULL;
+ color = ansi_highlight_red();
+ }
+
+ if (d)
+ description = d;
+
+ r = table_add_cell_full(details_table, &cell, TABLE_STRING, checkmark, 1, 1, 0, 0, 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ if (color)
+ (void) table_set_color(details_table, cell, color);
+
+ r = table_add_cell(details_table, &cell, TABLE_STRING, a->id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ if (a->url)
+ (void) table_set_url(details_table, cell, a->url);
+
+ r = table_add_cell(details_table, NULL, TABLE_STRING, description);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+
+ r = table_add_cell(details_table, &cell, TABLE_UINT64, &a->weight);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ (void) table_set_align_percent(details_table, cell, 100);
+
+ r = table_add_cell(details_table, &cell, TABLE_UINT64, &badness);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ (void) table_set_align_percent(details_table, cell, 100);
+
+ r = table_add_cell(details_table, &cell, TABLE_UINT64, &a->range);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ (void) table_set_align_percent(details_table, cell, 100);
+
+ r = table_add_cell(details_table, &cell, TABLE_EMPTY, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ (void) table_set_align_percent(details_table, cell, 100);
+ }
+ }
+
+ if (details_table) {
+ size_t row;
+
+ for (row = 1; row < table_get_rows(details_table); row++) {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
+ const uint64_t *weight, *badness, *range;
+ TableCell *cell;
+ uint64_t x;
+
+ assert_se(weight = table_get_at(details_table, row, 3));
+ assert_se(badness = table_get_at(details_table, row, 4));
+ assert_se(range = table_get_at(details_table, row, 5));
+
+ if (*badness == UINT64_MAX || *badness == 0)
+ continue;
+
+ assert_se(cell = table_get_cell(details_table, row, 6));
+
+ x = DIV_ROUND_UP(DIV_ROUND_UP(*badness * *weight * 100U, *range), weight_sum);
+ xsprintf(buf, "%" PRIu64 ".%" PRIu64, x / 10, x % 10);
+
+ r = table_update(details_table, cell, TABLE_STRING, buf);
+ if (r < 0)
+ return log_error_errno(r, "Failed to update cell in table: %m");
+ }
+
+ r = table_print(details_table, stdout);
+ if (r < 0)
+ return log_error_errno(r, "Failed to output table: %m");
+ }
+
+ exposure = DIV_ROUND_UP(badness_sum * 100U, weight_sum);
+
+ for (i = 0; i < ELEMENTSOF(badness_table); i++)
+ if (exposure >= badness_table[i].exposure)
+ break;
+
+ assert(i < ELEMENTSOF(badness_table));
+
+ if (details_table) {
+ _cleanup_free_ char *clickable = NULL;
+ const char *name;
+
+ /* If we shall output the details table, also print the brief summary underneath */
+
+ if (info->fragment_path) {
+ r = terminal_urlify_path(info->fragment_path, info->id, &clickable);
+ if (r < 0)
+ return log_oom();
+
+ name = clickable;
+ } else
+ name = info->id;
+
+ printf("\n%s %sOverall exposure level for %s%s: %s%" PRIu64 ".%" PRIu64 " %s%s %s\n",
+ special_glyph(ARROW),
+ ansi_highlight(),
+ name,
+ ansi_normal(),
+ colors_enabled() ? strempty(badness_table[i].color) : "",
+ exposure / 10, exposure % 10,
+ badness_table[i].name,
+ ansi_normal(),
+ special_glyph(badness_table[i].smiley));
+ }
+
+ fflush(stdout);
+
+ if (overview_table) {
+ char buf[DECIMAL_STR_MAX(uint64_t) + 1 + DECIMAL_STR_MAX(uint64_t) + 1];
+ TableCell *cell;
+
+ r = table_add_cell(overview_table, &cell, TABLE_STRING, info->id);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ if (info->fragment_path) {
+ _cleanup_free_ char *url = NULL;
+
+ r = file_url_from_path(info->fragment_path, &url);
+ if (r < 0)
+ return log_error_errno(r, "Failed to generate URL from path: %m");
+
+ (void) table_set_url(overview_table, cell, url);
+ }
+
+ xsprintf(buf, "%" PRIu64 ".%" PRIu64, exposure / 10, exposure % 10);
+ r = table_add_cell(overview_table, &cell, TABLE_STRING, buf);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ (void) table_set_align_percent(overview_table, cell, 100);
+
+ r = table_add_cell(overview_table, &cell, TABLE_STRING, badness_table[i].name);
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ (void) table_set_color(overview_table, cell, strempty(badness_table[i].color));
+
+ r = table_add_cell(overview_table, NULL, TABLE_STRING, special_glyph(badness_table[i].smiley));
+ if (r < 0)
+ return log_error_errno(r, "Failed to add cell to table: %m");
+ }
+
+ return 0;
+}
+
+static int property_read_restrict_address_families(
+ sd_bus *bus,
+ const char *member,
+ sd_bus_message *m,
+ sd_bus_error *error,
+ void *userdata) {
+
+ struct security_info *info = userdata;
+ int whitelist, r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+
+ r = sd_bus_message_enter_container(m, 'r', "bas");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(m, "b", &whitelist);
+ if (r < 0)
+ return r;
+
+ info->restrict_address_family_inet =
+ info->restrict_address_family_unix =
+ info->restrict_address_family_netlink =
+ info->restrict_address_family_packet =
+ info->restrict_address_family_other = whitelist;
+
+ r = sd_bus_message_enter_container(m, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *name;
+
+ r = sd_bus_message_read(m, "s", &name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ if (STR_IN_SET(name, "AF_INET", "AF_INET6"))
+ info->restrict_address_family_inet = !whitelist;
+ else if (streq(name, "AF_UNIX"))
+ info->restrict_address_family_unix = !whitelist;
+ else if (streq(name, "AF_NETLINK"))
+ info->restrict_address_family_netlink = !whitelist;
+ else if (streq(name, "AF_PACKET"))
+ info->restrict_address_family_packet = !whitelist;
+ else
+ info->restrict_address_family_other = !whitelist;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_exit_container(m);
+}
+
+static int property_read_system_call_filter(
+ sd_bus *bus,
+ const char *member,
+ sd_bus_message *m,
+ sd_bus_error *error,
+ void *userdata) {
+
+ struct security_info *info = userdata;
+ int whitelist, r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+
+ r = sd_bus_message_enter_container(m, 'r', "bas");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(m, "b", &whitelist);
+ if (r < 0)
+ return r;
+
+ info->system_call_filter_whitelist = whitelist;
+
+ r = sd_bus_message_enter_container(m, 'a', "s");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *name;
+
+ r = sd_bus_message_read(m, "s", &name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = set_ensure_allocated(&info->system_call_filter, &string_hash_ops);
+ if (r < 0)
+ return r;
+
+ r = set_put_strdup(info->system_call_filter, name);
+ if (r < 0)
+ return r;
+ }
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_exit_container(m);
+}
+
+static int property_read_ip_address_allow(
+ sd_bus *bus,
+ const char *member,
+ sd_bus_message *m,
+ sd_bus_error *error,
+ void *userdata) {
+
+ struct security_info *info = userdata;
+ bool deny_ipv4 = false, deny_ipv6 = false;
+ int r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+
+ r = sd_bus_message_enter_container(m, 'a', "(iayu)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const void *data;
+ size_t size;
+ int32_t family;
+ uint32_t prefixlen;
+
+ r = sd_bus_message_enter_container(m, 'r', "iayu");
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ r = sd_bus_message_read(m, "i", &family);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read_array(m, 'y', &data, &size);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_read(m, "u", &prefixlen);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_exit_container(m);
+ if (r < 0)
+ return r;
+
+ if (streq(member, "IPAddressAllow")) {
+ union in_addr_union u;
+
+ if (family == AF_INET && size == 4 && prefixlen == 8)
+ memcpy(&u.in, data, size);
+ else if (family == AF_INET6 && size == 16 && prefixlen == 128)
+ memcpy(&u.in6, data, size);
+ else {
+ info->ip_address_allow_other = true;
+ continue;
+ }
+
+ if (in_addr_is_localhost(family, &u))
+ info->ip_address_allow_localhost = true;
+ else
+ info->ip_address_allow_other = true;
+ } else {
+ assert(streq(member, "IPAddressDeny"));
+
+ if (family == AF_INET && size == 4 && prefixlen == 0)
+ deny_ipv4 = true;
+ else if (family == AF_INET6 && size == 16 && prefixlen == 0)
+ deny_ipv6 = true;
+ }
+ }
+
+ info->ip_address_deny_all = deny_ipv4 && deny_ipv6;
+
+ return sd_bus_message_exit_container(m);
+}
+
+static int property_read_device_allow(
+ sd_bus *bus,
+ const char *member,
+ sd_bus_message *m,
+ sd_bus_error *error,
+ void *userdata) {
+
+ struct security_info *info = userdata;
+ size_t n = 0;
+ int r;
+
+ assert(bus);
+ assert(member);
+ assert(m);
+
+ r = sd_bus_message_enter_container(m, 'a', "(ss)");
+ if (r < 0)
+ return r;
+
+ for (;;) {
+ const char *name, *policy;
+
+ r = sd_bus_message_read(m, "(ss)", &name, &policy);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ break;
+
+ n++;
+ }
+
+ info->device_allow_non_empty = n > 0;
+
+ return sd_bus_message_exit_container(m);
+}
+
+static int acquire_security_info(sd_bus *bus, const char *name, struct security_info *info, AnalyzeSecurityFlags flags) {
+
+ static const struct bus_properties_map security_map[] = {
+ { "AmbientCapabilities", "t", NULL, offsetof(struct security_info, ambient_capabilities) },
+ { "CapabilityBoundingSet", "t", NULL, offsetof(struct security_info, capability_bounding_set) },
+ { "DefaultDependencies", "b", NULL, offsetof(struct security_info, default_dependencies) },
+ { "Delegate", "b", NULL, offsetof(struct security_info, delegate) },
+ { "DeviceAllow", "a(ss)", property_read_device_allow, 0 },
+ { "DevicePolicy", "s", NULL, offsetof(struct security_info, device_policy) },
+ { "DynamicUser", "b", NULL, offsetof(struct security_info, dynamic_user) },
+ { "FragmentPath", "s", NULL, offsetof(struct security_info, fragment_path) },
+ { "IPAddressAllow", "a(iayu)", property_read_ip_address_allow, 0 },
+ { "IPAddressDeny", "a(iayu)", property_read_ip_address_allow, 0 },
+ { "Id", "s", NULL, offsetof(struct security_info, id) },
+ { "KeyringMode", "s", NULL, offsetof(struct security_info, keyring_mode) },
+ { "LoadState", "s", NULL, offsetof(struct security_info, load_state) },
+ { "LockPersonality", "b", NULL, offsetof(struct security_info, lock_personality) },
+ { "MemoryDenyWriteExecute", "b", NULL, offsetof(struct security_info, memory_deny_write_execute) },
+ { "NoNewPrivileges", "b", NULL, offsetof(struct security_info, no_new_privileges) },
+ { "NotifyAccess", "s", NULL, offsetof(struct security_info, notify_access) },
+ { "PrivateDevices", "b", NULL, offsetof(struct security_info, private_devices) },
+ { "PrivateMounts", "b", NULL, offsetof(struct security_info, private_mounts) },
+ { "PrivateNetwork", "b", NULL, offsetof(struct security_info, private_network) },
+ { "PrivateTmp", "b", NULL, offsetof(struct security_info, private_tmp) },
+ { "PrivateUsers", "b", NULL, offsetof(struct security_info, private_users) },
+ { "PrivateUsers", "b", NULL, offsetof(struct security_info, private_users) },
+ { "ProtectControlGroups", "b", NULL, offsetof(struct security_info, protect_control_groups) },
+ { "ProtectHome", "s", NULL, offsetof(struct security_info, protect_home) },
+ { "ProtectKernelModules", "b", NULL, offsetof(struct security_info, protect_kernel_modules) },
+ { "ProtectKernelTunables", "b", NULL, offsetof(struct security_info, protect_kernel_tunables) },
+ { "ProtectSystem", "s", NULL, offsetof(struct security_info, protect_system) },
+ { "RemoveIPC", "b", NULL, offsetof(struct security_info, remove_ipc) },
+ { "RestrictAddressFamilies", "(bas)", property_read_restrict_address_families, 0 },
+ { "RestrictNamespaces", "t", NULL, offsetof(struct security_info, restrict_namespaces) },
+ { "RestrictRealtime", "b", NULL, offsetof(struct security_info, restrict_realtime) },
+ { "RootDirectory", "s", NULL, offsetof(struct security_info, root_directory) },
+ { "RootImage", "s", NULL, offsetof(struct security_info, root_image) },
+ { "SupplementaryGroups", "as", NULL, offsetof(struct security_info, supplementary_groups) },
+ { "SystemCallArchitectures", "as", NULL, offsetof(struct security_info, system_call_architectures) },
+ { "SystemCallFilter", "(as)", property_read_system_call_filter, 0 },
+ { "Type", "s", NULL, offsetof(struct security_info, type) },
+ { "UMask", "u", NULL, offsetof(struct security_info, _umask) },
+ { "User", "s", NULL, offsetof(struct security_info, user) },
+ {}
+ };
+
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_free_ char *path = NULL;
+ int r;
+
+ /* Note: this mangles *info on failure! */
+
+ assert(bus);
+ assert(name);
+ assert(info);
+
+ path = unit_dbus_path_from_name(name);
+ if (!path)
+ return log_oom();
+
+ r = bus_map_all_properties(bus,
+ "org.freedesktop.systemd1",
+ path,
+ security_map,
+ BUS_MAP_STRDUP|BUS_MAP_BOOLEAN_AS_BOOL,
+ &error,
+ NULL,
+ info);
+ if (r < 0)
+ return log_error_errno(r, "Failed to get unit properties: %s", bus_error_message(&error, r));
+
+ if (!streq_ptr(info->load_state, "loaded")) {
+
+ if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LOADED))
+ return -EMEDIUMTYPE;
+
+ if (streq_ptr(info->load_state, "not-found"))
+ log_error("Unit %s not found, cannot analyze.", name);
+ else if (streq_ptr(info->load_state, "masked"))
+ log_error("Unit %s is masked, cannot analyze.", name);
+ else
+ log_error("Unit %s not loaded properly, cannot analyze.", name);
+
+ return -EINVAL;
+ }
+
+ if (FLAGS_SET(flags, ANALYZE_SECURITY_ONLY_LONG_RUNNING) && streq_ptr(info->type, "oneshot"))
+ return -EMEDIUMTYPE;
+
+ if (info->private_devices ||
+ info->private_tmp ||
+ info->protect_control_groups ||
+ info->protect_kernel_tunables ||
+ info->protect_kernel_modules ||
+ !streq_ptr(info->protect_home, "no") ||
+ !streq_ptr(info->protect_system, "no") ||
+ info->root_image)
+ info->private_mounts = true;
+
+ if (info->protect_kernel_modules)
+ info->capability_bounding_set &= ~(UINT64_C(1) << CAP_SYS_MODULE);
+
+ if (info->private_devices)
+ info->capability_bounding_set &= ~((UINT64_C(1) << CAP_MKNOD) |
+ (UINT64_C(1) << CAP_SYS_RAWIO));
+
+ return 0;
+}
+
+static int analyze_security_one(sd_bus *bus, const char *name, Table* overview_table, AnalyzeSecurityFlags flags) {
+ _cleanup_(security_info_free) struct security_info info = {
+ .default_dependencies = true,
+ .capability_bounding_set = UINT64_MAX,
+ .restrict_namespaces = UINT64_MAX,
+ ._umask = 0002,
+ };
+ int r;
+
+ assert(bus);
+ assert(name);
+
+ r = acquire_security_info(bus, name, &info, flags);
+ if (r == -EMEDIUMTYPE) /* Ignore this one because not loaded or Type is oneshot */
+ return 0;
+ if (r < 0)
+ return r;
+
+ r = assess(&info, overview_table, flags);
+ if (r < 0)
+ return r;
+
+ return 0;
+}
+
+int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) {
+ _cleanup_(table_unrefp) Table *overview_table = NULL;
+ int ret = 0, r;
+
+ assert(bus);
+
+ if (strv_length(units) != 1) {
+ overview_table = table_new("UNIT", "EXPOSURE", "PREDICATE", "HAPPY");
+ if (!overview_table)
+ return log_oom();
+ }
+
+ if (strv_isempty(units)) {
+ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
+ _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL;
+ _cleanup_strv_free_ char **list = NULL;
+ size_t allocated = 0, n = 0;
+ char **i;
+
+ r = sd_bus_call_method(
+ bus,
+ "org.freedesktop.systemd1",
+ "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager",
+ "ListUnits",
+ &error, &reply,
+ NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
+
+ r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
+ if (r < 0)
+ return bus_log_parse_error(r);
+
+ for (;;) {
+ UnitInfo info;
+ char *copy = NULL;
+
+ r = bus_parse_unit_info(reply, &info);
+ if (r < 0)
+ return bus_log_parse_error(r);
+ if (r == 0)
+ break;
+
+ if (!endswith(info.id, ".service"))
+ continue;
+
+ if (!GREEDY_REALLOC(list, allocated, n+2))
+ return log_oom();
+
+ copy = strdup(info.id);
+ if (!copy)
+ return log_oom();
+
+ list[n++] = copy;
+ list[n] = NULL;
+ }
+
+ strv_sort(list);
+
+ flags |= ANALYZE_SECURITY_SHORT|ANALYZE_SECURITY_ONLY_LOADED|ANALYZE_SECURITY_ONLY_LONG_RUNNING;
+
+ STRV_FOREACH(i, list) {
+ r = analyze_security_one(bus, *i, overview_table, flags);
+ if (r < 0 && ret >= 0)
+ ret = r;
+ }
+
+ } else {
+ char **i;
+
+ STRV_FOREACH(i, units) {
+ _cleanup_free_ char *mangled = NULL, *instance = NULL;
+ const char *name;
+
+ if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT) && i != units) {
+ putc('\n', stdout);
+ fflush(stdout);
+ }
+
+ r = unit_name_mangle_with_suffix(*i, 0, ".service", &mangled);
+ if (r < 0)
+ return log_error_errno(r, "Failed to mangle unit name '%s': %m", *i);
+
+ if (!endswith(mangled, ".service")) {
+ log_error("Unit %s is not a service unit, refusing.", *i);
+ return -EINVAL;
+ }
+
+ if (unit_name_is_valid(mangled, UNIT_NAME_TEMPLATE)) {
+ r = unit_name_replace_instance(mangled, "test-instance", &instance);
+ if (r < 0)
+ return log_oom();
+
+ name = instance;
+ } else
+ name = mangled;
+
+ r = analyze_security_one(bus, name, overview_table, flags);
+ if (r < 0 && ret >= 0)
+ ret = r;
+ }
+ }
+
+ if (overview_table) {
+ if (!FLAGS_SET(flags, ANALYZE_SECURITY_SHORT)) {
+ putc('\n', stdout);
+ fflush(stdout);
+ }
+
+ r = table_print(overview_table, stdout);
+ if (r < 0)
+ return log_error_errno(r, "Failed to output table: %m");
+ }
+
+ return ret;
+}
diff --git a/src/analyze/analyze-security.h b/src/analyze/analyze-security.h
new file mode 100644
index 0000000000..c00ae7c80a
--- /dev/null
+++ b/src/analyze/analyze-security.h
@@ -0,0 +1,12 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+#pragma once
+
+#include "sd-bus.h"
+
+typedef enum AnalyzeSecurityFlags {
+ ANALYZE_SECURITY_SHORT = 1 << 0,
+ ANALYZE_SECURITY_ONLY_LOADED = 1 << 1,
+ ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2,
+} AnalyzeSecurityFlags;
+
+int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags);
diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c
index 62c62ca526..2b9cb65256 100644
--- a/src/analyze/analyze.c
+++ b/src/analyze/analyze.c
@@ -12,14 +12,15 @@
#include "sd-bus.h"
#include "alloc-util.h"
+#include "analyze-security.h"
#include "analyze-verify.h"
#include "bus-error.h"
#include "bus-unit-util.h"
#include "bus-util.h"
#include "calendarspec.h"
-#include "def.h"
#include "conf-files.h"
#include "copy.h"
+#include "def.h"
#include "fd-util.h"
#include "fileio.h"
#include "glob-util.h"
@@ -169,7 +170,7 @@ static int bus_get_uint64_property(sd_bus *bus, const char *path, const char *in
't', val);
if (r < 0)
- return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, -r));
+ return log_error_errno(r, "Failed to parse reply: %s", bus_error_message(&error, r));
return 0;
}
@@ -192,7 +193,7 @@ static int bus_get_unit_property_strv(sd_bus *bus, const char *path, const char
&error,
strv);
if (r < 0)
- return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, -r));
+ return log_error_errno(r, "Failed to get unit property %s: %s", property, bus_error_message(&error, r));
return 0;
}
@@ -355,7 +356,7 @@ static int acquire_time_data(sd_bus *bus, struct unit_times **out) {
&error, &reply,
NULL);
if (r < 0)
- return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
+ return log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r < 0)
@@ -978,7 +979,7 @@ static int list_dependencies(sd_bus *bus, const char *name) {
&reply,
"s");
if (r < 0)
- return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, -r));
+ return log_error_errno(r, "Failed to get ID: %s", bus_error_message(&error, r));
r = sd_bus_message_read(reply, "s", &id);
if (r < 0)
@@ -1238,7 +1239,7 @@ static int dot(int argc, char *argv[], void *userdata) {
&reply,
"");
if (r < 0)
- log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, -r));
+ log_error_errno(r, "Failed to list units: %s", bus_error_message(&error, r));
r = sd_bus_message_enter_container(reply, SD_BUS_TYPE_ARRAY, "(ssssssouso)");
if (r < 0)
@@ -1340,15 +1341,16 @@ static int dump(int argc, char *argv[], void *userdata) {
}
static int cat_config(int argc, char *argv[], void *userdata) {
- char **arg;
+ char **arg, **list;
int r;
(void) pager_open(arg_pager_flags);
- STRV_FOREACH(arg, argv + 1) {
+ list = strv_skip(argv, 1);
+ STRV_FOREACH(arg, list) {
const char *t = NULL;
- if (arg != argv + 1)
+ if (arg != list)
print_separator();
if (path_is_absolute(*arg)) {
@@ -1783,6 +1785,19 @@ static int do_verify(int argc, char *argv[], void *userdata) {
return verify_units(strv_skip(argv, 1), arg_scope, arg_man, arg_generators);
}
+static int do_security(int argc, char *argv[], void *userdata) {
+ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL;
+ int r;
+
+ r = acquire_bus(&bus, NULL);
+ if (r < 0)
+ return log_error_errno(r, "Failed to create bus connection: %m");
+
+ (void) pager_open(arg_pager_flags);
+
+ return analyze_security(bus, strv_skip(argv, 1), 0);
+}
+
static int help(int argc, char *argv[], void *userdata) {
_cleanup_free_ char *link = NULL;
int r;
@@ -1827,6 +1842,7 @@ static int help(int argc, char *argv[], void *userdata) {
" calendar SPEC... Validate repetitive calendar time events\n"
" service-watchdogs [BOOL] Get/set service watchdog state\n"
" timespan SPAN... Validate a time span\n"
+ " security [UNIT...] Analyze security of unit\n"
"\nSee the %s for details.\n"
, program_invocation_short_name
, link
@@ -2014,6 +2030,7 @@ static int run(int argc, char *argv[]) {
{ "calendar", 2, VERB_ANY, 0, test_calendar },
{ "service-watchdogs", VERB_ANY, 2, 0, service_watchdogs },
{ "timespan", 2, VERB_ANY, 0, dump_timespan },
+ { "security", VERB_ANY, VERB_ANY, 0, do_security },
{}
};
diff --git a/src/analyze/meson.build b/src/analyze/meson.build
index 3a69a259b1..4db4dfa552 100644
--- a/src/analyze/meson.build
+++ b/src/analyze/meson.build
@@ -4,4 +4,6 @@ systemd_analyze_sources = files('''
analyze.c
analyze-verify.c
analyze-verify.h
+ analyze-security.c
+ analyze-security.h
'''.split())
diff --git a/src/basic/locale-util.c b/src/basic/locale-util.c
index b2ef66392e..2225208cd2 100644
--- a/src/basic/locale-util.c
+++ b/src/basic/locale-util.c
@@ -16,6 +16,7 @@
#include "def.h"
#include "dirent-util.h"
+#include "env-util.h"
#include "fd-util.h"
#include "hashmap.h"
#include "locale-util.h"
@@ -347,6 +348,24 @@ bool keymap_is_valid(const char *name) {
return true;
}
+static bool emoji_enabled(void) {
+ static int cached_emoji_enabled = -1;
+
+ if (cached_emoji_enabled < 0) {
+ int val;
+
+ val = getenv_bool("SYSTEMD_EMOJI");
+ if (val < 0)
+ cached_emoji_enabled =
+ is_locale_utf8() &&
+ !STRPTR_IN_SET(getenv("TERM"), "dumb", "linux");
+ else
+ cached_emoji_enabled = val;
+ }
+
+ return cached_emoji_enabled;
+}
+
const char *special_glyph(SpecialGlyph code) {
/* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be
@@ -359,40 +378,56 @@ const char *special_glyph(SpecialGlyph code) {
static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
/* ASCII fallback */
[false] = {
- [TREE_VERTICAL] = "| ",
- [TREE_BRANCH] = "|-",
- [TREE_RIGHT] = "`-",
- [TREE_SPACE] = " ",
- [TRIANGULAR_BULLET] = ">",
- [BLACK_CIRCLE] = "*",
- [BULLET] = "*",
- [ARROW] = "->",
- [MDASH] = "-",
- [ELLIPSIS] = "...",
- [MU] = "u",
- [CHECK_MARK] = "+",
- [CROSS_MARK] = "-",
+ [TREE_VERTICAL] = "| ",
+ [TREE_BRANCH] = "|-",
+ [TREE_RIGHT] = "`-",
+ [TREE_SPACE] = " ",
+ [TRIANGULAR_BULLET] = ">",
+ [BLACK_CIRCLE] = "*",
+ [BULLET] = "*",
+ [ARROW] = "->",
+ [MDASH] = "-",
+ [ELLIPSIS] = "...",
+ [MU] = "u",
+ [CHECK_MARK] = "+",
+ [CROSS_MARK] = "-",
+ [ECSTATIC_SMILEY] = ":-]",
+ [HAPPY_SMILEY] = ":-}",
+ [SLIGHTLY_HAPPY_SMILEY] = ":-)",
+ [NEUTRAL_SMILEY] = ":-|",
+ [SLIGHTLY_UNHAPPY_SMILEY] = ":-(",
+ [UNHAPPY_SMILEY] = ":-{️",
+ [DEPRESSED_SMILEY] = ":-[",
},
/* UTF-8 */
[true] = {
- [TREE_VERTICAL] = "\342\224\202 ", /* │ */
- [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
- [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
- [TREE_SPACE] = " ", /* */
- [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
- [BLACK_CIRCLE] = "\342\227\217", /* ● */
- [BULLET] = "\342\200\242", /* • */
- [ARROW] = "\342\206\222", /* → */
- [MDASH] = "\342\200\223", /* – */
- [ELLIPSIS] = "\342\200\246", /* … */
- [MU] = "\316\274", /* μ */
- [CHECK_MARK] = "\342\234\223", /* ✓ */
- [CROSS_MARK] = "\342\234\227", /* ✗ */
+ [TREE_VERTICAL] = "\342\224\202 ", /* │ */
+ [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
+ [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
+ [TREE_SPACE] = " ", /* */
+ [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
+ [BLACK_CIRCLE] = "\342\227\217", /* ● */
+ [BULLET] = "\342\200\242", /* • */
+ [ARROW] = "\342\206\222", /* → */
+ [MDASH] = "\342\200\223", /* – */
+ [ELLIPSIS] = "\342\200\246", /* … */
+ [MU] = "\316\274", /* μ */
+ [CHECK_MARK] = "\342\234\223", /* ✓ */
+ [CROSS_MARK] = "\342\234\227", /* ✗ */
+ [ECSTATIC_SMILEY] = "\360\237\230\207", /* 😇 */
+ [HAPPY_SMILEY] = "\360\237\230\200", /* 😀 */
+ [SLIGHTLY_HAPPY_SMILEY] = "\360\237\231\202", /* 🙂 */
+ [NEUTRAL_SMILEY] = "\360\237\230\220", /* 😐 */
+ [SLIGHTLY_UNHAPPY_SMILEY] = "\360\237\231\201", /* 🙁 */
+ [UNHAPPY_SMILEY] = "\360\237\230\250", /* 😨️️ */
+ [DEPRESSED_SMILEY] = "\360\237\244\242", /* 🤢 */
},
};
- return draw_table[is_locale_utf8()][code];
+ assert(code < _SPECIAL_GLYPH_MAX);
+
+ return draw_table[code >= _SPECIAL_GLYPH_FIRST_SMILEY ? emoji_enabled() : is_locale_utf8()][code];
}
void locale_variables_free(char*l[_VARIABLE_LC_MAX]) {
diff --git a/src/basic/locale-util.h b/src/basic/locale-util.h
index 2aa2bef8c5..8f4ce5485d 100644
--- a/src/basic/locale-util.h
+++ b/src/basic/locale-util.h
@@ -52,6 +52,14 @@ typedef enum {
MU,
CHECK_MARK,
CROSS_MARK,
+ _SPECIAL_GLYPH_FIRST_SMILEY,
+ ECSTATIC_SMILEY = _SPECIAL_GLYPH_FIRST_SMILEY,
+ HAPPY_SMILEY,
+ SLIGHTLY_HAPPY_SMILEY,
+ NEUTRAL_SMILEY,
+ SLIGHTLY_UNHAPPY_SMILEY,
+ UNHAPPY_SMILEY,
+ DEPRESSED_SMILEY,
_SPECIAL_GLYPH_MAX
} SpecialGlyph;
diff --git a/src/basic/macro.h b/src/basic/macro.h
index 0a258cc614..f54f13e4fe 100644
--- a/src/basic/macro.h
+++ b/src/basic/macro.h
@@ -246,11 +246,12 @@ static inline unsigned long ALIGN_POWER2(unsigned long u) {
* computation should be possible in the given type. Therefore, we use
* [x / y + !!(x % y)]. Note that on "Real CPUs" a division returns both the
* quotient and the remainder, so both should be equally fast. */
-#define DIV_ROUND_UP(_x, _y) \
+#define DIV_ROUND_UP(x, y) __DIV_ROUND_UP(UNIQ, (x), UNIQ, (y))
+#define __DIV_ROUND_UP(xq, x, yq, y) \
({ \
- const typeof(_x) __x = (_x); \
- const typeof(_y) __y = (_y); \
- (__x / __y + !!(__x % __y)); \
+ const typeof(x) UNIQ_T(X, xq) = (x); \
+ const typeof(y) UNIQ_T(Y, yq) = (y); \
+ (UNIQ_T(X, xq) / UNIQ_T(Y, yq) + !!(UNIQ_T(X, xq) % UNIQ_T(Y, yq))); \
})
#ifdef __COVERITY__
diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c
index 718357e290..5b4e94c134 100644
--- a/src/basic/parse-util.c
+++ b/src/basic/parse-util.c
@@ -20,7 +20,8 @@
#include "string-util.h"
int parse_boolean(const char *v) {
- assert(v);
+ if (!v)
+ return -EINVAL;
if (streq(v, "1") || strcaseeq(v, "yes") || strcaseeq(v, "y") || strcaseeq(v, "true") || strcaseeq(v, "t") || strcaseeq(v, "on"))
return 1;
diff --git a/src/shared/format-table.c b/src/shared/format-table.c
index 10e15c9d70..e685efea0e 100644
--- a/src/shared/format-table.c
+++ b/src/shared/format-table.c
@@ -9,6 +9,7 @@
#include "gunicode.h"
#include "pager.h"
#include "parse-util.h"
+#include "pretty-print.h"
#include "string-util.h"
#include "terminal-util.h"
#include "time-util.h"
@@ -58,6 +59,7 @@ typedef struct TableData {
unsigned align_percent; /* 0 … 100, where to pad with spaces when expanding is needed. 0: left-aligned, 100: right-aligned */
const char *color; /* ANSI color string to use for this cell. When written to terminal should not move cursor. Will automatically be reset after the cell */
+ char *url; /* A URL to use for a clickable hyperlink */
char *formatted; /* A cached textual representation of the cell data, before ellipsation/alignment */
union {
@@ -68,6 +70,8 @@ typedef struct TableData {
uint64_t size;
char string[0];
uint32_t uint32;
+ uint64_t uint64;
+ int percent; /* we use 'int' as datatype for percent values in order to match the result of parse_percent() */
/* … add more here as we start supporting more cell data types … */
};
} TableData;
@@ -103,6 +107,8 @@ struct Table {
size_t *sort_map; /* The columns to order rows by, in order of preference. */
size_t n_sort_map;
+
+ bool *reverse_map;
};
Table *table_new_raw(size_t n_columns) {
@@ -175,6 +181,8 @@ static TableData *table_data_free(TableData *d) {
assert(d);
free(d->formatted);
+ free(d->url);
+
return mfree(d);
}
@@ -193,6 +201,7 @@ Table *table_unref(Table *t) {
free(t->data);
free(t->display_map);
free(t->sort_map);
+ free(t->reverse_map);
return mfree(t);
}
@@ -215,11 +224,15 @@ static size_t table_data_size(TableDataType type, const void *data) {
return sizeof(usec_t);
case TABLE_SIZE:
+ case TABLE_UINT64:
return sizeof(uint64_t);
case TABLE_UINT32:
return sizeof(uint32_t);
+ case TABLE_PERCENT:
+ return sizeof(int);
+
default:
assert_not_reached("Uh? Unexpected cell type");
}
@@ -262,7 +275,7 @@ static bool table_data_matches(
if (k != l)
return false;
- return memcmp(data, d->data, l) == 0;
+ return memcmp_safe(data, d->data, l) == 0;
}
static TableData *table_data_new(
@@ -376,6 +389,7 @@ int table_dup_cell(Table *t, TableCell *cell) {
}
static int table_dedup_cell(Table *t, TableCell *cell) {
+ _cleanup_free_ char *curl = NULL;
TableData *nd, *od;
size_t i;
@@ -394,10 +408,26 @@ static int table_dedup_cell(Table *t, TableCell *cell) {
assert(od->n_ref > 1);
- nd = table_data_new(od->type, od->data, od->minimum_width, od->maximum_width, od->weight, od->align_percent, od->ellipsize_percent);
+ if (od->url) {
+ curl = strdup(od->url);
+ if (!curl)
+ return -ENOMEM;
+ }
+
+ nd = table_data_new(
+ od->type,
+ od->data,
+ od->minimum_width,
+ od->maximum_width,
+ od->weight,
+ od->align_percent,
+ od->ellipsize_percent);
if (!nd)
return -ENOMEM;
+ nd->color = od->color;
+ nd->url = TAKE_PTR(curl);
+
table_data_unref(od);
t->data[i] = nd;
@@ -524,6 +554,66 @@ int table_set_color(Table *t, TableCell *cell, const char *color) {
return 0;
}
+int table_set_url(Table *t, TableCell *cell, const char *url) {
+ _cleanup_free_ char *copy = NULL;
+ int r;
+
+ assert(t);
+ assert(cell);
+
+ if (url) {
+ copy = strdup(url);
+ if (!copy)
+ return -ENOMEM;
+ }
+
+ r = table_dedup_cell(t, cell);
+ if (r < 0)
+ return r;
+
+ return free_and_replace(table_get_data(t, cell)->url, copy);
+}
+
+int table_update(Table *t, TableCell *cell, TableDataType type, const void *data) {
+ _cleanup_free_ char *curl = NULL;
+ TableData *nd, *od;
+ size_t i;
+
+ assert(t);
+ assert(cell);
+
+ i = TABLE_CELL_TO_INDEX(cell);
+ if (i >= t->n_cells)
+ return -ENXIO;
+
+ assert_se(od = t->data[i]);
+
+ if (od->url) {
+ curl = strdup(od->url);
+ if (!curl)
+ return -ENOMEM;
+ }
+
+ nd = table_data_new(
+ type,
+ data,
+ od->minimum_width,
+ od->maximum_width,
+ od->weight,
+ od->align_percent,
+ od->ellipsize_percent);
+ if (!nd)
+ return -ENOMEM;
+
+ nd->color = od->color;
+ nd->url = TAKE_PTR(curl);
+
+ table_data_unref(od);
+ t->data[i] = nd;
+
+ return 0;
+}
+
int table_add_many_internal(Table *t, TableDataType first_type, ...) {
TableDataType type;
va_list ap;
@@ -542,6 +632,8 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
uint64_t size;
usec_t usec;
uint32_t uint32;
+ uint64_t uint64;
+ int percent;
bool b;
} buffer;
@@ -576,6 +668,16 @@ int table_add_many_internal(Table *t, TableDataType first_type, ...) {
data = &buffer.uint32;
break;
+ case TABLE_UINT64:
+ buffer.uint64 = va_arg(ap, uint64_t);
+ data = &buffer.uint64;
+ break;
+
+ case TABLE_PERCENT:
+ buffer.percent = va_arg(ap, int);
+ data = &buffer.percent;
+ break;
+
case _TABLE_DATA_TYPE_MAX:
/* Used as end marker */
va_end(ap);
@@ -699,6 +801,12 @@ static int cell_data_compare(TableData *a, size_t index_a, TableData *b, size_t
case TABLE_UINT32:
return CMP(a->uint32, b->uint32);
+ case TABLE_UINT64:
+ return CMP(a->uint64, b->uint64);
+
+ case TABLE_PERCENT:
+ return CMP(a->percent, b->percent);
+
default:
;
}
@@ -732,7 +840,7 @@ static int table_data_compare(const size_t *a, const size_t *b, Table *t) {
r = cell_data_compare(d, *a, dd, *b);
if (r != 0)
- return r;
+ return t->reverse_map && t->reverse_map[t->sort_map[i]] ? -r : r;
}
/* Order identical lines by the order there were originally added in */
@@ -809,6 +917,30 @@ static const char *table_data_format(TableData *d) {
break;
}
+ case TABLE_UINT64: {
+ _cleanup_free_ char *p;
+
+ p = new(char, DECIMAL_STR_WIDTH(d->uint64) + 1);
+ if (!p)
+ return NULL;
+
+ sprintf(p, "%" PRIu64, d->uint64);
+ d->formatted = TAKE_PTR(p);
+ break;
+ }
+
+ case TABLE_PERCENT: {
+ _cleanup_free_ char *p;
+
+ p = new(char, DECIMAL_STR_WIDTH(d->percent) + 2);
+ if (!p)
+ return NULL;
+
+ sprintf(p, "%i%%" , d->percent);
+ d->formatted = TAKE_PTR(p);
+ break;
+ }
+
default:
assert_not_reached("Unexpected type?");
}
@@ -838,11 +970,13 @@ static int table_data_requested_width(TableData *d, size_t *ret) {
return 0;
}
-static char *align_string_mem(const char *str, size_t new_length, unsigned percent) {
- size_t w = 0, space, lspace, old_length;
+static char *align_string_mem(const char *str, const char *url, size_t new_length, unsigned percent) {
+ size_t w = 0, space, lspace, old_length, clickable_length;
+ _cleanup_free_ char *clickable = NULL;
const char *p;
char *ret;
size_t i;
+ int r;
/* As with ellipsize_mem(), 'old_length' is a byte size while 'new_length' is a width in character cells */
@@ -851,6 +985,15 @@ static char *align_string_mem(const char *str, size_t new_length, unsigned perce
old_length = strlen(str);
+ if (url) {
+ r = terminal_urlify(url, str, &clickable);
+ if (r < 0)
+ return NULL;
+
+ clickable_length = strlen(clickable);
+ } else
+ clickable_length = old_length;
+
/* Determine current width on screen */
p = str;
while (p < str + old_length) {
@@ -867,23 +1010,23 @@ static char *align_string_mem(const char *str, size_t new_length, unsigned perce
/* Already wider than the target, if so, don't do anything */
if (w >= new_length)
- return strndup(str, old_length);
+ return clickable ? TAKE_PTR(clickable) : strdup(str);
/* How much spaces shall we add? An how much on the left side? */
space = new_length - w;
lspace = space * percent / 100U;
- ret = new(char, space + old_length + 1);
+ ret = new(char, space + clickable_length + 1);
if (!ret)
return NULL;
for (i = 0; i < lspace; i++)
ret[i] = ' ';
- memcpy(ret + lspace, str, old_length);
- for (i = lspace + old_length; i < space + old_length; i++)
+ memcpy(ret + lspace, clickable ?: str, clickable_length);
+ for (i = lspace + clickable_length; i < space + clickable_length; i++)
ret[i] = ' ';
- ret[space + old_length] = 0;
+ ret[space + clickable_length] = 0;
return ret;
}
@@ -1136,23 +1279,41 @@ int table_print(Table *t, FILE *f) {
} else if (l < width[j]) {
/* Field is shorter than allocated space. Let's align with spaces */
- buffer = align_string_mem(field, width[j], d->align_percent);
+ buffer = align_string_mem(field, d->url, width[j], d->align_percent);
if (!buffer)
return -ENOMEM;
field = buffer;
}
+ if (l >= width[j] && d->url) {
+ _cleanup_free_ char *clickable = NULL;
+
+ r = terminal_urlify(d->url, field, &clickable);
+ if (r < 0)
+ return r;
+
+ free_and_replace(buffer, clickable);
+ field = buffer;
+ }
+
+ if (row == t->data) /* underline header line fully, including the column separator */
+ fputs(ansi_underline(), f);
+
if (j > 0)
fputc(' ', f); /* column separator */
- if (d->color)
+ if (d->color && colors_enabled()) {
+ if (row == t->data) /* first undo header underliner */
+ fputs(ANSI_NORMAL, f);
+
fputs(d->color, f);
+ }
fputs(field, f);
- if (d->color)
- fputs(ansi_normal(), f);
+ if (colors_enabled() && (d->color || row == t->data))
+ fputs(ANSI_NORMAL, f);
}
fputc('\n', f);
@@ -1199,3 +1360,57 @@ size_t table_get_columns(Table *t) {
assert(t->n_columns > 0);
return t->n_columns;
}
+
+int table_set_reverse(Table *t, size_t column, bool b) {
+ assert(t);
+ assert(column < t->n_columns);
+
+ if (!t->reverse_map) {
+ if (!b)
+ return 0;
+
+ t->reverse_map = new0(bool, t->n_columns);
+ if (!t->reverse_map)
+ return -ENOMEM;
+ }
+
+ t->reverse_map[column] = b;
+ return 0;
+}
+
+TableCell *table_get_cell(Table *t, size_t row, size_t column) {
+ size_t i;
+
+ assert(t);
+
+ if (column >= t->n_columns)
+ return NULL;
+
+ i = row * t->n_columns + column;
+ if (i >= t->n_cells)
+ return NULL;
+
+ return TABLE_INDEX_TO_CELL(i);
+}
+
+const void *table_get(Table *t, TableCell *cell) {
+ TableData *d;
+
+ assert(t);
+
+ d = table_get_data(t, cell);
+ if (!d)
+ return NULL;
+
+ return d->data;
+}
+
+const void* table_get_at(Table *t, size_t row, size_t column) {
+ TableCell *cell;
+
+ cell = table_get_cell(t, row, column);
+ if (!cell)
+ return NULL;
+
+ return table_get(t, cell);
+}
diff --git a/src/shared/format-table.h b/src/shared/format-table.h
index 6dc2d16052..a2bb2e0846 100644
--- a/src/shared/format-table.h
+++ b/src/shared/format-table.h
@@ -15,6 +15,8 @@ typedef enum TableDataType {
TABLE_TIMESPAN,
TABLE_SIZE,
TABLE_UINT32,
+ TABLE_UINT64,
+ TABLE_PERCENT,
_TABLE_DATA_TYPE_MAX,
_TABLE_DATA_TYPE_INVALID = -1,
} TableDataType;
@@ -42,6 +44,9 @@ int table_set_weight(Table *t, TableCell *cell, unsigned weight);
int table_set_align_percent(Table *t, TableCell *cell, unsigned percent);
int table_set_ellipsize_percent(Table *t, TableCell *cell, unsigned percent);
int table_set_color(Table *t, TableCell *cell, const char *color);
+int table_set_url(Table *t, TableCell *cell, const char *color);
+
+int table_update(Table *t, TableCell *cell, TableDataType type, const void *data);
int table_add_many_internal(Table *t, TableDataType first_type, ...);
#define table_add_many(t, ...) table_add_many_internal(t, __VA_ARGS__, _TABLE_DATA_TYPE_MAX)
@@ -50,6 +55,7 @@ void table_set_header(Table *table, bool b);
void table_set_width(Table *t, size_t width);
int table_set_display(Table *t, size_t first_column, ...);
int table_set_sort(Table *t, size_t first_column, ...);
+int table_set_reverse(Table *t, size_t column, bool b);
int table_print(Table *t, FILE *f);
int table_format(Table *t, char **ret);
@@ -60,3 +66,8 @@ static inline TableCell* TABLE_HEADER_CELL(size_t i) {
size_t table_get_rows(Table *t);
size_t table_get_columns(Table *t);
+
+TableCell *table_get_cell(Table *t, size_t row, size_t column);
+
+const void *table_get(Table *t, TableCell *cell);
+const void *table_get_at(Table *t, size_t row, size_t column);
diff --git a/src/shared/pretty-print.c b/src/shared/pretty-print.c
index 1086018645..de6274a3da 100644
--- a/src/shared/pretty-print.c
+++ b/src/shared/pretty-print.c
@@ -60,10 +60,38 @@ int terminal_urlify(const char *url, const char *text, char **ret) {
return 0;
}
-int terminal_urlify_path(const char *path, const char *text, char **ret) {
+int file_url_from_path(const char *path, char **ret) {
_cleanup_free_ char *absolute = NULL;
struct utsname u;
- const char *url;
+ char *url = NULL;
+ int r;
+
+ if (uname(&u) < 0)
+ return -errno;
+
+ if (!path_is_absolute(path)) {
+ r = path_make_absolute_cwd(path, &absolute);
+ if (r < 0)
+ return r;
+
+ path = absolute;
+ }
+
+ /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
+ * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
+ * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
+ * careful with validating the strings either. */
+
+ url = strjoin("file://", u.nodename, path);
+ if (!url)
+ return -ENOMEM;
+
+ *ret = url;
+ return 0;
+}
+
+int terminal_urlify_path(const char *path, const char *text, char **ret) {
+ _cleanup_free_ char *url = NULL;
int r;
assert(path);
@@ -88,23 +116,9 @@ int terminal_urlify_path(const char *path, const char *text, char **ret) {
return 0;
}
- if (uname(&u) < 0)
- return -errno;
-
- if (!path_is_absolute(path)) {
- r = path_make_absolute_cwd(path, &absolute);
- if (r < 0)
- return r;
-
- path = absolute;
- }
-
- /* As suggested by https://gist.github.com/egmontkob/eb114294efbcd5adb1944c9f3cb5feda, let's include the local
- * hostname here. Note that we don't use gethostname_malloc() or gethostname_strict() since we are interested
- * in the raw string the kernel has set, whatever it may be, under the assumption that terminals are not overly
- * careful with validating the strings either. */
-
- url = strjoina("file://", u.nodename, path);
+ r = file_url_from_path(path, &url);
+ if (r < 0)
+ return r;
return terminal_urlify(url, text, ret);
}
diff --git a/src/shared/pretty-print.h b/src/shared/pretty-print.h
index cf9a70dd9e..12ab9acf58 100644
--- a/src/shared/pretty-print.h
+++ b/src/shared/pretty-print.h
@@ -3,6 +3,8 @@
void print_separator(void);
+int file_url_from_path(const char *path, char **ret);
+
int terminal_urlify(const char *url, const char *text, char **ret);
int terminal_urlify_path(const char *path, const char *text, char **ret);
int terminal_urlify_man(const char *page, const char *section, char **ret);
diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c
index 67ccd43c3f..42d6dd2a94 100644
--- a/src/shared/seccomp-util.c
+++ b/src/shared/seccomp-util.c
@@ -371,8 +371,6 @@ const SyscallFilterSet syscall_filter_sets[_SYSCALL_FILTER_SET_MAX] = {
.value =
"lookup_dcookie\0"
"perf_event_open\0"
- "process_vm_readv\0"
- "process_vm_writev\0"
"ptrace\0"
"rtas\0"
#ifdef __NR_s390_runtime_instr
diff --git a/src/test/test-locale-util.c b/src/test/test-locale-util.c
index a1d4307eb5..48994547ac 100644
--- a/src/test/test-locale-util.c
+++ b/src/test/test-locale-util.c
@@ -65,7 +65,7 @@ static void test_keymaps(void) {
#define dump_glyph(x) log_info(STRINGIFY(x) ": %s", special_glyph(x))
static void dump_special_glyphs(void) {
- assert_cc(CROSS_MARK + 1 == _SPECIAL_GLYPH_MAX);
+ assert_cc(DEPRESSED_SMILEY + 1 == _SPECIAL_GLYPH_MAX);
log_info("/* %s */", __func__);
@@ -84,6 +84,13 @@ static void dump_special_glyphs(void) {
dump_glyph(MU);
dump_glyph(CHECK_MARK);
dump_glyph(CROSS_MARK);
+ dump_glyph(ECSTATIC_SMILEY);
+ dump_glyph(HAPPY_SMILEY);
+ dump_glyph(SLIGHTLY_HAPPY_SMILEY);
+ dump_glyph(NEUTRAL_SMILEY);
+ dump_glyph(SLIGHTLY_UNHAPPY_SMILEY);
+ dump_glyph(UNHAPPY_SMILEY);
+ dump_glyph(DEPRESSED_SMILEY);
}
int main(int argc, char *argv[]) {