diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 48976f52bf..a2f9154791 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -770,6 +770,28 @@ Service b@0.service not loaded, b.socket cannot be started. operate on files inside the specified image path PATH. + + + + With security, perform an offline security review + of the specified unit file(s), i.e. does not have to rely on PID 1 to acquire security + information for the files like the security verb when used by itself does. + This means that can be used with and + as well. If a unit's overall exposure level is above that set by + (default value is 100), will return + an error. + + + + + + With security, allow the user to set a custom value + to compare the overall exposure level with, for the specified unit file(s). If a unit's + overall exposure level, is greater than that set by the user, security + will return an error. can be used with + as well and its default value is 100. + + diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index 1b9447a125..6f33d53cfc 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' + comps='--help --version --no-pager --system --user -H --host -M --machine --offline --threshold' 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 0dd080afb7..f91357cb61 100644 --- a/shell-completion/zsh/_systemd-analyze +++ b/shell-completion/zsh/_systemd-analyze @@ -90,6 +90,8 @@ _arguments \ '--root=[Add support for root argument]:PATH' \ '--image=[Add support for discrete images]:PATH' \ '--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' \ '--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 05e598ee5c..24500e3a5b 100644 --- a/src/analyze/analyze-security.c +++ b/src/analyze/analyze-security.c @@ -2,7 +2,9 @@ #include +#include "af-list.h" #include "analyze-security.h" +#include "analyze-verify.h" #include "bus-error.h" #include "bus-map-properties.h" #include "bus-unit-util.h" @@ -12,6 +14,7 @@ #include "in-addr-util.h" #include "locale-util.h" #include "macro.h" +#include "manager.h" #include "missing_capability.h" #include "missing_sched.h" #include "nulstr-util.h" @@ -21,14 +24,16 @@ #if HAVE_SECCOMP # include "seccomp-util.h" #endif +#include "service.h" #include "set.h" #include "stdio-util.h" #include "strv.h" #include "terminal-util.h" #include "unit-def.h" #include "unit-name.h" +#include "unit-serialize.h" -struct security_info { +typedef struct SecurityInfo { char *id; char *type; char *load_state; @@ -81,7 +86,7 @@ struct security_info { bool restrict_address_family_packet; bool restrict_address_family_other; - uint64_t restrict_namespaces; + unsigned long long restrict_namespaces; bool restrict_realtime; bool restrict_suid_sgid; @@ -92,13 +97,13 @@ struct security_info { char *device_policy; bool device_allow_non_empty; - char **system_call_architectures; + Set *system_call_architectures; bool system_call_filter_allow_list; - Set *system_call_filter; + Hashmap *system_call_filter; - uint32_t _umask; -}; + mode_t _umask; +} SecurityInfo; struct security_assessor { const char *id; @@ -110,7 +115,7 @@ struct security_assessor { uint64_t range; int (*assess)( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description); @@ -119,9 +124,24 @@ struct security_assessor { bool default_dependencies_only; }; -static void security_info_free(struct security_info *i) { +static SecurityInfo *security_info_new(void) { + SecurityInfo *info = new(SecurityInfo, 1); + if (!info) + return NULL; + + *info = (SecurityInfo) { + .default_dependencies = true, + .capability_bounding_set = UINT64_MAX, + .restrict_namespaces = UINT64_MAX, + ._umask = 0002, + }; + + return info; +} + +static SecurityInfo *security_info_free(SecurityInfo *i) { if (!i) - return; + return NULL; free(i->id); free(i->type); @@ -144,12 +164,16 @@ static void security_info_free(struct security_info *i) { free(i->device_policy); strv_free(i->supplementary_groups); - strv_free(i->system_call_architectures); + set_free(i->system_call_architectures); - set_free(i->system_call_filter); + hashmap_free(i->system_call_filter); + + return mfree(i); } -static bool security_info_runs_privileged(const struct security_info *i) { +DEFINE_TRIVIAL_CLEANUP_FUNC(SecurityInfo*, security_info_free); + +static bool security_info_runs_privileged(const SecurityInfo *i) { assert(i); if (STRPTR_IN_SET(i->user, "0", "root")) @@ -163,7 +187,7 @@ static bool security_info_runs_privileged(const struct security_info *i) { static int assess_bool( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -182,7 +206,7 @@ static int assess_bool( static int assess_user( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -219,7 +243,7 @@ static int assess_user( static int assess_protect_home( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -261,7 +285,7 @@ static int assess_protect_home( static int assess_protect_system( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -303,7 +327,7 @@ static int assess_protect_system( static int assess_root_directory( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -321,7 +345,7 @@ static int assess_root_directory( static int assess_capability_bounding_set( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -337,7 +361,7 @@ static int assess_capability_bounding_set( static int assess_umask( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -378,7 +402,7 @@ static int assess_umask( static int assess_keyring_mode( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -394,7 +418,7 @@ static int assess_keyring_mode( static int assess_protect_proc( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -416,7 +440,7 @@ static int assess_protect_proc( static int assess_proc_subset( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -432,7 +456,7 @@ static int assess_proc_subset( static int assess_notify_access( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -448,7 +472,7 @@ static int assess_notify_access( static int assess_remove_ipc( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -467,7 +491,7 @@ static int assess_remove_ipc( static int assess_supplementary_groups( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -486,7 +510,7 @@ static int assess_supplementary_groups( static int assess_restrict_namespaces( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -502,7 +526,7 @@ static int assess_restrict_namespaces( static int assess_system_call_architectures( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -513,10 +537,11 @@ static int assess_system_call_architectures( assert(ret_badness); assert(ret_description); - if (strv_isempty(info->system_call_architectures)) { + if (set_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"))) { + } else if (set_contains(info->system_call_architectures, "native") && + set_size(info->system_call_architectures) == 1) { b = 0; d = strdup("Service may execute system calls only with native ABI"); } else { @@ -535,7 +560,7 @@ static int assess_system_call_architectures( #if HAVE_SECCOMP -static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) { +static bool syscall_names_in_filter(Hashmap *s, bool allow_list, const SyscallFilterSet *f, const char **ret_offending_syscall) { const char *syscall; NULSTR_FOREACH(syscall, f->value) { @@ -556,7 +581,7 @@ static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilter if (id < 0) continue; - if (set_contains(s, syscall) == allow_list) { + if (hashmap_contains(s, syscall) == allow_list) { log_debug("Offending syscall filter item: %s", syscall); if (ret_offending_syscall) *ret_offending_syscall = syscall; @@ -570,7 +595,7 @@ static bool syscall_names_in_filter(Set *s, bool allow_list, const SyscallFilter static int assess_system_call_filter( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -587,7 +612,7 @@ static int assess_system_call_filter( uint64_t b; int r; - if (!info->system_call_filter_allow_list && set_isempty(info->system_call_filter)) { + if (!info->system_call_filter_allow_list && hashmap_isempty(info->system_call_filter)) { r = free_and_strdup(&d, "Service does not filter system calls"); b = 10; } else { @@ -635,7 +660,7 @@ static int assess_system_call_filter( static int assess_ip_address_allow( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -675,7 +700,7 @@ static int assess_ip_address_allow( static int assess_device_allow( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -712,7 +737,7 @@ static int assess_device_allow( static int assess_ambient_capabilities( const struct security_assessor *a, - const struct security_info *info, + const SecurityInfo *info, const void *data, uint64_t *ret_badness, char **ret_description) { @@ -753,7 +778,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, private_devices), + .offset = offsetof(SecurityInfo, private_devices), }, { .id = "PrivateMounts=", @@ -763,7 +788,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, private_mounts), + .offset = offsetof(SecurityInfo, private_mounts), }, { .id = "PrivateNetwork=", @@ -773,7 +798,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 2500, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, private_network), + .offset = offsetof(SecurityInfo, private_network), }, { .id = "PrivateTmp=", @@ -783,7 +808,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, private_tmp), + .offset = offsetof(SecurityInfo, private_tmp), .default_dependencies_only = true, }, { @@ -794,7 +819,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, private_users), + .offset = offsetof(SecurityInfo, private_users), }, { .id = "ProtectControlGroups=", @@ -804,7 +829,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, protect_control_groups), + .offset = offsetof(SecurityInfo, protect_control_groups), }, { .id = "ProtectKernelModules=", @@ -814,7 +839,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, protect_kernel_modules), + .offset = offsetof(SecurityInfo, protect_kernel_modules), }, { .id = "ProtectKernelTunables=", @@ -824,7 +849,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, protect_kernel_tunables), + .offset = offsetof(SecurityInfo, protect_kernel_tunables), }, { .id = "ProtectKernelLogs=", @@ -834,7 +859,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, protect_kernel_logs), + .offset = offsetof(SecurityInfo, protect_kernel_logs), }, { .id = "ProtectClock=", @@ -844,7 +869,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, protect_clock), + .offset = offsetof(SecurityInfo, protect_clock), }, { .id = "ProtectHome=", @@ -862,7 +887,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 50, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, protect_hostname), + .offset = offsetof(SecurityInfo, protect_hostname), }, { .id = "ProtectSystem=", @@ -890,7 +915,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 100, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, lock_personality), + .offset = offsetof(SecurityInfo, lock_personality), }, { .id = "MemoryDenyWriteExecute=", @@ -900,7 +925,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 100, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, memory_deny_write_execute), + .offset = offsetof(SecurityInfo, memory_deny_write_execute), }, { .id = "NoNewPrivileges=", @@ -910,7 +935,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, no_new_privileges), + .offset = offsetof(SecurityInfo, no_new_privileges), }, { .id = "CapabilityBoundingSet=~CAP_SYS_ADMIN", @@ -1227,7 +1252,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 100, .range = 1, .assess = assess_remove_ipc, - .offset = offsetof(struct security_info, remove_ipc), + .offset = offsetof(SecurityInfo, remove_ipc), }, { .id = "Delegate=", @@ -1237,7 +1262,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 100, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, delegate), + .offset = offsetof(SecurityInfo, delegate), .parameter = true, /* invert! */ }, { @@ -1248,7 +1273,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 500, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_realtime), + .offset = offsetof(SecurityInfo, restrict_realtime), }, { .id = "RestrictSUIDSGID=", @@ -1258,7 +1283,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_suid_sgid), + .offset = offsetof(SecurityInfo, restrict_suid_sgid), }, { .id = "RestrictNamespaces=~CLONE_NEWUSER", @@ -1338,7 +1363,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1500, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_address_family_inet), + .offset = offsetof(SecurityInfo, restrict_address_family_inet), }, { .id = "RestrictAddressFamilies=~AF_UNIX", @@ -1348,7 +1373,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 25, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_address_family_unix), + .offset = offsetof(SecurityInfo, restrict_address_family_unix), }, { .id = "RestrictAddressFamilies=~AF_NETLINK", @@ -1358,7 +1383,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 200, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_address_family_netlink), + .offset = offsetof(SecurityInfo, restrict_address_family_netlink), }, { .id = "RestrictAddressFamilies=~AF_PACKET", @@ -1368,7 +1393,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1000, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_address_family_packet), + .offset = offsetof(SecurityInfo, restrict_address_family_packet), }, { .id = "RestrictAddressFamilies=~…", @@ -1378,7 +1403,7 @@ static const struct security_assessor security_assessor_table[] = { .weight = 1250, .range = 1, .assess = assess_bool, - .offset = offsetof(struct security_info, restrict_address_family_other), + .offset = offsetof(SecurityInfo, restrict_address_family_other), }, { .id = "SystemCallArchitectures=", @@ -1502,7 +1527,7 @@ static const struct security_assessor security_assessor_table[] = { }, }; -static int assess(const struct security_info *info, Table *overview_table, AnalyzeSecurityFlags flags) { +static int assess(const SecurityInfo *info, Table *overview_table, AnalyzeSecurityFlags flags, unsigned threshold) { static const struct { uint64_t exposure; const char *name; @@ -1698,6 +1723,60 @@ static int assess(const struct security_info *info, Table *overview_table, Analy return table_log_add_error(r); } + /* Return error when overall exposure level is over threshold */ + if (exposure > threshold) + return -EINVAL; + + return 0; +} + +static int property_read_restrict_namespaces( + sd_bus *bus, + const char *member, + sd_bus_message *m, + sd_bus_error *error, + void *userdata) { + + SecurityInfo *info = userdata; + int r; + uint64_t namespaces; + + assert(bus); + assert(member); + assert(m); + assert(info); + + r = sd_bus_message_read(m, "t", &namespaces); + if (r < 0) + return r; + + info->restrict_namespaces = (unsigned long long) namespaces; + + return 0; +} + +static int property_read_umask( + sd_bus *bus, + const char *member, + sd_bus_message *m, + sd_bus_error *error, + void *userdata) { + + SecurityInfo *info = userdata; + int r; + uint32_t umask; + + assert(bus); + assert(member); + assert(m); + assert(info); + + r = sd_bus_message_read(m, "u", &umask); + if (r < 0) + return r; + + info->_umask = (mode_t) umask; + return 0; } @@ -1708,7 +1787,7 @@ static int property_read_restrict_address_families( sd_bus_error *error, void *userdata) { - struct security_info *info = userdata; + SecurityInfo *info = userdata; int allow_list, r; assert(bus); @@ -1761,6 +1840,42 @@ static int property_read_restrict_address_families( return sd_bus_message_exit_container(m); } +static int property_read_syscall_archs( + sd_bus *bus, + const char *member, + sd_bus_message *m, + sd_bus_error *error, + void *userdata) { + + SecurityInfo *info = userdata; + int r; + + assert(bus); + assert(member); + assert(m); + assert(info); + + 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_put_strdup(&info->system_call_architectures, name); + if (r < 0) + return r; + } + + return sd_bus_message_exit_container(m); +} + static int property_read_system_call_filter( sd_bus *bus, const char *member, @@ -1768,7 +1883,7 @@ static int property_read_system_call_filter( sd_bus_error *error, void *userdata) { - struct security_info *info = userdata; + SecurityInfo *info = userdata; int allow_list, r; assert(bus); @@ -1798,7 +1913,9 @@ static int property_read_system_call_filter( if (r == 0) break; - r = set_put_strdup(&info->system_call_filter, name); + /* The actual ExecContext stores the system call id as the map value, which we don't + * need. So we assign NULL to all values here. */ + r = hashmap_put_strdup(&info->system_call_filter, name, NULL); if (r < 0) return r; } @@ -1817,7 +1934,7 @@ static int property_read_ip_address_allow( sd_bus_error *error, void *userdata) { - struct security_info *info = userdata; + SecurityInfo *info = userdata; bool deny_ipv4 = false, deny_ipv6 = false; int r; @@ -1895,7 +2012,7 @@ static int property_read_ip_filters( sd_bus_error *error, void *userdata) { - struct security_info *info = userdata; + SecurityInfo *info = userdata; _cleanup_(strv_freep) char **l = NULL; int r; @@ -1922,7 +2039,7 @@ static int property_read_device_allow( sd_bus_error *error, void *userdata) { - struct security_info *info = userdata; + SecurityInfo *info = userdata; size_t n = 0; int r; @@ -1951,56 +2068,56 @@ static int property_read_device_allow( return sd_bus_message_exit_container(m); } -static int acquire_security_info(sd_bus *bus, const char *name, struct security_info *info, AnalyzeSecurityFlags flags) { +static int acquire_security_info(sd_bus *bus, const char *name, SecurityInfo *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 }, - { "IPIngressFilterPath", "as", property_read_ip_filters, 0 }, - { "IPEgressFilterPath", "as", property_read_ip_filters, 0 }, - { "Id", "s", NULL, offsetof(struct security_info, id) }, - { "KeyringMode", "s", NULL, offsetof(struct security_info, keyring_mode) }, - { "ProtectProc", "s", NULL, offsetof(struct security_info, protect_proc) }, - { "ProcSubset", "s", NULL, offsetof(struct security_info, proc_subset) }, - { "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) }, - { "ProtectKernelLogs", "b", NULL, offsetof(struct security_info, protect_kernel_logs) }, - { "ProtectClock", "b", NULL, offsetof(struct security_info, protect_clock) }, - { "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) }, + { "AmbientCapabilities", "t", NULL, offsetof(SecurityInfo, ambient_capabilities) }, + { "CapabilityBoundingSet", "t", NULL, offsetof(SecurityInfo, capability_bounding_set) }, + { "DefaultDependencies", "b", NULL, offsetof(SecurityInfo, default_dependencies) }, + { "Delegate", "b", NULL, offsetof(SecurityInfo, delegate) }, + { "DeviceAllow", "a(ss)", property_read_device_allow, 0 }, + { "DevicePolicy", "s", NULL, offsetof(SecurityInfo, device_policy) }, + { "DynamicUser", "b", NULL, offsetof(SecurityInfo, dynamic_user) }, + { "FragmentPath", "s", NULL, offsetof(SecurityInfo, fragment_path) }, + { "IPAddressAllow", "a(iayu)", property_read_ip_address_allow, 0 }, + { "IPAddressDeny", "a(iayu)", property_read_ip_address_allow, 0 }, + { "IPIngressFilterPath", "as", property_read_ip_filters, 0 }, + { "IPEgressFilterPath", "as", property_read_ip_filters, 0 }, + { "Id", "s", NULL, offsetof(SecurityInfo, id) }, + { "KeyringMode", "s", NULL, offsetof(SecurityInfo, keyring_mode) }, + { "ProtectProc", "s", NULL, offsetof(SecurityInfo, protect_proc) }, + { "ProcSubset", "s", NULL, offsetof(SecurityInfo, proc_subset) }, + { "LoadState", "s", NULL, offsetof(SecurityInfo, load_state) }, + { "LockPersonality", "b", NULL, offsetof(SecurityInfo, lock_personality) }, + { "MemoryDenyWriteExecute", "b", NULL, offsetof(SecurityInfo, memory_deny_write_execute) }, + { "NoNewPrivileges", "b", NULL, offsetof(SecurityInfo, no_new_privileges) }, + { "NotifyAccess", "s", NULL, offsetof(SecurityInfo, notify_access) }, + { "PrivateDevices", "b", NULL, offsetof(SecurityInfo, private_devices) }, + { "PrivateMounts", "b", NULL, offsetof(SecurityInfo, private_mounts) }, + { "PrivateNetwork", "b", NULL, offsetof(SecurityInfo, private_network) }, + { "PrivateTmp", "b", NULL, offsetof(SecurityInfo, private_tmp) }, + { "PrivateUsers", "b", NULL, offsetof(SecurityInfo, private_users) }, + { "ProtectControlGroups", "b", NULL, offsetof(SecurityInfo, protect_control_groups) }, + { "ProtectHome", "s", NULL, offsetof(SecurityInfo, protect_home) }, + { "ProtectHostname", "b", NULL, offsetof(SecurityInfo, protect_hostname) }, + { "ProtectKernelModules", "b", NULL, offsetof(SecurityInfo, protect_kernel_modules) }, + { "ProtectKernelTunables", "b", NULL, offsetof(SecurityInfo, protect_kernel_tunables) }, + { "ProtectKernelLogs", "b", NULL, offsetof(SecurityInfo, protect_kernel_logs) }, + { "ProtectClock", "b", NULL, offsetof(SecurityInfo, protect_clock) }, + { "ProtectSystem", "s", NULL, offsetof(SecurityInfo, protect_system) }, + { "RemoveIPC", "b", NULL, offsetof(SecurityInfo, remove_ipc) }, + { "RestrictAddressFamilies", "(bas)", property_read_restrict_address_families, 0 }, + { "RestrictNamespaces", "t", property_read_restrict_namespaces, 0 }, + { "RestrictRealtime", "b", NULL, offsetof(SecurityInfo, restrict_realtime) }, + { "RestrictSUIDSGID", "b", NULL, offsetof(SecurityInfo, restrict_suid_sgid) }, + { "RootDirectory", "s", NULL, offsetof(SecurityInfo, root_directory) }, + { "RootImage", "s", NULL, offsetof(SecurityInfo, root_image) }, + { "SupplementaryGroups", "as", NULL, offsetof(SecurityInfo, supplementary_groups) }, + { "SystemCallArchitectures", "as", property_read_syscall_archs, 0 }, + { "SystemCallFilter", "(as)", property_read_system_call_filter, 0 }, + { "Type", "s", NULL, offsetof(SecurityInfo, type) }, + { "UMask", "u", property_read_umask, 0 }, + { "User", "s", NULL, offsetof(SecurityInfo, user) }, {} }; @@ -2075,37 +2192,306 @@ static int acquire_security_info(sd_bus *bus, const char *name, struct security_ return 0; } -static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table, AnalyzeSecurityFlags flags) { - _cleanup_(security_info_free) struct security_info info = { - .default_dependencies = true, - .capability_bounding_set = UINT64_MAX, - .restrict_namespaces = UINT64_MAX, - ._umask = 0002, - }; +static int analyze_security_one(sd_bus *bus, const char *name, Table *overview_table, + AnalyzeSecurityFlags flags, unsigned threshold) { + + _cleanup_(security_info_freep) SecurityInfo *info = security_info_new(); + if (!info) + return log_oom(); + int r; assert(bus); assert(name); - r = acquire_security_info(bus, name, &info, flags); + 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); + r = assess(info, overview_table, flags, threshold); if (r < 0) return r; return 0; } -int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) { +/* Refactoring SecurityInfo so that it can make use of existing struct variables instead of reading from dbus */ +static int get_security_info(Unit *u, ExecContext *c, CGroupContext *g, SecurityInfo **ret_info) { + assert(ret_info); + + _cleanup_(security_info_freep) SecurityInfo *info = security_info_new(); + if (!info) + return log_oom(); + + if (u) { + if (u->id) { + info->id = strdup(u->id); + if (!info->id) + return log_oom(); + } + if (unit_type_to_string(u->type)) { + info->type = strdup(unit_type_to_string(u->type)); + if (!info->type) + return log_oom(); + } + if (unit_load_state_to_string(u->load_state)) { + info->load_state = strdup(unit_load_state_to_string(u->load_state)); + if (!info->load_state) + return log_oom(); + } + if (u->fragment_path) { + info->fragment_path = strdup(u->fragment_path); + if (!info->fragment_path) + return log_oom(); + } + info->default_dependencies = u->default_dependencies; + if (u->type == UNIT_SERVICE && notify_access_to_string(SERVICE(u)->notify_access)) { + info->notify_access = strdup(notify_access_to_string(SERVICE(u)->notify_access)); + if (!info->notify_access) + return log_oom(); + } + } + + if (c) { + info->ambient_capabilities = c->capability_ambient_set; + info->capability_bounding_set = c->capability_bounding_set; + if (c->user) { + info->user = strdup(c->user); + if (!info->user) + return log_oom(); + } + if (c->supplementary_groups) { + info->supplementary_groups = strv_copy(c->supplementary_groups); + if (!info->supplementary_groups) + return log_oom(); + } + info->dynamic_user = c->dynamic_user; + if (exec_keyring_mode_to_string(c->keyring_mode)) { + info->keyring_mode = strdup(exec_keyring_mode_to_string(c->keyring_mode)); + if (!info->keyring_mode) + return log_oom(); + } + if (protect_proc_to_string(c->protect_proc)) { + info->protect_proc = strdup(protect_proc_to_string(c->protect_proc)); + if (!info->protect_proc) + return log_oom(); + } + if (proc_subset_to_string(c->proc_subset)) { + info->proc_subset = strdup(proc_subset_to_string(c->proc_subset)); + if (!info->proc_subset) + return log_oom(); + } + info->lock_personality = c->lock_personality; + info->memory_deny_write_execute = c->memory_deny_write_execute; + info->no_new_privileges = c->no_new_privileges; + info->protect_hostname = c->protect_hostname; + info->private_devices = c->private_devices; + info->private_mounts = c->private_mounts; + info->private_network = c->private_network; + info->private_tmp = c->private_tmp; + info->private_users = c->private_users; + info->protect_control_groups = c->protect_control_groups; + info->protect_kernel_modules = c->protect_kernel_modules; + info->protect_kernel_tunables = c->protect_kernel_tunables; + info->protect_kernel_logs = c->protect_kernel_logs; + info->protect_clock = c->protect_clock; + if (protect_home_to_string(c->protect_home)) { + info->protect_home = strdup(protect_home_to_string(c->protect_home)); + if (!info->protect_home) + return log_oom(); + } + if (protect_system_to_string(c->protect_system)) { + info->protect_system = strdup(protect_system_to_string(c->protect_system)); + if (!info->protect_system) + return log_oom(); + } + info->remove_ipc = c->remove_ipc; + 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 = + c->address_families_allow_list; + + void *key; + SET_FOREACH(key, c->address_families) { + int family = PTR_TO_INT(key); + if (family == 0) + continue; + if (IN_SET(family, AF_INET, AF_INET6)) + info->restrict_address_family_inet = !c->address_families_allow_list; + else if (family == AF_UNIX) + info->restrict_address_family_unix = !c->address_families_allow_list; + else if (family == AF_NETLINK) + info->restrict_address_family_netlink = !c->address_families_allow_list; + else if (family == AF_PACKET) + info->restrict_address_family_packet = !c->address_families_allow_list; + else + info->restrict_address_family_other = !c->address_families_allow_list; + } + + info->restrict_namespaces = c->restrict_namespaces; + info->restrict_realtime = c->restrict_realtime; + info->restrict_suid_sgid = c->restrict_suid_sgid; + if (c->root_directory) { + info->root_directory = strdup(c->root_directory); + if (!info->root_directory) + return log_oom(); + } + if (c->root_image) { + info->root_image = strdup(c->root_image); + if (!info->root_image) + return log_oom(); + } + info->_umask = c->umask; + if (c->syscall_archs) { + info->system_call_architectures = set_copy(c->syscall_archs); + if (!info->system_call_architectures) + return log_oom(); + } + info->system_call_filter_allow_list = c->syscall_allow_list; + if (c->syscall_filter) { + info->system_call_filter = hashmap_copy(c->syscall_filter); + if (!info->system_call_filter) + return log_oom(); + } + } + + if (g) { + info->delegate = g->delegate; + if (cgroup_device_policy_to_string(g->device_policy)) { + info->device_policy = strdup(cgroup_device_policy_to_string(g->device_policy)); + if (!info->device_policy) + return log_oom(); + } + + IPAddressAccessItem *i; + bool deny_ipv4 = false, deny_ipv6 = false; + + LIST_FOREACH(items, i, g->ip_address_deny) { + if (i->family == AF_INET && i->prefixlen == 0) + deny_ipv4 = true; + else if (i->family == AF_INET6 && i->prefixlen == 0) + deny_ipv6 = true; + } + info->ip_address_deny_all = deny_ipv4 && deny_ipv6; + + info->ip_address_allow_localhost = info->ip_address_allow_other = false; + LIST_FOREACH(items, i, g->ip_address_allow) { + if (in_addr_is_localhost(i->family, &i->address)) + info->ip_address_allow_localhost = true; + else + info->ip_address_allow_other = true; + } + + info->ip_filters_custom_ingress = !strv_isempty(g->ip_filters_ingress); + info->ip_filters_custom_egress = !strv_isempty(g->ip_filters_egress); + info->device_allow_non_empty = !LIST_IS_EMPTY(g->device_allow); + } + + *ret_info = TAKE_PTR(info); + + return 0; +} + +static int offline_security_check(Unit *u, unsigned threshold) { + _cleanup_(table_unrefp) Table *overview_table = NULL; + AnalyzeSecurityFlags flags = 0; + _cleanup_(security_info_freep) SecurityInfo *info = NULL; + int r; + + assert(u); + + if (DEBUG_LOGGING) + unit_dump(u, stdout, "\t"); + + r = get_security_info(u, unit_get_exec_context(u), unit_get_cgroup_context(u), &info); + if (r < 0) + return r; + + return assess(info, overview_table, flags, threshold); +} + +static int offline_security_checks(char **filenames, 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 | + run_generators * MANAGER_TEST_RUN_GENERATORS; + + _cleanup_(manager_freep) Manager *m = NULL; + Unit *units[strv_length(filenames)]; + _cleanup_free_ char *var = NULL; + int r, k; + size_t count = 0; + char **filename; + + if (strv_isempty(filenames)) + return 0; + + /* set the path */ + r = verify_generate_path(&var, filenames); + if (r < 0) + return log_error_errno(r, "Failed to generate unit load path: %m"); + + assert_se(set_unit_path(var) >= 0); + + r = manager_new(scope, flags, &m); + if (r < 0) + return log_error_errno(r, "Failed to initialize manager: %m"); + + log_debug("Starting manager..."); + + r = manager_startup(m, /* serialization= */ NULL, /* fds= */ NULL, root); + if (r < 0) + return r; + + log_debug("Loading remaining units from the command line..."); + + STRV_FOREACH(filename, filenames) { + _cleanup_free_ char *prepared = NULL; + + log_debug("Handling %s...", *filename); + + k = verify_prepare_filename(*filename, &prepared); + if (k < 0) { + log_warning_errno(k, "Failed to prepare filename %s: %m", *filename); + if (r == 0) + r = k; + continue; + } + + k = manager_load_startable_unit_or_warn(m, NULL, prepared, &units[count]); + if (k < 0) { + if (r == 0) + r = k; + continue; + } + + count++; + } + + for (size_t i = 0; i < count; i++) { + k = offline_security_check(units[i], threshold); + if (k < 0 && r == 0) + r = k; + } + + 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) { + _cleanup_(table_unrefp) Table *overview_table = NULL; int ret = 0, r; assert(bus); + if (offline) + return offline_security_checks(units, scope, check_man, run_generators, threshold, root); + if (strv_length(units) != 1) { overview_table = table_new("unit", "exposure", "predicate", "happy"); if (!overview_table) @@ -2164,7 +2550,7 @@ int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) { 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); + r = analyze_security_one(bus, *i, overview_table, flags, threshold); if (r < 0 && ret >= 0) ret = r; } @@ -2199,7 +2585,7 @@ int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags) { } else name = mangled; - r = analyze_security_one(bus, name, overview_table, flags); + r = analyze_security_one(bus, name, overview_table, flags, threshold); if (r < 0 && ret >= 0) ret = r; } diff --git a/src/analyze/analyze-security.h b/src/analyze/analyze-security.h index e8de39f3bc..57a93afbef 100644 --- a/src/analyze/analyze-security.h +++ b/src/analyze/analyze-security.h @@ -1,12 +1,17 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include + #include "sd-bus.h" +#include "unit-file.h" + typedef enum AnalyzeSecurityFlags { ANALYZE_SECURITY_SHORT = 1 << 0, ANALYZE_SECURITY_ONLY_LOADED = 1 << 1, ANALYZE_SECURITY_ONLY_LONG_RUNNING = 1 << 2, } AnalyzeSecurityFlags; -int analyze_security(sd_bus *bus, char **units, AnalyzeSecurityFlags flags); +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); diff --git a/src/analyze/analyze-verify.c b/src/analyze/analyze-verify.c index cd5377200b..2a436c545e 100644 --- a/src/analyze/analyze-verify.c +++ b/src/analyze/analyze-verify.c @@ -33,7 +33,7 @@ static void log_syntax_callback(const char *unit, int level, void *userdata) { } } -static int prepare_filename(const char *filename, char **ret) { +int verify_prepare_filename(const char *filename, char **ret) { int r; const char *name; _cleanup_free_ char *abspath = NULL; @@ -70,7 +70,7 @@ static int prepare_filename(const char *filename, char **ret) { return 0; } -static int generate_path(char **var, char **filenames) { +int verify_generate_path(char **var, char **filenames) { const char *old; char **filename; @@ -266,7 +266,7 @@ int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run set_log_syntax_callback(log_syntax_callback, &s); /* set the path */ - r = generate_path(&var, filenames); + r = verify_generate_path(&var, filenames); if (r < 0) return log_error_errno(r, "Failed to generate unit load path: %m"); @@ -291,7 +291,7 @@ int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run log_debug("Handling %s...", *filename); - k = prepare_filename(*filename, &prepared); + k = verify_prepare_filename(*filename, &prepared); if (k < 0) { log_error_errno(k, "Failed to prepare filename %s: %m", *filename); if (r == 0) diff --git a/src/analyze/analyze-verify.h b/src/analyze/analyze-verify.h index 09d3aea02c..47b78a8158 100644 --- a/src/analyze/analyze-verify.h +++ b/src/analyze/analyze-verify.h @@ -14,6 +14,8 @@ typedef enum RecursiveErrors { _RECURSIVE_ERRORS_INVALID = -EINVAL, } RecursiveErrors; +int verify_generate_path(char **var, char **filenames); +int verify_prepare_filename(const char *filename, char **ret); int verify_executable(Unit *u, const ExecCommand *exec, const char *root); int verify_units(char **filenames, UnitFileScope scope, bool check_man, bool run_generators, RecursiveErrors recursive_errors, const char *root); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index ceb18db740..9bc7e606e8 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -91,6 +91,8 @@ static bool arg_man = true; static bool arg_generators = false; static char *arg_root = NULL; static char *arg_image = NULL; +static bool arg_offline = false; +static unsigned arg_threshold = 100; static unsigned arg_iterations = 1; static usec_t arg_base_time = USEC_INFINITY; @@ -2160,7 +2162,7 @@ static int do_security(int argc, char *argv[], void *userdata) { (void) pager_open(arg_pager_flags); - return analyze_security(bus, strv_skip(argv, 1), 0); + return analyze_security(bus, strv_skip(argv, 1), arg_scope, arg_man, arg_generators, arg_offline, arg_threshold, arg_root, 0); } static int help(int argc, char *argv[], void *userdata) { @@ -2208,6 +2210,9 @@ static int help(int argc, char *argv[], void *userdata) { "\nOptions:\n" " -h --help Show this help\n" " --recursive-errors=MODE Control which units are verified\n" + " --offline=BOOL Perform a security review on unit file(s)\n" + " --threshold=N Exit with a non-zero status when overall\n" + " exposure level is over threshold value\n" " --version Show package version\n" " --no-pager Do not pipe output into a pager\n" " --system Operate on system systemd instance\n" @@ -2259,6 +2264,8 @@ static int parse_argv(int argc, char *argv[]) { ARG_ITERATIONS, ARG_BASE_TIME, ARG_RECURSIVE_ERRORS, + ARG_OFFLINE, + ARG_THRESHOLD, }; static const struct option options[] = { @@ -2269,6 +2276,8 @@ static int parse_argv(int argc, char *argv[]) { { "root", required_argument, NULL, ARG_ROOT }, { "image", required_argument, NULL, ARG_IMAGE }, { "recursive-errors", required_argument, NULL, ARG_RECURSIVE_ERRORS }, + { "offline", required_argument, NULL, ARG_OFFLINE }, + { "threshold", required_argument, NULL, ARG_THRESHOLD }, { "system", no_argument, NULL, ARG_SYSTEM }, { "user", no_argument, NULL, ARG_USER }, { "global", no_argument, NULL, ARG_GLOBAL }, @@ -2387,6 +2396,19 @@ static int parse_argv(int argc, char *argv[]) { return r; break; + case ARG_OFFLINE: + r = parse_boolean_argument("--offline", optarg, &arg_offline); + if (r < 0) + return r; + break; + + case ARG_THRESHOLD: + r = safe_atou(optarg, &arg_threshold); + if (r < 0 || arg_threshold > 100) + return log_error_errno(r < 0 ? r : SYNTHETIC_ERRNO(EINVAL), "Failed to parse threshold: %s", optarg); + + break; + case ARG_ITERATIONS: r = safe_atou(optarg, &arg_iterations); if (r < 0) @@ -2408,6 +2430,14 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if (arg_offline && !streq_ptr(argv[optind], "security")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --offline= is only supported for security right now."); + + if (arg_threshold != 100 && !streq_ptr(argv[optind], "security")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --threshold= is only supported for security right now."); + if (arg_scope == UNIT_FILE_GLOBAL && !STR_IN_SET(argv[optind] ?: "time", "dot", "unit-paths", "verify")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -2417,9 +2447,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_root || arg_image) && !STRPTR_IN_SET(argv[optind], "cat-config", "verify")) + 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), - "Options --root= and --image= are only supported for cat-config and verify right now."); + "Options --root= and --image= are only supported for cat-config, verify and security when used with --offline= right now."); /* Having both an image and a root is not supported by the code */ if (arg_root && arg_image) diff --git a/src/basic/set.h b/src/basic/set.h index 0f8673934f..5cae13160b 100644 --- a/src/basic/set.h +++ b/src/basic/set.h @@ -26,7 +26,7 @@ static inline Set* set_free_free(Set *s) { /* no set_free_free_free */ -#define set_copy(s) ((Set*) _hashmap_copy(HASHMAP_BASE(h) HASHMAP_DEBUG_SRC_ARGS)) +#define set_copy(s) ((Set*) _hashmap_copy(HASHMAP_BASE(s) HASHMAP_DEBUG_SRC_ARGS)) int _set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS); #define set_ensure_allocated(h, ops) _set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS) diff --git a/src/test/test-set.c b/src/test/test-set.c index f89c968f21..fd12021cfa 100644 --- a/src/test/test-set.c +++ b/src/test/test-set.c @@ -117,6 +117,38 @@ static void test_set_ensure_allocated(void) { assert_se(set_size(m) == 0); } +static void test_set_copy(void) { + Set *s, *copy; + char *key1, *key2, *key3, *key4; + + log_info("/* %s */", __func__); + + key1 = strdup("key1"); + assert_se(key1); + key2 = strdup("key2"); + assert_se(key2); + key3 = strdup("key3"); + assert_se(key3); + key4 = strdup("key4"); + assert_se(key4); + + s = set_new(&string_hash_ops); + assert_se(s); + + assert_se(set_put(s, key1) >= 0); + assert_se(set_put(s, key2) >= 0); + assert_se(set_put(s, key3) >= 0); + assert_se(set_put(s, key4) >= 0); + + copy = set_copy(s); + assert_se(copy); + + assert(set_equal(s, copy)); + + set_free(s); + set_free_free(copy); +} + static void test_set_ensure_put(void) { _cleanup_set_free_ Set *m = NULL; @@ -311,6 +343,7 @@ int main(int argc, const char *argv[]) { test_set_ensure_consume(); test_set_strjoin(); test_set_equal(); + test_set_copy(); return 0; }