diff --git a/docs/TRANSIENT-SETTINGS.md b/docs/TRANSIENT-SETTINGS.md index 89f0a7e80d..f8ff413d28 100644 --- a/docs/TRANSIENT-SETTINGS.md +++ b/docs/TRANSIENT-SETTINGS.md @@ -156,6 +156,7 @@ All execution-related settings are available for transient units. ✓ SystemCallFilter= ✓ SystemCallArchitectures= ✓ SystemCallErrorNumber= +✓ SystemCallLog= ✓ MemoryDenyWriteExecute= ✓ RestrictNamespaces= ✓ RestrictRealtime= diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 8be6a1aadd..d0bb5fc962 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -1888,7 +1888,8 @@ RestrictNamespaces=~cgroup net EACCES or EUCLEAN (see errno3 for a full list). This value will be returned when a deny-listed system call is triggered, instead of - terminating the processes immediately. This value takes precedence over the one given in + terminating the processes immediately. Special setting kill can be used to + explicitly specify killing. This value takes precedence over the one given in SystemCallErrorNumber=, see below. If running in user mode, or in system mode, but without the CAP_SYS_ADMIN capability (e.g. setting User=nobody), NoNewPrivileges=yes is implied. This feature @@ -2098,8 +2099,9 @@ SystemCallErrorNumber=EPERM return when the system call filter configured with SystemCallFilter= is triggered, instead of terminating the process immediately. See errno3 for a - full list of error codes. When this setting is not used, or when the empty string is assigned, the - process will be terminated immediately when the filter is triggered. + full list of error codes. When this setting is not used, or when the empty string or the special + setting kill is assigned, the process will be terminated immediately when the + filter is triggered. @@ -2134,6 +2136,21 @@ SystemCallErrorNumber=EPERM details. + + SystemCallLog= + + Takes a space-separated list of system call names. If this setting is used, all + system calls executed by the unit processes for the listed ones will be logged. If the first + character of the list is ~, the effect is inverted: all system calls except the + listed system calls will be logged. If running in user mode, or in system mode, but without the + CAP_SYS_ADMIN capability (e.g. setting User=nobody), + NoNewPrivileges=yes is implied. This feature makes use of the Secure Computing + Mode 2 interfaces of the kernel ('seccomp filtering') and is useful for auditing or setting up a + minimal sandboxing environment. This option may be specified more than once, in which case the filter + masks are merged. If the empty string is assigned, the filter is reset, all prior assignments will + have no effect. This does not affect commands prefixed with +. + + diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 44f0438cf4..818c9054d6 100644 --- a/src/basic/parse-util.c +++ b/src/basic/parse-util.c @@ -16,6 +16,9 @@ #include "missing_network.h" #include "parse-util.h" #include "process-util.h" +#if HAVE_SECCOMP +#include "seccomp-util.h" +#endif #include "stat-util.h" #include "string-util.h" #include "strv.h" @@ -314,6 +317,7 @@ int parse_errno(const char *t) { return e; } +#if HAVE_SECCOMP int parse_syscall_and_errno(const char *in, char **name, int *error) { _cleanup_free_ char *n = NULL; char *p; @@ -332,7 +336,7 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) { p = strchr(in, ':'); if (p) { - e = parse_errno(p + 1); + e = seccomp_parse_errno_or_action(p + 1); if (e < 0) return e; @@ -351,6 +355,7 @@ int parse_syscall_and_errno(const char *in, char **name, int *error) { return 0; } +#endif static const char *mangle_base(const char *s, unsigned *base) { const char *k; diff --git a/src/basic/parse-util.h b/src/basic/parse-util.h index 9a516ce5f6..2cee65c49a 100644 --- a/src/basic/parse-util.h +++ b/src/basic/parse-util.h @@ -19,7 +19,9 @@ int parse_mtu(int family, const char *s, uint32_t *ret); int parse_size(const char *t, uint64_t base, uint64_t *size); int parse_range(const char *t, unsigned *lower, unsigned *upper); int parse_errno(const char *t); +#if HAVE_SECCOMP int parse_syscall_and_errno(const char *in, char **name, int *error); +#endif #define SAFE_ATO_REFUSE_PLUS_MINUS (1U << 30) #define SAFE_ATO_REFUSE_LEADING_ZERO (1U << 29) diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 8f915ac2f5..b3d417cac7 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -387,7 +387,7 @@ static int property_get_syscall_filter( continue; if (num >= 0) { - e = errno_to_name(num); + e = seccomp_errno_or_action_to_string(num); if (e) { s = strjoin(name, ":", e); if (!s) @@ -415,6 +415,58 @@ static int property_get_syscall_filter( return sd_bus_message_close_container(reply); } +static int property_get_syscall_log( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + _cleanup_strv_free_ char **l = NULL; + int r; + +#if HAVE_SECCOMP + void *id, *val; +#endif + + assert(bus); + assert(reply); + assert(c); + + r = sd_bus_message_open_container(reply, 'r', "bas"); + if (r < 0) + return r; + + r = sd_bus_message_append(reply, "b", c->syscall_log_allow_list); + if (r < 0) + return r; + +#if HAVE_SECCOMP + HASHMAP_FOREACH_KEY(val, id, c->syscall_log) { + char *name = NULL; + + name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, PTR_TO_INT(id) - 1); + if (!name) + continue; + + r = strv_consume(&l, name); + if (r < 0) + return r; + } +#endif + + strv_sort(l); + + r = sd_bus_message_append_strv(reply, l); + if (r < 0) + return r; + + return sd_bus_message_close_container(reply); +} + static int property_get_syscall_archs( sd_bus *bus, const char *path, @@ -1068,6 +1120,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("SystemCallFilter", "(bas)", property_get_syscall_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SystemCallArchitectures", "as", property_get_syscall_archs, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SystemCallErrorNumber", "i", bus_property_get_int, offsetof(ExecContext, syscall_errno), SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("SystemCallLog", "(bas)", property_get_syscall_log, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("Personality", "s", property_get_personality, offsetof(ExecContext, personality), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("LockPersonality", "b", bus_property_get_bool, offsetof(ExecContext, lock_personality), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST), @@ -1424,7 +1477,7 @@ static const char* mount_propagation_flags_to_string_with_check(unsigned long n) static BUS_DEFINE_SET_TRANSIENT(nsec, "t", uint64_t, nsec_t, NSEC_FMT); static BUS_DEFINE_SET_TRANSIENT_IS_VALID(log_level, "i", int32_t, int, "%" PRIi32, log_level_is_valid); #if HAVE_SECCOMP -static BUS_DEFINE_SET_TRANSIENT_IS_VALID(errno, "i", int32_t, int, "%" PRIi32, errno_is_valid); +static BUS_DEFINE_SET_TRANSIENT_IS_VALID(errno, "i", int32_t, int, "%" PRIi32, seccomp_errno_or_action_is_valid); #endif static BUS_DEFINE_SET_TRANSIENT_PARSE(std_input, ExecInput, exec_input_from_string); static BUS_DEFINE_SET_TRANSIENT_PARSE(std_output, ExecOutput, exec_output_from_string); @@ -2230,6 +2283,76 @@ int bus_exec_context_set_transient_property( return 1; + } else if (streq(name, "SystemCallLog")) { + int allow_list; + _cleanup_strv_free_ char **l = NULL; + + r = sd_bus_message_enter_container(message, 'r', "bas"); + if (r < 0) + return r; + + r = sd_bus_message_read(message, "b", &allow_list); + if (r < 0) + return r; + + r = sd_bus_message_read_strv(message, &l); + if (r < 0) + return r; + + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + + if (!UNIT_WRITE_FLAGS_NOOP(flags)) { + _cleanup_free_ char *joined = NULL; + SeccompParseFlags invert_flag = allow_list ? 0 : SECCOMP_PARSE_INVERT; + char **s; + + if (strv_isempty(l)) { + c->syscall_log_allow_list = false; + c->syscall_log = hashmap_free(c->syscall_log); + + unit_write_settingf(u, flags, name, "SystemCallLog="); + return 1; + } + + if (!c->syscall_log) { + c->syscall_log = hashmap_new(NULL); + if (!c->syscall_log) + return log_oom(); + + c->syscall_log_allow_list = allow_list; + } + + STRV_FOREACH(s, l) { + _cleanup_free_ char *n = NULL; + int e; + + r = parse_syscall_and_errno(*s, &n, &e); + if (r < 0) + return r; + + r = seccomp_parse_syscall_filter(n, + 0, /* errno not used */ + c->syscall_log, + SECCOMP_PARSE_LOG | SECCOMP_PARSE_PERMISSIVE | + invert_flag | + (c->syscall_log_allow_list ? SECCOMP_PARSE_ALLOW_LIST : 0), + u->id, + NULL, 0); + if (r < 0) + return r; + } + + joined = strv_join(l, " "); + if (!joined) + return -ENOMEM; + + unit_write_settingf(u, flags, name, "SystemCallLog=%s%s", allow_list ? "" : "~", joined); + } + + return 1; + } else if (streq(name, "SystemCallArchitectures")) { _cleanup_strv_free_ char **l = NULL; diff --git a/src/core/execute.c b/src/core/execute.c index 50294a506f..be35093969 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1407,6 +1407,13 @@ static bool context_has_syscall_filters(const ExecContext *c) { !hashmap_isempty(c->syscall_filter); } +static bool context_has_syscall_logs(const ExecContext *c) { + assert(c); + + return c->syscall_log_allow_list || + !hashmap_isempty(c->syscall_log); +} + static bool context_has_no_new_privileges(const ExecContext *c) { assert(c); @@ -1428,6 +1435,7 @@ static bool context_has_no_new_privileges(const ExecContext *c) { c->protect_kernel_logs || c->private_devices || context_has_syscall_filters(c) || + context_has_syscall_logs(c) || !set_isempty(c->syscall_archs) || c->lock_personality || c->protect_hostname; @@ -1465,7 +1473,7 @@ static int apply_syscall_filter(const Unit* u, const ExecContext *c, bool needs_ if (skip_seccomp_unavailable(u, "SystemCallFilter=")) return 0; - negative_action = c->syscall_errno == 0 ? scmp_act_kill_process() : SCMP_ACT_ERRNO(c->syscall_errno); + negative_action = c->syscall_errno == SECCOMP_ERROR_NUMBER_KILL ? scmp_act_kill_process() : SCMP_ACT_ERRNO(c->syscall_errno); if (c->syscall_allow_list) { default_action = negative_action; @@ -1484,6 +1492,39 @@ static int apply_syscall_filter(const Unit* u, const ExecContext *c, bool needs_ return seccomp_load_syscall_filter_set_raw(default_action, c->syscall_filter, action, false); } +static int apply_syscall_log(const Unit* u, const ExecContext *c) { +#ifdef SCMP_ACT_LOG + uint32_t default_action, action; +#endif + + assert(u); + assert(c); + + if (!context_has_syscall_logs(c)) + return 0; + +#ifdef SCMP_ACT_LOG + if (skip_seccomp_unavailable(u, "SystemCallLog=")) + return 0; + + if (c->syscall_log_allow_list) { + /* Log nothing but the ones listed */ + default_action = SCMP_ACT_ALLOW; + action = SCMP_ACT_LOG; + } else { + /* Log everything but the ones listed */ + default_action = SCMP_ACT_LOG; + action = SCMP_ACT_ALLOW; + } + + return seccomp_load_syscall_filter_set_raw(default_action, c->syscall_log, action, false); +#else + /* old libseccomp */ + log_unit_debug(u, "SECCOMP feature SCMP_ACT_LOG not available, skipping SystemCallLog="); + return 0; +#endif +} + static int apply_syscall_archs(const Unit *u, const ExecContext *c) { assert(u); assert(c); @@ -4438,6 +4479,12 @@ static int exec_child( return log_unit_error_errno(unit, r, "Failed to lock personalities: %m"); } + r = apply_syscall_log(unit, context); + if (r < 0) { + *exit_status = EXIT_SECCOMP; + return log_unit_error_errno(unit, r, "Failed to apply system call log filters: %m"); + } + /* This really should remain the last step before the execve(), to make sure our own code is unaffected * by the filter as little as possible. */ r = apply_syscall_filter(unit, context, needs_ambient_hack); @@ -4675,6 +4722,9 @@ void exec_context_init(ExecContext *c) { assert_cc(NAMESPACE_FLAGS_INITIAL != NAMESPACE_FLAGS_ALL); c->restrict_namespaces = NAMESPACE_FLAGS_INITIAL; c->log_level_max = -1; +#if HAVE_SECCOMP + c->syscall_errno = SECCOMP_ERROR_NUMBER_KILL; +#endif numa_policy_reset(&c->numa_policy); } @@ -5474,7 +5524,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { fputs(strna(name), f); if (num >= 0) { - errno_name = errno_to_name(num); + errno_name = seccomp_errno_or_action_to_string(num); if (errno_name) fprintf(f, ":%s", errno_name); else @@ -5517,15 +5567,20 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix) { prefix, c->network_namespace_path); if (c->syscall_errno > 0) { +#if HAVE_SECCOMP const char *errno_name; +#endif fprintf(f, "%sSystemCallErrorNumber: ", prefix); - errno_name = errno_to_name(c->syscall_errno); +#if HAVE_SECCOMP + errno_name = seccomp_errno_or_action_to_string(c->syscall_errno); if (errno_name) - fprintf(f, "%s\n", errno_name); + fputs(errno_name, f); else - fprintf(f, "%d\n", c->syscall_errno); + fprintf(f, "%d", c->syscall_errno); +#endif + fputc('\n', f); } for (size_t i = 0; i < c->n_mount_images; i++) { diff --git a/src/core/execute.h b/src/core/execute.h index 810e585fa8..02a2c8d1e7 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -302,6 +302,9 @@ struct ExecContext { int syscall_errno; bool syscall_allow_list:1; + Hashmap *syscall_log; + bool syscall_log_allow_list:1; /* Log listed system calls */ + bool address_families_allow_list:1; Set *address_families; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index 4bad8314dc..c60d565eb4 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -79,6 +79,7 @@ m4_ifdef(`HAVE_SECCOMP', `$1.SystemCallFilter, config_parse_syscall_filter, 0, offsetof($1, exec_context) $1.SystemCallArchitectures, config_parse_syscall_archs, 0, offsetof($1, exec_context.syscall_archs) $1.SystemCallErrorNumber, config_parse_syscall_errno, 0, offsetof($1, exec_context) +$1.SystemCallLog, config_parse_syscall_log, 0, offsetof($1, exec_context) $1.MemoryDenyWriteExecute, config_parse_bool, 0, offsetof($1, exec_context.memory_deny_write_execute) $1.RestrictNamespaces, config_parse_restrict_namespaces, 0, offsetof($1, exec_context) $1.RestrictRealtime, config_parse_bool, 0, offsetof($1, exec_context.restrict_realtime) @@ -88,6 +89,7 @@ $1.LockPersonality, config_parse_bool, 0, `$1.SystemCallFilter, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.SystemCallArchitectures, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.SystemCallErrorNumber, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 +$1.SystemCallLog, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.MemoryDenyWriteExecute, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.RestrictNamespaces, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 $1.RestrictRealtime, config_parse_warn_compat, DISABLED_CONFIGURATION, 0 diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index d3919adddf..a4240225f1 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -3197,6 +3197,86 @@ int config_parse_syscall_filter( } } +int config_parse_syscall_log( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + _unused_ const Unit *u = userdata; + bool invert = false; + const char *p; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(u); + + if (isempty(rvalue)) { + /* Empty assignment resets the list */ + c->syscall_log = hashmap_free(c->syscall_log); + c->syscall_log_allow_list = false; + return 0; + } + + if (rvalue[0] == '~') { + invert = true; + rvalue++; + } + + if (!c->syscall_log) { + c->syscall_log = hashmap_new(NULL); + if (!c->syscall_log) + return log_oom(); + + if (invert) + /* Log everything but the ones listed */ + c->syscall_log_allow_list = false; + else + /* Log nothing but the ones listed */ + c->syscall_log_allow_list = true; + } + + p = rvalue; + for (;;) { + _cleanup_free_ char *word = NULL, *name = NULL; + int num; + + r = extract_first_word(&p, &word, NULL, 0); + if (r == 0) + return 0; + if (r == -ENOMEM) + return log_oom(); + if (r < 0) { + log_syntax(unit, LOG_WARNING, filename, line, r, "Invalid syntax, ignoring: %s", rvalue); + return 0; + } + + r = parse_syscall_and_errno(word, &name, &num); + if (r < 0 || num >= 0) { /* errno code not allowed */ + log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to parse syscall, ignoring: %s", word); + continue; + } + + r = seccomp_parse_syscall_filter( + name, 0, c->syscall_log, + SECCOMP_PARSE_LOG|SECCOMP_PARSE_PERMISSIVE| + (invert ? SECCOMP_PARSE_INVERT : 0)| + (c->syscall_log_allow_list ? SECCOMP_PARSE_ALLOW_LIST : 0), + unit, filename, line); + if (r < 0) + return r; + } +} + int config_parse_syscall_archs( const char *unit, const char *filename, @@ -3264,9 +3344,9 @@ int config_parse_syscall_errno( assert(lvalue); assert(rvalue); - if (isempty(rvalue)) { + if (isempty(rvalue) || streq(rvalue, "kill")) { /* Empty assignment resets to KILL */ - c->syscall_errno = 0; + c->syscall_errno = SECCOMP_ERROR_NUMBER_KILL; return 0; } @@ -5444,6 +5524,7 @@ void unit_dump_config_items(FILE *f) { { config_parse_syscall_filter, "SYSCALLS" }, { config_parse_syscall_archs, "ARCHS" }, { config_parse_syscall_errno, "ERRNO" }, + { config_parse_syscall_log, "SYSCALLS" }, { config_parse_address_families, "FAMILIES" }, { config_parse_restrict_namespaces, "NAMESPACES" }, #endif diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index e90953b80f..3504227cae 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -65,6 +65,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_unit_requires_mounts_for); CONFIG_PARSER_PROTOTYPE(config_parse_syscall_filter); CONFIG_PARSER_PROTOTYPE(config_parse_syscall_archs); CONFIG_PARSER_PROTOTYPE(config_parse_syscall_errno); +CONFIG_PARSER_PROTOTYPE(config_parse_syscall_log); CONFIG_PARSER_PROTOTYPE(config_parse_environ); CONFIG_PARSER_PROTOTYPE(config_parse_pass_environ); CONFIG_PARSER_PROTOTYPE(config_parse_unset_environ); diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index c72c9791c0..3ae3c12f92 100644 --- a/src/shared/bus-unit-util.c +++ b/src/shared/bus-unit-util.c @@ -30,6 +30,9 @@ #include "path-util.h" #include "process-util.h" #include "rlimit-util.h" +#if HAVE_SECCOMP +#include "seccomp-util.h" +#endif #include "securebits-util.h" #include "signal-util.h" #include "socket-util.h" @@ -107,7 +110,10 @@ DEFINE_BUS_APPEND_PARSE("i", ioprio_class_from_string); DEFINE_BUS_APPEND_PARSE("i", ip_tos_from_string); DEFINE_BUS_APPEND_PARSE("i", log_facility_unshifted_from_string); DEFINE_BUS_APPEND_PARSE("i", log_level_from_string); -DEFINE_BUS_APPEND_PARSE("i", parse_errno); +#if !HAVE_SECCOMP +static inline int seccomp_parse_errno_or_action(const char *eq) { return -EINVAL; } +#endif +DEFINE_BUS_APPEND_PARSE("i", seccomp_parse_errno_or_action); DEFINE_BUS_APPEND_PARSE("i", sched_policy_from_string); DEFINE_BUS_APPEND_PARSE("i", secure_bits_from_string); DEFINE_BUS_APPEND_PARSE("i", signal_from_string); @@ -927,7 +933,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con return bus_append_parse_nice(m, field, eq); if (streq(field, "SystemCallErrorNumber")) - return bus_append_parse_errno(m, field, eq); + return bus_append_seccomp_parse_errno_or_action(m, field, eq); if (streq(field, "IOSchedulingClass")) return bus_append_ioprio_class_from_string(m, field, eq); @@ -1293,7 +1299,8 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con } if (STR_IN_SET(field, "RestrictAddressFamilies", - "SystemCallFilter")) { + "SystemCallFilter", + "SystemCallLog")) { int allow_list = 1; const char *p = eq; diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 10f78d6c2c..358960d5c4 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -1071,7 +1071,13 @@ int seccomp_load_syscall_filter_set_raw(uint32_t default_action, Hashmap* set, u int id = PTR_TO_INT(syscall_id) - 1; int error = PTR_TO_INT(val); - if (action != SCMP_ACT_ALLOW && error >= 0) + if (error == SECCOMP_ERROR_NUMBER_KILL) + a = scmp_act_kill_process(); +#ifdef SCMP_ACT_LOG + else if (action == SCMP_ACT_LOG) + a = SCMP_ACT_LOG; +#endif + else if (action != SCMP_ACT_ALLOW && error >= 0) a = SCMP_ACT_ERRNO(error); r = seccomp_rule_add_exact(seccomp, a, id, 0); diff --git a/src/shared/seccomp-util.h b/src/shared/seccomp-util.h index b62ee7c448..ff3b96df4b 100644 --- a/src/shared/seccomp-util.h +++ b/src/shared/seccomp-util.h @@ -5,7 +5,10 @@ #include #include +#include "errno-list.h" +#include "parse-util.h" #include "set.h" +#include "string-util.h" const char* seccomp_arch_to_string(uint32_t c); int seccomp_arch_from_string(const char *n, uint32_t *ret); @@ -115,3 +118,25 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(scmp_filter_ctx, seccomp_release); int parse_syscall_archs(char **l, Set **ret_archs); uint32_t scmp_act_kill_process(void); + +/* This is a special value to be used where syscall filters otherwise expect errno numbers, will be + replaced with real seccomp action. */ +enum { + SECCOMP_ERROR_NUMBER_KILL = INT_MAX - 1, +}; + +static inline bool seccomp_errno_or_action_is_valid(int n) { + return n == SECCOMP_ERROR_NUMBER_KILL || errno_is_valid(n); +} + +static inline int seccomp_parse_errno_or_action(const char *p) { + if (streq_ptr(p, "kill")) + return SECCOMP_ERROR_NUMBER_KILL; + return parse_errno(p); +} + +static inline const char *seccomp_errno_or_action_to_string(int num) { + if (num == SECCOMP_ERROR_NUMBER_KILL) + return "kill"; + return errno_to_name(num); +} diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index aa046c6ada..115983f98b 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4822,7 +4822,7 @@ static int print_property(const char *name, const char *expected_value, sd_bus_m return 1; - } else if (STR_IN_SET(name, "SystemCallFilter", "RestrictAddressFamilies")) { + } else if (STR_IN_SET(name, "SystemCallFilter", "SystemCallLog", "RestrictAddressFamilies")) { _cleanup_strv_free_ char **l = NULL; int allow_list; diff --git a/src/test/test-execute.c b/src/test/test-execute.c index e32e0c0b6c..b5b93b5283 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -434,6 +434,8 @@ static void test_exec_systemcallfilter(Manager *m) { test(__func__, m, "exec-systemcallfilter-with-errno-name.service", errno_from_name("EILSEQ"), CLD_EXITED); test(__func__, m, "exec-systemcallfilter-with-errno-number.service", 255, CLD_EXITED); test(__func__, m, "exec-systemcallfilter-with-errno-multi.service", errno_from_name("EILSEQ"), CLD_EXITED); + test(__func__, m, "exec-systemcallfilter-override-error-action.service", SIGSYS, CLD_KILLED); + test(__func__, m, "exec-systemcallfilter-override-error-action2.service", errno_from_name("EILSEQ"), CLD_EXITED); #endif } diff --git a/src/test/test-parse-util.c b/src/test/test-parse-util.c index 3ca5e1e639..3806c3f8cf 100644 --- a/src/test/test-parse-util.c +++ b/src/test/test-parse-util.c @@ -10,6 +10,9 @@ #include "log.h" #include "parse-util.h" #include "string-util.h" +#if HAVE_SECCOMP +#include "seccomp-util.h" +#endif static void test_parse_boolean(void) { assert_se(parse_boolean("1") == 1); @@ -852,6 +855,7 @@ static void test_parse_errno(void) { } static void test_parse_syscall_and_errno(void) { +#if HAVE_SECCOMP _cleanup_free_ char *n = NULL; int e; @@ -882,11 +886,16 @@ static void test_parse_syscall_and_errno(void) { assert_se(e == 255); n = mfree(n); + assert_se(parse_syscall_and_errno("hoge:kill", &n, &e) >= 0); + assert_se(streq(n, "hoge")); + assert_se(e == SECCOMP_ERROR_NUMBER_KILL); + n = mfree(n); + /* The function checks the syscall name is empty or not. */ assert_se(parse_syscall_and_errno("", &n, &e) == -EINVAL); assert_se(parse_syscall_and_errno(":255", &n, &e) == -EINVAL); - /* errno must be a valid errno name or number between 0 and ERRNO_MAX == 4095 */ + /* errno must be a valid errno name or number between 0 and ERRNO_MAX == 4095, or "kill" */ assert_se(parse_syscall_and_errno("hoge:4096", &n, &e) == -ERANGE); assert_se(parse_syscall_and_errno("hoge:-3", &n, &e) == -ERANGE); assert_se(parse_syscall_and_errno("hoge:12.3", &n, &e) == -EINVAL); @@ -896,6 +905,7 @@ static void test_parse_syscall_and_errno(void) { assert_se(parse_syscall_and_errno("hoge:-EINVAL", &n, &e) == -EINVAL); assert_se(parse_syscall_and_errno("hoge:EINVALaaa", &n, &e) == -EINVAL); assert_se(parse_syscall_and_errno("hoge:", &n, &e) == -EINVAL); +#endif } static void test_parse_mtu(void) { diff --git a/test/test-execute/exec-systemcallfilter-override-error-action.service b/test/test-execute/exec-systemcallfilter-override-error-action.service new file mode 100644 index 0000000000..3569b4500c --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-override-error-action.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for SystemCallFilter with specific kill action overriding default errno action + +[Service] +ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)' +Type=oneshot +SystemCallFilter=~uname:kill +SystemCallErrorNumber=EILSEQ diff --git a/test/test-execute/exec-systemcallfilter-override-error-action2.service b/test/test-execute/exec-systemcallfilter-override-error-action2.service new file mode 100644 index 0000000000..04bfd6bfcb --- /dev/null +++ b/test/test-execute/exec-systemcallfilter-override-error-action2.service @@ -0,0 +1,8 @@ +[Unit] +Description=Test for SystemCallFilter with specific errno action overriding default kill action + +[Service] +ExecStart=/usr/bin/python3 -c 'import os\ntry: os.uname()\nexcept Exception as e: exit(e.errno)' +Type=oneshot +SystemCallFilter=~uname:EILSEQ +SystemCallErrorNumber=kill