From 005bfaf11876e261de6b99d597b69f664b53e7c5 Mon Sep 17 00:00:00 2001 From: Topi Miettinen Date: Wed, 5 Aug 2020 16:31:26 +0300 Subject: [PATCH] exec: Add kill action to system call filters Define explicit action "kill" for SystemCallErrorNumber=. In addition to errno code, allow specifying "kill" as action for SystemCallFilter=. --- v7: seccomp_parse_errno_or_action() returns -EINVAL if !HAVE_SECCOMP v6: use streq_ptr(), let errno_to_name() handle bad values, kill processes, init syscall_errno v5: actually use seccomp_errno_or_action_to_string(), don't fail bus unit parsing without seccomp v4: fix build without seccomp v3: drop log action v2: action -> number --- man/systemd.exec.xml | 8 +++--- src/basic/parse-util.c | 7 +++++- src/basic/parse-util.h | 2 ++ src/core/dbus-execute.c | 4 +-- src/core/execute.c | 18 +++++++++---- src/core/load-fragment.c | 4 +-- src/shared/bus-unit-util.c | 10 ++++++-- src/shared/seccomp-util.c | 4 ++- src/shared/seccomp-util.h | 25 +++++++++++++++++++ src/test/test-execute.c | 2 ++ src/test/test-parse-util.c | 12 ++++++++- ...emcallfilter-override-error-action.service | 8 ++++++ ...mcallfilter-override-error-action2.service | 8 ++++++ 13 files changed, 95 insertions(+), 17 deletions(-) create mode 100644 test/test-execute/exec-systemcallfilter-override-error-action.service create mode 100644 test/test-execute/exec-systemcallfilter-override-error-action2.service diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 8be6a1aadd3..46fa9008942 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. diff --git a/src/basic/parse-util.c b/src/basic/parse-util.c index 44f0438cf46..818c9054d6e 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 9a516ce5f6d..2cee65c49ae 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 8f915ac2f51..05d46520af9 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) @@ -1424,7 +1424,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); diff --git a/src/core/execute.c b/src/core/execute.c index 50294a506f0..d9fdebcd708 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -1465,7 +1465,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; @@ -4675,6 +4675,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 +5477,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 +5520,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/load-fragment.c b/src/core/load-fragment.c index d3919adddfa..ae361b60206 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -3264,9 +3264,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; } diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c index c72c9791c00..eb62e1231b0 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); diff --git a/src/shared/seccomp-util.c b/src/shared/seccomp-util.c index 10f78d6c2c6..0b7cdbaadf5 100644 --- a/src/shared/seccomp-util.c +++ b/src/shared/seccomp-util.c @@ -1071,7 +1071,9 @@ 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(); + 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 b62ee7c4484..ff3b96df4bb 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/test/test-execute.c b/src/test/test-execute.c index e32e0c0b6c4..b5b93b52839 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 3ca5e1e639c..3806c3f8cf9 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 00000000000..3569b4500c6 --- /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 00000000000..04bfd6bfcbd --- /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