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