From ecfd082b7111c633f402cde184f6499105cff513 Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Mon, 23 Aug 2021 14:20:10 -0700 Subject: [PATCH 1/3] systemd-analyze: add new 'security' option to allow user to choose custom requirements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A new option --security-policy= is added to work with the 'security' verb in order to enable users to create and pass in a JSON file consisting of user defined requirements against which to compare the specified unit file(s). These requirements then serve as the measure of security threats for the file instead of the initial hard coded set of requirements that the 'security' verb of systemd-analyze relied on. Example Run: A snapshot of the user defined testfile.json file is shown below instead of the complete file for readability purposes. { "PrivateDevices": {"description_good": "Service has no access to hardware devices", "description_bad": "Service potentially has access to hardware devices", "weight": 1000, "range": 1 }, "PrivateMounts": {"description_good": "Service cannot install system mounts", "description_bad": "Service may install system mounts", "weight": 1000, "range": 1 }, "PrivateNetwork": {"description_good": "Service has no access to the host's network", "description_bad": "Service has access to the host's network", "weight": 2500, "range": 1 }, "PrivateTmp": {"description_good": "Service has no access to other software's temporary files", "description_bad": "Service has access to other software's temporary files", "weight": 1000, "range": 1 }, "PrivateUsers": {"description_good": "Service does not have access to other users", "description_bad": "Service has access to other users", "weight": 1000, "range": 1 } } 1. I created the jsontest.service file in order to test the --security-policy= option as follows: maanya-goenka@debian:~/systemd (custom-security)$ cat<jsontest.service > [Service] > ExecStart = echo hello > PrivateNetwork = yes > PrivateDevices = yes > PrivateMounts = yes > EOF The security analysis table outputted below has been truncated to include only the first few lines for readability. maanya-goenka@debian:~/systemd (custom-security)$ sudo build/systemd-analyze security --root= --offline=true --security-policy=src/analyze/testfile.json jsontest.service /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. NAME DESCRIPTION ✓ PrivateNetwork Service has no access to the host's network ✗ UserOrDynamicUser Service runs as root user ✗ CapabilityBoundingSet_CAP_SET_UID_GID_PCAP Service may change UID/GID identities/capabilities ✓ PrivateMounts Service cannot install system mounts ✓ PrivateDevices Service has no access to hardware devices → Overall exposure level for jsontest.service: 8.3 EXPOSED 🙁 maanya-goenka@debian:~/systemd (custom-security)$ echo $? 0 2. In order to ensure that the JSON data was actually being correctly parsed, I made some changes to the JSON file, specifically to the id "PrivateNetwork" as follows: Before: -------- "PrivateNetwork": {"description_good": "Service has no access to the host's network", "description_bad": "Service has access to the host's network", "weight": 2500, "range": 1 } After: -------- "PrivateNetwork": {"description_good": "Service runs without access to host network", "description_bad": "Service has access to the host's network", "weight": 6000, "range": 1 } As expected, the new description for the description_good field of the Private Network id was updated in the analysis table outputted below and the overall exposure level of the unit file decreased because the weight assigned to 'Private Network' (which is set to yes) increased from 2500 to 6000. maanya-goenka@debian:~/systemd (custom-security)$ sudo build/systemd-analyze security --root= --offline=true --security-policy=src/analyze/testfile.json jsontest.service /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. NAME DESCRIPTION ✓ PrivateNetwork Service runs without access to the host's network ✗ UserOrDynamicUser Service runs as root user ✗ CapabilityBoundingSet_CAP_SET_UID_GID_PCAP Service may change UID/GID identities/capabilities ✓ PrivateMounts Service cannot install system mounts ✓ PrivateDevices Service has no access to hardware devices → Overall exposure level for jsontest.service: 7.8 EXPOSED 🙁 maanya-goenka@debian:~/systemd (custom-security)$ echo $? 0 3. When paired with security's --threshold= option, systemd-analyze exits with a non-zero error status indicating that the overall exposure level for the unit file (=78) is greater than the set threshold (=70). The same jsontest.service file is used for the demo run below: maanya-goenka@debian:~/systemd (custom-security)$ sudo build/systemd-analyze security --root= --offline=true --security-policy=src/analyze/testfile.json --threshold=70 jsontest.service /usr/lib/systemd/system/plymouth-start.service:15: Unit configured to use KillMode=none. This is unsafe, as it disables systemd's process lifecycle management for the service. Please update your service to use a safer KillMode=, such as 'mixed' or 'control-group'. Support for KillMode=none is deprecated and will eventually be removed. /usr/lib/systemd/system/gdm.service:30: Standard output type syslog is obsolete, automatically updating to journal. Please update your unit file, and consider removing the setting altogether. /usr/lib/systemd/system/dbus.socket:5: ListenStream= references a path below legacy directory /var/run/, updating /var/run/dbus/system_bus_socket → /run/dbus/system_bus_socket; please update the unit file accordingly. NAME DESCRIPTION ✓ PrivateNetwork Service runs without access to host network ✗ UserOrDynamicUser Service runs as root user ✗ CapabilityBoundingSet_CAP_SET_UID_GID_PCAP Service may change UID/GID identities/capabilities ✓ PrivateMounts Service cannot install system mounts ✓ PrivateDevices Service has no access to hardware devices → Overall exposure level for jsontest.service: 7.8 EXPOSED 🙁 maanya-goenka@debian:~/systemd (custom-security)$ echo $? 1 new option --- man/systemd-analyze.xml | 317 ++++++++++++++++++++++++++ shell-completion/bash/systemd-analyze | 2 +- shell-completion/zsh/_systemd-analyze | 1 + src/analyze/analyze-security.c | 12 +- src/analyze/analyze-security.h | 13 +- src/analyze/analyze.c | 48 +++- 6 files changed, 387 insertions(+), 6 deletions(-) diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index a2f9154791..932218d80e 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -792,6 +792,323 @@ Service b@0.service not loaded, b.socket cannot be started. as well and its default value is 100. + + + + With security, allow the user to define a custom set of + requirements formatted as a JSON file against which to compare the specified unit file(s) + and determine their overall exposure level to security threats. + + + Accepted Assessment Test Identifiers + + + + + + Assessment Test Identifier + + + + + UserOrDynamicUser + + + SupplementaryGroups + + + PrivateMounts + + + PrivateDevices + + + PrivateTmp + + + PrivateNetwork + + + PrivateUsers + + + ProtectControlGroups + + + ProtectKernelModules + + + ProtectKernelTunables + + + ProtectKernelLogs + + + ProtectClock + + + ProtectHome + + + ProtectHostname + + + ProtectSystem + + + RootDirectoryOrRootImage + + + LockPersonality + + + MemoryDenyWriteExecute + + + NoNewPrivileges + + + CapabilityBoundingSet_CAP_SYS_ADMIN + + + CapabilityBoundingSet_CAP_SET_UID_GID_PCAP + + + CapabilityBoundingSet_CAP_SYS_PTRACE + + + CapabilityBoundingSet_CAP_SYS_TIME + + + CapabilityBoundingSet_CAP_NET_ADMIN + + + CapabilityBoundingSet_CAP_SYS_RAWIO + + + CapabilityBoundingSet_CAP_SYS_MODULE + + + CapabilityBoundingSet_CAP_AUDIT + + + CapabilityBoundingSet_CAP_SYSLOG + + + CapabilityBoundingSet_CAP_SYS_NICE_RESOURCE + + + CapabilityBoundingSet_CAP_MKNOD + + + CapabilityBoundingSet_CAP_CHOWN_FSETID_SETFCAP + + + CapabilityBoundingSet_CAP_DAC_FOWNER_IPC_OWNER + + + CapabilityBoundingSet_CAP_KILL + + + CapabilityBoundingSet_CAP_NET_BIND_SERVICE_BROADCAST_RAW + + + CapabilityBoundingSet_CAP_SYS_BOOT + + + CapabilityBoundingSet_CAP_MAC + + + CapabilityBoundingSet_CAP_LINUX_IMMUTABLE + + + CapabilityBoundingSet_CAP_IPC_LOCK + + + CapabilityBoundingSet_CAP_SYS_CHROOT + + + CapabilityBoundingSet_CAP_BLOCK_SUSPEND + + + CapabilityBoundingSet_CAP_WAKE_ALARM + + + CapabilityBoundingSet_CAP_LEASE + + + CapabilityBoundingSet_CAP_SYS_TTY_CONFIG + + + UMask + + + KeyringMode + + + ProtectProc + + + ProcSubset + + + NotifyAccess + + + RemoveIPC + + + Delegate + + + RestrictRealtime + + + RestrictSUIDSGID + + + RestrictNamespaces_CLONE_NEWUSER + + + RestrictNamespaces_CLONE_NEWNS + + + RestrictNamespaces_CLONE_NEWIPC + + + RestrictNamespaces_CLONE_NEWPID + + + RestrictNamespaces_CLONE_NEWCGROUP + + + RestrictNamespaces_CLONE_NEWUTS + + + RestrictNamespaces_CLONE_NEWNET + + + RestrictAddressFamilies_AF_INET_INET6 + + + RestrictAddressFamilies_AF_UNIX + + + RestrictAddressFamilies_AF_NETLINK + + + RestrictAddressFamilies_AF_PACKET + + + RestrictAddressFamilies_OTHER + + + SystemCallArchitectures + + + SystemCallFilter_swap + + + SystemCallFilter_obsolete + + + SystemCallFilter_clock + + + SystemCallFilter_cpu_emulation + + + SystemCallFilter_debug + + + SystemCallFilter_mount + + + SystemCallFilter_module + + + SystemCallFilter_raw_io + + + SystemCallFilter_reboot + + + SystemCallFilter_privileged + + + SystemCallFilter_resources + + + IPAddressDeny + + + DeviceAllow + + + AmbientCapabilities + + + +
+ + + JSON Policy + The JSON file passed as a path parameter to + has a top-level JSON object, with keys being the assessment test identifiers mentioned + above. The values in the file should be JSON objects with one or more of the + following fields: description_na (string), description_good (string), description_bad + (string), weight (unsigned integer), and range (unsigned integer). If any of these fields + corresponding to a specific id of the unit file is missing from the JSON object, the + default built-in field value corresponding to that same id is used for security analysis + as default. The weight and range fields are used in determining the overall exposure level + of the unit files so by allowing users to manipulate these fields, 'security' gives them + the option to decide for themself which ids are more important and hence, should have a greater + effect on the exposure level. + + + { + "PrivateDevices": + { + "description_good": "Service has no access to hardware devices", + "description_bad": "Service potentially has access to hardware devices", + "weight": 1000, + "range": 1 + }, + "PrivateMounts": + { + "description_good": "Service cannot install system mounts", + "description_bad": "Service may install system mounts", + "weight": 1000, + "range": 1 + }, + "PrivateNetwork": + { + "description_good": "Service has no access to the host's network", + "description_bad": "Service has access to the host's network", + "weight": 2500, + "range": 1 + }, + "PrivateTmp": + { + "description_good": "Service has no access to other software's temporary files", + "description_bad": "Service has access to other software's temporary files", + "weight": 1000, + "range": 1 + }, + "PrivateUsers": + { + "description_good": "Service does not have access to other users", + "description_bad": "Service has access to other users", + "weight": 1000, + "range": 1 + } + } + + +
+
+ + diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index 6f33d53cfc..07d069a6d7 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -144,7 +144,7 @@ _systemd_analyze() { elif __contains_word "$verb" ${VERBS[SECURITY]}; then if [[ $cur = -* ]]; then - comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold' + comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold --security-policy' else if __contains_word "--user" ${COMP_WORDS[*]}; then mode=--user diff --git a/shell-completion/zsh/_systemd-analyze b/shell-completion/zsh/_systemd-analyze index f91357cb61..3b77c3b938 100644 --- a/shell-completion/zsh/_systemd-analyze +++ b/shell-completion/zsh/_systemd-analyze @@ -92,6 +92,7 @@ _arguments \ '--recursive-errors=[When verifying a unit, control dependency verification]:MODE' \ '--offline=[Perform a security review of the specified unit file(s)]:BOOL' \ '--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \ + '--security-policy=[Allow user to use customized requirements to compare unit file(s) against]: PATH' \ '--no-pager[Do not pipe output into a pager]' \ '--man=[Do (not) check for existence of man pages]:boolean:(1 0)' \ '--order[When generating graph for dot, show only order]' \ diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 24500e3a5b..6c886e731c 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -2481,8 +2481,16 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c return r; } -int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_man, bool run_generators, - bool offline, unsigned threshold, const char *root, AnalyzeSecurityFlags flags) { +int analyze_security(sd_bus *bus, + char **units, + JsonVariant *policy, + UnitFileScope scope, + bool check_man, + bool run_generators, + bool offline, + unsigned threshold, + const char *root, + AnalyzeSecurityFlags flags) { _cleanup_(table_unrefp) Table *overview_table = NULL; int ret = 0, r; diff --git a/src/analyze/analyze-security.h b/src/analyze/analyze-security.h index 57a93afbef..8ad5a689f5 100644 --- a/src/analyze/analyze-security.h +++ b/src/analyze/analyze-security.h @@ -5,6 +5,7 @@ #include "sd-bus.h" +#include "json.h" #include "unit-file.h" typedef enum AnalyzeSecurityFlags { @@ -13,5 +14,13 @@ typedef enum AnalyzeSecurityFlags { ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2, } AnalyzeSecurityFlags; -int analyze_security(sd_bus *bus, char **units, UnitFileScope scope, bool check_man, bool run_generators, - bool offline, unsigned threshold, const char *root, AnalyzeSecurityFlags flags); +int analyze_security(sd_bus *bus, + char **units, + JsonVariant *policy, + UnitFileScope scope, + bool check_man, + bool run_generators, + bool offline, + unsigned threshold, + const char *root, + AnalyzeSecurityFlags flags); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index 9bc7e606e8..816532f69e 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -91,6 +91,7 @@ static bool arg_man = true; static bool arg_generators = false; static char *arg_root = NULL; static char *arg_image = NULL; +static char *arg_security_policy = NULL; static bool arg_offline = false; static unsigned arg_threshold = 100; static unsigned arg_iterations = 1; @@ -100,6 +101,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); STATIC_DESTRUCTOR_REGISTER(arg_image, freep); +STATIC_DESTRUCTOR_REGISTER(arg_security_policy, freep); typedef struct BootTimes { usec_t firmware_time; @@ -2154,7 +2156,9 @@ static int do_verify(int argc, char *argv[], void *userdata) { static int do_security(int argc, char *argv[], void *userdata) { _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *policy = NULL; int r; + unsigned line, column; r = acquire_bus(&bus, NULL); if (r < 0) @@ -2162,7 +2166,35 @@ static int do_security(int argc, char *argv[], void *userdata) { (void) pager_open(arg_pager_flags); - return analyze_security(bus, strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_offline, arg_threshold, arg_root, 0); + if (arg_security_policy) { + r = json_parse_file(/*f=*/ NULL, arg_security_policy, /*flags=*/ 0, &policy, &line, &column); + if (r < 0) + return log_error_errno(r, "Failed to parse '%s' at %u:%u: %m", arg_security_policy, line, column); + } else { + _cleanup_fclose_ FILE *f = NULL; + _cleanup_free_ char *pp = NULL; + + r = search_and_fopen_nulstr("systemd-analyze-security.policy", "re", /*root=*/ NULL, CONF_PATHS_NULSTR("systemd"), &f, &pp); + if (r < 0 && r != -ENOENT) + return r; + + if (f != NULL) { + r = json_parse_file(f, pp, /*flags=*/ 0, &policy, &line, &column); + if (r < 0) + return log_error_errno(r, "[%s:%u:%u] Failed to parse JSON policy: %m", pp, line, column); + } + } + + return analyze_security(bus, + strv_skip(argv, 1), + policy, + arg_scope, + arg_man, + arg_generators, + arg_offline, + arg_threshold, + arg_root, + /*flags=*/ 0); } static int help(int argc, char *argv[], void *userdata) { @@ -2214,6 +2246,8 @@ static int help(int argc, char *argv[], void *userdata) { " --threshold=N Exit with a non-zero status when overall\n" " exposure level is over threshold value\n" " --version Show package version\n" + " --security-policy=PATH Use custom JSON security policy instead\n" + " of built-in one\n" " --no-pager Do not pipe output into a pager\n" " --system Operate on system systemd instance\n" " --user Operate on user systemd instance\n" @@ -2266,6 +2300,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_RECURSIVE_ERRORS, ARG_OFFLINE, ARG_THRESHOLD, + ARG_SECURITY_POLICY, }; static const struct option options[] = { @@ -2278,6 +2313,7 @@ static int parse_argv(int argc, char *argv[]) { { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS }, { "offline", required_argument, NULL, ARG_OFFLINE }, { "threshold", required_argument, NULL, ARG_THRESHOLD }, + { "security-policy", required_argument, NULL, ARG_SECURITY_POLICY }, { "system", no_argument, NULL, ARG_SYSTEM }, { "user", no_argument, NULL, ARG_USER }, { "global", no_argument, NULL, ARG_GLOBAL }, @@ -2409,6 +2445,12 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_SECURITY_POLICY: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_security_policy); + if (r < 0) + return r; + break; + case ARG_ITERATIONS: r = safe_atou(optarg, &arg_iterations); if (r < 0) @@ -2447,6 +2489,10 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --user is not supported for cat-config right now."); + if (arg_security_policy && !streq_ptr(argv[optind], "security")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --security-policy= is only supported for security."); + if ((arg_root || arg_image) && (!STRPTR_IN_SET(argv[optind], "cat-config", "verify")) && (!(streq_ptr(argv[optind], "security") && arg_offline))) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), From 3838d22cfb7d3627ad41db5f95321e884b260b58 Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Mon, 23 Aug 2021 14:21:50 -0700 Subject: [PATCH 2/3] systemd-analyze: allow parsing of JSON file to obtain custom security requirements for comparison The 'security' verb of systemd-analyze needs to be able to parse JSON files to be able to read in the user-defined requirements and use them to determine the overall exposure level of the specified unit file(s). The JSON files are expected to have a specific format where the keys in the file are the unit ids consisting of only alphanumeric characters and underscores and the values are JSON objects again consisting of key value pairs. The keys in these objects may include one or more of the following properties: description_na, description_good, description_bad, weight, and range. The first three of these are expected to be strings and the latter two are expected to be unsigned integer values. If one or more of these properties is missing from the JSON object, then the default values of the properties as specified in the hard coded set of security directives is used. The other properties that assess() needs to determine overall exposure levels for a unit file for example, the assess function and parameter type among others, are not to be included in the JSON files defined by the user because the values assigned to these fields are expected to be consistent across unit files for each id. --- src/analyze/analyze-security.c | 242 ++++++++++++++++++++++++++++++--- 1 file changed, 221 insertions(+), 21 deletions(-) diff --git a/src/analyze/analyze-security.c b/src/analyze/analyze-security.c index 6c886e731c..63d1998ed3 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -107,6 +107,7 @@ typedef struct SecurityInfo { struct security_assessor { const char *id; + const char *json_field; const char *description_good; const char *description_bad; const char *description_na; @@ -754,6 +755,7 @@ static int assess_ambient_capabilities( static const struct security_assessor security_assessor_table[] = { { .id = "User=/DynamicUser=", + .json_field = "UserOrDynamicUser", .description_bad = "Service runs as root user", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#User=", .weight = 2000, @@ -762,6 +764,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SupplementaryGroups=", + .json_field = "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", @@ -772,6 +775,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "PrivateDevices=", + .json_field = "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=", @@ -782,6 +786,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "PrivateMounts=", + .json_field = "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=", @@ -792,6 +797,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "PrivateNetwork=", + .json_field = "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=", @@ -802,6 +808,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "PrivateTmp=", + .json_field = "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=", @@ -813,6 +820,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "PrivateUsers=", + .json_field = "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=", @@ -823,6 +831,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectControlGroups=", + .json_field = "ProtectControlGroups", .description_good = "Service cannot modify the control group file system", .description_bad = "Service may modify the control group file system", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectControlGroups=", @@ -833,6 +842,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectKernelModules=", + .json_field = "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=", @@ -843,6 +853,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectKernelTunables=", + .json_field = "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=", @@ -853,6 +864,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectKernelLogs=", + .json_field = "ProtectKernelLogs", .description_good = "Service cannot read from or write to the kernel log ring buffer", .description_bad = "Service may read from or write to the kernel log ring buffer", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectKernelLogs=", @@ -863,6 +875,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectClock=", + .json_field = "ProtectClock", .description_good = "Service cannot write to the hardware clock or system clock", .description_bad = "Service may write to the hardware clock or system clock", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectClock=", @@ -873,6 +886,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectHome=", + .json_field = "ProtectHome", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHome=", .weight = 1000, .range = 10, @@ -881,6 +895,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectHostname=", + .json_field = "ProtectHostname", .description_good = "Service cannot change system host/domainname", .description_bad = "Service may change system host/domainname", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectHostname=", @@ -891,6 +906,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectSystem=", + .json_field = "ProtectSystem", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectSystem=", .weight = 1000, .range = 10, @@ -899,6 +915,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RootDirectory=/RootImage=", + .json_field = "RootDirectoryOrRootImage", .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=", @@ -909,6 +926,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "LockPersonality=", + .json_field = "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=", @@ -919,6 +937,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "MemoryDenyWriteExecute=", + .json_field = "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=", @@ -929,6 +948,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "NoNewPrivileges=", + .json_field = "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=", @@ -939,6 +959,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN", + .json_field = "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=", @@ -949,6 +970,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SET(UID|GID|PCAP)", + .json_field = "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=", @@ -961,6 +983,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_PTRACE", + .json_field = "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=", @@ -971,6 +994,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_TIME", + .json_field = "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=", @@ -981,6 +1005,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_NET_ADMIN", + .json_field = "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=", @@ -991,6 +1016,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_RAWIO", + .json_field = "CapabilityBoundingSet_CAP_SYS_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=", @@ -1001,6 +1027,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_MODULE", + .json_field = "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=", @@ -1011,6 +1038,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_AUDIT_*", + .json_field = "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=", @@ -1023,6 +1051,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYSLOG", + .json_field = "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=", @@ -1033,6 +1062,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_(NICE|RESOURCE)", + .json_field = "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=", @@ -1044,6 +1074,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_MKNOD", + .json_field = "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=", @@ -1054,6 +1085,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_(CHOWN|FSETID|SETFCAP)", + .json_field = "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=", @@ -1066,6 +1098,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_(DAC_*|FOWNER|IPC_OWNER)", + .json_field = "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=", @@ -1079,6 +1112,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_KILL", + .json_field = "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=", @@ -1089,6 +1123,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_NET_(BIND_SERVICE|BROADCAST|RAW)", + .json_field = "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=", @@ -1101,6 +1136,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_BOOT", + .json_field = "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=", @@ -1111,6 +1147,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_MAC_*", + .json_field = "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=", @@ -1122,6 +1159,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_LINUX_IMMUTABLE", + .json_field = "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=", @@ -1132,6 +1170,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_IPC_LOCK", + .json_field = "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=", @@ -1142,6 +1181,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_CHROOT", + .json_field = "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=", @@ -1152,6 +1192,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_BLOCK_SUSPEND", + .json_field = "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=", @@ -1162,6 +1203,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_WAKE_ALARM", + .json_field = "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=", @@ -1172,6 +1214,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_LEASE", + .json_field = "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=", @@ -1182,6 +1225,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_TTY_CONFIG", + .json_field = "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=", @@ -1192,6 +1236,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "CapabilityBoundingSet=~CAP_SYS_PACCT", + .json_field = "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=", @@ -1202,6 +1247,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "UMask=", + .json_field = "UMask", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#UMask=", .weight = 100, .range = 10, @@ -1209,6 +1255,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "KeyringMode=", + .json_field = "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", @@ -1218,6 +1265,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProtectProc=", + .json_field = "ProtectProc", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProtectProc=", .description_good = "Service has restricted access to process tree (/proc hidepid=)", .description_bad = "Service has full access to process tree (/proc hidepid=)", @@ -1227,6 +1275,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "ProcSubset=", + .json_field = "ProcSubset", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#ProcSubset=", .description_good = "Service has no access to non-process /proc files (/proc subset=)", .description_bad = "Service has full access to non-process /proc files (/proc subset=)", @@ -1236,6 +1285,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "NotifyAccess=", + .json_field = "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", @@ -1245,6 +1295,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RemoveIPC=", + .json_field = "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", @@ -1256,6 +1307,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "Delegate=", + .json_field = "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", @@ -1267,6 +1319,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictRealtime=", + .json_field = "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", @@ -1277,6 +1330,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictSUIDSGID=", + .json_field = "RestrictSUIDSGID", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#RestrictSUIDSGID=", .description_good = "SUID/SGID file creation by service is restricted", .description_bad = "Service may create SUID/SGID files", @@ -1287,6 +1341,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWUSER", + .json_field = "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", @@ -1297,6 +1352,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWNS", + .json_field = "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", @@ -1307,6 +1363,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWIPC", + .json_field = "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", @@ -1317,6 +1374,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWPID", + .json_field = "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", @@ -1327,6 +1385,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWCGROUP", + .json_field = "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", @@ -1337,6 +1396,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWNET", + .json_field = "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", @@ -1347,6 +1407,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictNamespaces=~CLONE_NEWUTS", + .json_field = "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", @@ -1357,6 +1418,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictAddressFamilies=~AF_(INET|INET6)", + .json_field = "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", @@ -1367,6 +1429,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictAddressFamilies=~AF_UNIX", + .json_field = "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", @@ -1377,6 +1440,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictAddressFamilies=~AF_NETLINK", + .json_field = "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", @@ -1387,6 +1451,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictAddressFamilies=~AF_PACKET", + .json_field = "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", @@ -1397,6 +1462,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "RestrictAddressFamilies=~…", + .json_field = "RestrictAddressFamilies_OTHER", .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", @@ -1407,6 +1473,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallArchitectures=", + .json_field = "SystemCallArchitectures", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallArchitectures=", .weight = 1000, .range = 10, @@ -1415,6 +1482,7 @@ static const struct security_assessor security_assessor_table[] = { #if HAVE_SECCOMP { .id = "SystemCallFilter=~@swap", + .json_field = "SystemCallFilter_swap", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1423,6 +1491,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@obsolete", + .json_field = "SystemCallFilter_obsolete", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 250, .range = 10, @@ -1431,6 +1500,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@clock", + .json_field = "SystemCallFilter_clock", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1439,6 +1509,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@cpu-emulation", + .json_field = "SystemCallFilter_cpu_emulation", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 250, .range = 10, @@ -1447,6 +1518,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@debug", + .json_field = "SystemCallFilter_debug", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1455,6 +1527,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@mount", + .json_field = "SystemCallFilter_mount", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1463,6 +1536,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@module", + .json_field = "SystemCallFilter_module", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1471,6 +1545,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@raw-io", + .json_field = "SystemCallFilter_raw_io", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1479,6 +1554,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@reboot", + .json_field = "SystemCallFilter_reboot", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 1000, .range = 10, @@ -1487,6 +1563,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@privileged", + .json_field = "SystemCallFilter_privileged", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 700, .range = 10, @@ -1495,6 +1572,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "SystemCallFilter=~@resources", + .json_field = "SystemCallFilter_resources", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#SystemCallFilter=", .weight = 700, .range = 10, @@ -1504,6 +1582,7 @@ static const struct security_assessor security_assessor_table[] = { #endif { .id = "IPAddressDeny=", + .json_field = "IPAddressDeny", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#IPAddressDeny=", .weight = 1000, .range = 10, @@ -1511,6 +1590,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "DeviceAllow=", + .json_field = "DeviceAllow", .url = "https://www.freedesktop.org/software/systemd/man/systemd.exec.html#DeviceAllow=", .weight = 1000, .range = 10, @@ -1518,6 +1598,7 @@ static const struct security_assessor security_assessor_table[] = { }, { .id = "AmbientCapabilities=", + .json_field = "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", @@ -1527,7 +1608,109 @@ static const struct security_assessor security_assessor_table[] = { }, }; -static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecurityFlags flags, unsigned threshold) { +static JsonVariant* security_assessor_find_in_policy(const struct security_assessor *a, JsonVariant *policy, const char *name) { + JsonVariant *item; + assert(a); + + if (!policy) + return NULL; + if (!json_variant_is_object(policy)) { + log_debug("Specificied policy is not a JSON object, ignoring."); + return NULL; + } + + item = json_variant_by_key(policy, a->json_field); + if (!item) + return NULL; + if (!json_variant_is_object(item)) { + log_debug("Item for '%s' in policy JSON object is not an object, ignoring.", a->id); + return NULL; + } + + return name ? json_variant_by_key(item, name) : item; +} + +static uint64_t access_weight(const struct security_assessor *a, JsonVariant *policy) { + JsonVariant *val; + + assert(a); + + val = security_assessor_find_in_policy(a, policy, "weight"); + if (val) { + if (json_variant_is_unsigned(val)) + return json_variant_unsigned(val); + log_debug("JSON field 'weight' of policy for %s is not an unsigned integer, ignoring.", a->id); + } + + return a->weight; +} + +static uint64_t access_range(const struct security_assessor *a, JsonVariant *policy) { + JsonVariant *val; + + assert(a); + + val = security_assessor_find_in_policy(a, policy, "range"); + if (val) { + if (json_variant_is_unsigned(val)) + return json_variant_unsigned(val); + log_debug("JSON field 'range' of policy for %s is not an unsigned integer, ignoring.", a->id); + } + + return a->range; +} + +static const char *access_description_na(const struct security_assessor *a, JsonVariant *policy) { + JsonVariant *val; + + assert(a); + + val = security_assessor_find_in_policy(a, policy, "description_na"); + if (val) { + if (json_variant_is_string(val)) + return json_variant_string(val); + log_debug("JSON field 'description_na' of policy for %s is not a string, ignoring.", a->id); + } + + return a->description_na; +} + +static const char *access_description_good(const struct security_assessor *a, JsonVariant *policy) { + JsonVariant *val; + + assert(a); + + val = security_assessor_find_in_policy(a, policy, "description_good"); + if (val) { + if (json_variant_is_string(val)) + return json_variant_string(val); + log_debug("JSON field 'description_good' of policy for %s is not a string, ignoring.", a->id); + } + + return a->description_good; +} + +static const char *access_description_bad(const struct security_assessor *a, JsonVariant *policy) { + JsonVariant *val; + + assert(a); + + val = security_assessor_find_in_policy(a, policy, "description_bad"); + if (val) { + if (json_variant_is_string(val)) + return json_variant_string(val); + log_debug("JSON field 'description_bad' of policy for %s is not a string, ignoring.", a->id); + } + + return a->description_bad; +} + +static int assess(const SecurityInfo *info, + Table *overview_table, + AnalyzeSecurityFlags flags, + unsigned threshold, + JsonVariant *policy) { + static const struct { uint64_t exposure; const char *name; @@ -1565,6 +1748,8 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri _cleanup_free_ char *d = NULL; uint64_t badness; void *data; + uint64_t weight = access_weight(a, policy); + uint64_t range = access_range(a, policy); data = (uint8_t *) info + a->offset; @@ -1579,29 +1764,30 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri return r; } - assert(a->range > 0); + assert(range > 0); if (badness != UINT64_MAX) { - assert(badness <= a->range); + assert(badness <= range); - badness_sum += DIV_ROUND_UP(badness * a->weight, a->range); - weight_sum += a->weight; + badness_sum += DIV_ROUND_UP(badness * weight, range); + weight_sum += weight; } if (details_table) { const char *checkmark, *description, *color = NULL; + const char *id = a->id; if (badness == UINT64_MAX) { checkmark = " "; - description = a->description_na; + description = access_description_na(a, policy); color = NULL; } else if (badness == a->range) { checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK); - description = a->description_bad; + description = access_description_bad(a, policy); color = ansi_highlight_red(); } else if (badness == 0) { checkmark = special_glyph(SPECIAL_GLYPH_CHECK_MARK); - description = a->description_good; + description = access_description_good(a, policy); color = ansi_highlight_green(); } else { checkmark = special_glyph(SPECIAL_GLYPH_CROSS_MARK); @@ -1612,17 +1798,20 @@ static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecuri if (d) description = d; + if (json_variant_by_key(policy, a->json_field) != NULL) + id = a->json_field; + r = table_add_many(details_table, TABLE_STRING, checkmark, TABLE_SET_MINIMUM_WIDTH, 1, TABLE_SET_MAXIMUM_WIDTH, 1, TABLE_SET_ELLIPSIZE_PERCENT, 0, TABLE_SET_COLOR, color, - TABLE_STRING, a->id, TABLE_SET_URL, a->url, + TABLE_STRING, id, TABLE_SET_URL, a->url, TABLE_STRING, description, - TABLE_UINT64, a->weight, TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, weight, TABLE_SET_ALIGN_PERCENT, 100, TABLE_UINT64, badness, TABLE_SET_ALIGN_PERCENT, 100, - TABLE_UINT64, a->range, TABLE_SET_ALIGN_PERCENT, 100, + TABLE_UINT64, range, TABLE_SET_ALIGN_PERCENT, 100, TABLE_EMPTY, TABLE_SET_ALIGN_PERCENT, 100); if (r < 0) return table_log_add_error(r); @@ -2192,8 +2381,12 @@ static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *in return 0; } -static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table, - AnalyzeSecurityFlags flags, unsigned threshold) { +static int analyze_security_one(sd_bus *bus, + const char *name, + Table *overview_table, + AnalyzeSecurityFlags flags, + unsigned threshold, + JsonVariant *policy) { _cleanup_(security_info_freep) SecurityInfo *info = security_info_new(); if (!info) @@ -2210,7 +2403,7 @@ static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_t if (r < 0) return r; - r = assess(info, overview_table, flags, threshold); + r = assess(info, overview_table, flags, threshold, policy); if (r < 0) return r; @@ -2396,7 +2589,7 @@ static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, Security return 0; } -static int offline_security_check(Unit *u, unsigned threshold) { +static int offline_security_check(Unit *u, unsigned threshold, JsonVariant *policy) { _cleanup_(table_unrefp) Table *overview_table = NULL; AnalyzeSecurityFlags flags = 0; _cleanup_(security_info_freep) SecurityInfo *info = NULL; @@ -2411,10 +2604,17 @@ static int offline_security_check(Unit *u, unsigned threshold) { if (r < 0) return r; - return assess(info, overview_table, flags, threshold); + return assess(info, overview_table, flags, threshold, policy); } -static int offline_security_checks(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, unsigned threshold, const char *root) { +static int offline_security_checks(char **filenames, + JsonVariant *policy, + UnitFileScope scope, + bool check_man, + bool run_generators, + unsigned threshold, + const char *root) { + const ManagerTestRunFlags flags = MANAGER_TEST_RUN_MINIMAL | MANAGER_TEST_RUN_ENV_GENERATORS | @@ -2473,7 +2673,7 @@ static int offline_security_checks(char **filenames, UnitFileScope scope, bool c } for (size_t i = 0; i < count; i++) { - k = offline_security_check(units[i], threshold); + k = offline_security_check(units[i], threshold, policy); if (k < 0 && r == 0) r = k; } @@ -2498,7 +2698,7 @@ int analyze_security(sd_bus *bus, assert(bus); if (offline) - return offline_security_checks(units, scope, check_man, run_generators, threshold, root); + return offline_security_checks(units, policy, scope, check_man, run_generators, threshold, root); if (strv_length(units) != 1) { overview_table = table_new("unit", "exposure", "predicate", "happy"); @@ -2558,7 +2758,7 @@ int analyze_security(sd_bus *bus, 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, threshold); + r = analyze_security_one(bus, *i, overview_table, flags, threshold, policy); if (r < 0 && ret >= 0) ret = r; } @@ -2593,7 +2793,7 @@ int analyze_security(sd_bus *bus, } else name = mangled; - r = analyze_security_one(bus, name, overview_table, flags, threshold); + r = analyze_security_one(bus, name, overview_table, flags, threshold, policy); if (r < 0 && ret >= 0) ret = r; } From 4b6221194d796c5839314e0462ca6a65f28dd1d1 Mon Sep 17 00:00:00 2001 From: Maanya Goenka Date: Wed, 25 Aug 2021 09:44:26 -0700 Subject: [PATCH 3/3] test: add integration tests for systemd-analyze --- test/TEST-63-ANALYZE/Makefile | 1 + test/TEST-63-ANALYZE/test.sh | 9 + test/units/testsuite-63.sh | 574 ++++++++++++++++++++++++++++++++++ 3 files changed, 584 insertions(+) create mode 120000 test/TEST-63-ANALYZE/Makefile create mode 100755 test/TEST-63-ANALYZE/test.sh create mode 100755 test/units/testsuite-63.sh diff --git a/test/TEST-63-ANALYZE/Makefile b/test/TEST-63-ANALYZE/Makefile new file mode 120000 index 0000000000..e9f93b1104 --- /dev/null +++ b/test/TEST-63-ANALYZE/Makefile @@ -0,0 +1 @@ +../TEST-01-BASIC/Makefile \ No newline at end of file diff --git a/test/TEST-63-ANALYZE/test.sh b/test/TEST-63-ANALYZE/test.sh new file mode 100755 index 0000000000..a64a7da258 --- /dev/null +++ b/test/TEST-63-ANALYZE/test.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -e + +TEST_DESCRIPTION="test analyze" + +# shellcheck source=test/test-functions +. "${TEST_BASE_DIR:?}/test-functions" + +do_test "$@" diff --git a/test/units/testsuite-63.sh b/test/units/testsuite-63.sh new file mode 100755 index 0000000000..207dfeff68 --- /dev/null +++ b/test/units/testsuite-63.sh @@ -0,0 +1,574 @@ +#!/usr/bin/env bash +# shellcheck disable=SC2016 +set -eux + +systemd-analyze log-level debug + +mkdir -p /tmp/img/usr/lib/systemd/system/ +mkdir -p /tmp/img/opt/ + +touch /tmp/img/opt/script0.sh +chmod +x /tmp/img/opt/script0.sh + +cat </tmp/img/usr/lib/systemd/system/testfile.service +[Service] +ExecStart = /opt/script0.sh +EOF + +set +e +# Default behaviour is to recurse through all dependencies when unit is loaded +systemd-analyze verify --root=/tmp/img/ testfile.service \ + && { echo 'unexpected success'; exit 1; } + +# As above, recurses through all dependencies when unit is loaded +systemd-analyze verify --recursive-errors=yes --root=/tmp/img/ testfile.service \ + && { echo 'unexpected success'; exit 1; } + +# Recurses through unit file and its direct dependencies when unit is loaded +systemd-analyze verify --recursive-errors=one --root=/tmp/img/ testfile.service \ + && { echo 'unexpected success'; exit 1; } + +set -e + +# zero exit status since dependencies are ignored when unit is loaded +systemd-analyze verify --recursive-errors=no --root=/tmp/img/ testfile.service + +rm /tmp/img/usr/lib/systemd/system/testfile.service + +cat </tmp/testfile.service +[Unit] +foo = bar + +[Service] +ExecStart = echo hello +EOF + +cat </tmp/testfile2.service +[Unit] +Requires = testfile.service + +[Service] +ExecStart = echo hello +EOF + +# Zero exit status since no additional dependencies are recursively loaded when the unit file is loaded +systemd-analyze verify --recursive-errors=no /tmp/testfile2.service + +set +e +# Non-zero exit status since all associated dependencies are recusrively loaded when the unit file is loaded +systemd-analyze verify --recursive-errors=yes /tmp/testfile2.service \ + && { echo 'unexpected success'; exit 1; } +set -e + +rm /tmp/testfile.service +rm /tmp/testfile2.service + +cat </tmp/testfile.service +[Service] +ExecStart = echo hello +EOF + + +# Zero exit status since the value used for comparison determine exposure to security threats is by default 100 +systemd-analyze security --offline=true /tmp/testfile.service + +set +e +#The overall exposure level assigned to the unit is greater than the set threshold +systemd-analyze security --threshold=90 --offline=true /tmp/testfile.service \ + && { echo 'unexpected success'; exit 1; } +set -e + +rm /tmp/testfile.service + +cat </tmp/img/usr/lib/systemd/system/testfile.service +[Service] +ExecStart = echo hello +PrivateNetwork = yes +PrivateDevices = yes +PrivateUsers = yes +EOF + +# The new overall exposure level assigned to the unit is less than the set thresholds +# Verifies that the --offline= option works with --root= +systemd-analyze security --threshold=90 --offline=true --root=/tmp/img/ testfile.service + +# Added an additional "INVALID_ID" id to the .json to verify that nothing breaks when input is malformed +# The PrivateNetwork id description and weight was changed to verify that 'security' is actually reading in +# values from the .json file when required. The default weight for "PrivateNetwork" is 2500, and the new weight +# assigned to that id in the .json file is 6000. This increased weight means that when the "PrivateNetwork" key is +# set to 'yes' (as above in the case of testfile.service) in the content of the unit file, the overall exposure +# level for the unit file should decrease to account for that increased weight. +cat </tmp/testfile.json +{"User_Or_DynamicUser": + {"description_bad": "Service runs as root user", + "weight": 2000, + "range": 10 + }, +"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", + "weight": 200, + "range": 1 + }, +"PrivateDevices": + {"description_good": "Service has no access to hardware devices", + "description_bad": "Service potentially has access to hardware devices", + "weight": 1000, + "range": 1 + }, +"PrivateMounts": + {"description_good": "Service cannot install system mounts", + "description_bad": "Service may install system mounts", + "weight": 1000, + "range": 1 + }, +"PrivateNetwork": + {"description_good": "Service doesn't have access to the host's network", + "description_bad": "Service has access to the host's network", + "weight": 6000, + "range": 1 + }, +"PrivateTmp": + {"description_good": "Service has no access to other software's temporary files", + "description_bad": "Service has access to other software's temporary files", + "weight": 1000, + "range": 1 + }, +"PrivateUsers": + {"description_good": "Service does not have access to other users", + "description_bad": "Service has access to other users", + "weight": 1000, + "range": 1 + }, +"ProtectControlGroups": + {"description_good": "Service cannot modify the control group file system", + "description_bad": "Service may modify the control group file system", + "weight": 1000, + "range": 1 + }, +"ProtectKernelModules": + {"description_good": "Service cannot load or read kernel modules", + "description_bad": "Service may load or read kernel modules", + "weight": 1000, + "range": 1 + }, +"ProtectKernelTunables": + {"description_good": "Service cannot alter kernel tunables (/proc/sys, …)", + "description_bad": "Service may alter kernel tunables", + "weight": 1000, + "range": 1 + }, +"ProtectKernelLogs": + {"description_good": "Service cannot read from or write to the kernel log ring buffer", + "description_bad": "Service may read from or write to the kernel log ring buffer", + "weight": 1000, + "range": 1 + }, +"ProtectClock": + {"description_good": "Service cannot write to the hardware clock or system clock", + "description_bad": "Service may write to the hardware clock or system clock", + "weight": 1000, + "range": 1 + }, +"ProtectHome": + {"weight": 1000, + "range": 10 + }, +"ProtectHostname": + {"description_good": "Service cannot change system host/domainname", + "description_bad": "Service may change system host/domainname", + "weight": 50, + "range": 1 + }, +"ProtectSystem": + {"weight": 1000, + "range": 10 + }, +"RootDirectory_Or_RootImage": + {"description_good": "Service has its own root directory/image", + "description_bad": "Service runs within the host's root directory", + "weight": 200, + "range": 1 + }, +"LockPersonality": + {"description_good": "Service cannot change ABI personality", + "description_bad": "Service may change ABI personality", + "weight": 100, + "range": 1 + }, +"MemoryDenyWriteExecute": + {"description_good": "Service cannot create writable executable memory mappings", + "description_bad": "Service may create writable executable memory mappings", + "weight": 100, + "range": 1 + }, +"NoNewPrivileges": + {"description_good": "Service processes cannot acquire new privileges", + "description_bad": "Service processes may acquire new privileges", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_ADMIN": + {"description_good": "Service has no administrator privileges", + "description_bad": "Service has administrator privileges", + "weight": 1500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SET_UID_GID_PCAP": + {"description_good": "Service cannot change UID/GID identities/capabilities", + "description_bad": "Service may change UID/GID identities/capabilities", + "weight": 1500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_PTRACE": + {"description_good": "Service has no ptrace() debugging abilities", + "description_bad": "Service has ptrace() debugging abilities", + "weight": 1500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_TIME": + {"description_good": "Service processes cannot change the system clock", + "description_bad": "Service processes may change the system clock", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_NET_ADMIN": + {"description_good": "Service has no network configuration privileges", + "description_bad": "Service has network configuration privileges", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_RAWIO": + {"description_good": "Service has no raw I/O access", + "description_bad": "Service has raw I/O access", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_MODULE": + {"description_good": "Service cannot load kernel modules", + "description_bad": "Service may load kernel modules", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_AUDIT": + {"description_good": "Service has no audit subsystem access", + "description_bad": "Service has audit subsystem access", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYSLOG": + {"description_good": "Service has no access to kernel logging", + "description_bad": "Service has access to kernel logging", + "weight": 500, + "range": 1 + }, +"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", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_MKNOD": + {"description_good": "Service cannot create device nodes", + "description_bad": "Service may create device nodes", + "weight": 500, + "range": 1 + }, +"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", + "weight": 1000, + "range": 1 + }, +"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", + "weight": 1000, + "range": 1 + }, +"CapabilityBoundingSet_CAP_KILL": + {"description_good": "Service cannot send UNIX signals to arbitrary processes", + "description_bad": "Service may send UNIX signals to arbitrary processes", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_NET_BIND_SERVICE_BROADCAST_RAW": + {"description_good": "Service has no elevated networking privileges", + "description_bad": "Service has elevated networking privileges", + "weight": 500, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_BOOT": + {"description_good": "Service cannot issue reboot()", + "description_bad": "Service may issue reboot()", + "weight": 100, + "range": 1 + }, +"CapabilityBoundingSet_CAP_MAC": + {"description_good": "Service cannot adjust SMACK MAC", + "description_bad": "Service may adjust SMACK MAC", + "weight": 100, + "range": 1 + }, +"CapabilityBoundingSet_CAP_LINUX_IMMUTABLE": + {"description_good": "Service cannot mark files immutable", + "description_bad": "Service may mark files immutable", + "weight": 75, + "range": 1 + }, +"CapabilityBoundingSet_CAP_IPC_LOCK": + {"description_good": "Service cannot lock memory into RAM", + "description_bad": "Service may lock memory into RAM", + "weight": 50, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_CHROOT": + {"description_good": "Service cannot issue chroot()", + "description_bad": "Service may issue chroot()", + "weight": 50, + "range": 1 + }, +"CapabilityBoundingSet_CAP_BLOCK_SUSPEND": + {"description_good": "Service cannot establish wake locks", + "description_bad": "Service may establish wake locks", + "weight": 25, + "range": 1 + }, +"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", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_LEASE": + {"description_good": "Service cannot create file leases", + "description_bad": "Service may create file leases", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_TTY_CONFIG": + {"description_good": "Service cannot issue vhangup()", + "description_bad": "Service may issue vhangup()", + "weight": 25, + "range": 1 + }, +"CapabilityBoundingSet_CAP_SYS_PACCT": + {"description_good": "Service cannot use acct()", + "description_bad": "Service may use acct()", + "weight": 25, + "range": 1 + }, +"UMask": + {"weight": 100, + "range": 10 + }, +"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 + }, +"ProtectProc": + {"description_good": "Service has restricted access to process tree(/proc hidepid=)", + "description_bad": "Service has full access to process tree(/proc hidepid=)", + "weight": 1000, + "range": 3 + }, +"ProcSubset": + {"description_good": "Service has no access to non-process/proc files(/proc subset=)", + "description_bad": "Service has full access to non-process/proc files(/proc subset=)", + "weight": 10, + "range": 1 + }, +"NotifyAccess": + {"description_good": "Service child processes cannot alter service state", + "description_bad": "Service child processes may alter service state", + "weight": 1000, + "range": 1 + }, +"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 + }, +"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 + }, +"RestrictRealtime": + {"description_good": "Service realtime scheduling access is restricted", + "description_bad": "Service may acquire realtime scheduling", + "weight": 500, + "range": 1 + }, +"RestrictSUIDSGID": + {"description_good": "SUID/SGIDfilecreationbyserviceisrestricted", + "description_bad": "ServicemaycreateSUID/SGIDfiles", + "weight": 1000, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWUSER": + {"description_good": "Servicecannotcreateusernamespaces", + "description_bad": "Servicemaycreateusernamespaces", + "weight": 1500, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWNS": + {"description_good": "Service cannot create file system namespaces", + "description_bad": "Service may create file system namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWIPC": + {"description_good": "Service cannot create IPC namespaces", + "description_bad": "Service may create IPC namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWPID": + {"description_good": "Service cannot create process namespaces", + "description_bad": "Service may create process namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWCGROUP": + {"description_good": "Service cannot create cgroup namespaces", + "description_bad": "Service may create cgroup namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWNET": + {"description_good": "Service cannot create network namespaces", + "description_bad": "Service may create network namespaces", + "weight": 500, + "range": 1 + }, +"RestrictNamespaces_CLONE_NEWUTS": + {"description_good": "Service cannot create hostname namespaces", + "description_bad": "Service may create hostname namespaces", + "weight": 100, + "range": 1 + }, +"RestrictAddressFamilies_AF_INET_INET6": + {"description_good": "Service cannot allocate Internet sockets", + "description_bad": "Service may allocate Internet sockets", + "weight": 1500, + "range": 1 + }, +"RestrictAddressFamilies_AF_UNIX": + {"description_good": "Service cannot allocate local sockets", + "description_bad": "Service may allocate local sockets", + "weight": 25, + "range": 1 + }, +"RestrictAddressFamilies_AF_NETLINK": + {"description_good": "Service cannot allocate netlink sockets", + "description_bad": "Service may allocate netlink sockets", + "weight": 200, + "range": 1 + }, +"RestrictAddressFamilies_AF_PACKET": + {"description_good": "Service cannot allocate packet sockets", + "description_bad": "Service may allocate packet sockets", + "weight": 1000, + "range": 1 + }, +"RestrictAddressFamilies_OTHER": + {"description_good": "Service cannot allocate exotic sockets", + "description_bad": "Service may allocate exotic sockets", + "weight": 1250, + "range": 1 + }, +"SystemCallArchitectures": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_swap": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_obsolete": + {"weight": 250, + "range": 10 + }, +"SystemCallFilter_clock": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_cpu_emulation": + {"weight": 250, + "range": 10 + }, +"SystemCallFilter_debug": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_mount": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_module": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_raw_io": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_reboot": + {"weight": 1000, + "range": 10 + }, +"SystemCallFilter_privileged": + {"weight": 700, + "range": 10 + }, +"SystemCallFilter_resources": + {"weight": 700, + "range": 10 + }, +"IPAddressDeny": + {"weight": 1000, + "range": 10 + }, +"DeviceAllow": + {"weight": 1000, + "range": 10 + }, +"AmbientCapabilities": + {"description_good": "Service process does not receive ambient capabilities", + "description_bad": "Service process receives ambient capabilities", + "weight": 500, + "range": 1 + }, +"INVALID_ID": + {"weight": 1000, + "range": 10 + } +} +EOF + +# Reads in custom security requirements from the parsed .json file and uses these for comparison +systemd-analyze security --threshold=90 --offline=true \ + --security-policy=/tmp/testfile.json \ + --root=/tmp/img/ testfile.service + +set +e +systemd-analyze security --threshold=50 --offline=true \ + --security-policy=/tmp/testfile.json \ + --root=/tmp/img/ testfile.service \ + && { echo 'unexpected success'; exit 1; } +set -e + +rm /tmp/img/usr/lib/systemd/system/testfile.service + +systemd-analyze log-level info + +echo OK >/testok + +exit 0