Commit message: analyze: check for RestrictSUIDSGID= in "systemd-analyze security"

And let's give it a heigh weight, since it pretty much can be used for
bad things only.
CVE: CVE-2019-3843
CWE: CWE-269
Vulnerability description: It was discovered that a systemd service that uses DynamicUser property can create a SUID/SGID binary that would be allowed to run as the transient service UID/GID even after the service is terminated. A local attacker may use this flaw to access resources that will be owned by a potentially different service in the future, when the UID/GID will be recycled.

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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 to 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 the 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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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);
}

Project: systemd
File: analyze-security.c

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,    SPECIAL_GLYPH_DEPRESSED_SMILEY        },
                { 90,  "UNSAFE",    ANSI_HIGHLIGHT_RED,    SPECIAL_GLYPH_UNHAPPY_SMILEY          },
                { 75,  "EXPOSED",   ANSI_HIGHLIGHT_YELLOW, SPECIAL_GLYPH_SLIGHTLY_UNHAPPY_SMILEY },
                { 50,  "MEDIUM",    NULL,                  SPECIAL_GLYPH_NEUTRAL_SMILEY          },
                { 10,  "OK",        ANSI_HIGHLIGHT_GREEN,  SPECIAL_GLYPH_SLIGHTLY_HAPPY_SMILEY   },
                { 1,   "SAFE",      ANSI_HIGHLIGHT_GREEN,  SPECIAL_GLYPH_HAPPY_SMILEY            },
                { 0,   "PERFECT",   ANSI_HIGHLIGHT_GREEN,  SPECIAL_GLYPH_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(SPECIAL_GLYPH_CROSS_MARK);
                                description = a->description_bad;
                                color = ansi_highlight_red();
                        } else if (badness == 0) {
                                checkmark = special_glyph(SPECIAL_GLYPH_CHECK_MARK);
                                description = a->description_good;
                                color = ansi_highlight_green();
                        } else {
                                checkmark = special_glyph(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");
        }

        assert(weight_sum > 0);
        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(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;
}

Project: systemd
File: analyze-security.c

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);
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

static bool syscall_names_in_filter(Set *s, bool whitelist, const SyscallFilterSet *f) {
        const char *syscall;

        NULSTR_FOREACH(syscall, f->value) {
                int id;

                if (syscall[0] == '@') {
                        const SyscallFilterSet *g;

                        assert_se(g = syscall_filter_set_find(syscall));
                        if (syscall_names_in_filter(s, whitelist, g))
                                return true; /* bad! */

                        continue;
                }

                /* Let's see if the system call actually exists on this platform, before complaining */
                id = seccomp_syscall_resolve_name(syscall);
                if (id < 0)
                        continue;

                if (set_contains(s, syscall) == whitelist) {
                        log_debug("Offending syscall filter item: %s", syscall);
                        return true; /* bad! */
                }
        }

        return false;
}

Project: systemd
File: analyze-security.c

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);
}

Project: systemd
File: analyze-security.c

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)             },
                { "ProtectControlGroups",    "b",       NULL,                                    offsetof(struct security_info, protect_control_groups)    },
                { "ProtectHome",             "s",       NULL,                                    offsetof(struct security_info, protect_home)              },
                { "ProtectHostname",         "b",       NULL,                                    offsetof(struct security_info, protect_hostname)          },
                { "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)         },
                { "RestrictSUIDSGID",        "b",       NULL,                                    offsetof(struct security_info, restrict_suid_sgid)        },
                { "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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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);
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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);
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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);
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}

Project: systemd
File: analyze-security.c

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;
}