summaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorLennart Poettering <lennart@poettering.net>2014-02-25 20:37:03 +0100
committerLennart Poettering <lennart@poettering.net>2014-02-26 02:19:28 +0100
commit4298d0b5128326621c8f537107c4c8b459490721 (patch)
tree38ed9f6806b45d153f39ea9da61eae5d34530523 /src/core
parent9875fd7875d433eea5c6e3319916e1be18722086 (diff)
core: add new RestrictAddressFamilies= switch
This new unit settings allows restricting which address families are available to processes. This is an effective way to minimize the attack surface of services, by turning off entire network stacks for them. This is based on seccomp, and does not work on x86-32, since seccomp cannot filter socketcall() syscalls on that platform.
Diffstat (limited to 'src/core')
-rw-r--r--src/core/dbus-execute.c50
-rw-r--r--src/core/execute.c135
-rw-r--r--src/core/execute.h3
-rw-r--r--src/core/load-fragment-gperf.gperf.m46
-rw-r--r--src/core/load-fragment.c78
-rw-r--r--src/core/load-fragment.h1
6 files changed, 269 insertions, 4 deletions
diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c
index 935c62bdf2..02e2a6d3df 100644
--- a/src/core/dbus-execute.c
+++ b/src/core/dbus-execute.c
@@ -34,6 +34,7 @@
#include "dbus-execute.h"
#include "capability.h"
#include "env-util.h"
+#include "af-list.h"
#ifdef HAVE_SECCOMP
#include "seccomp-util.h"
@@ -518,6 +519,54 @@ static int property_get_personality(
return sd_bus_message_append(reply, "s", personality_to_string(c->personality));
}
+static int property_get_address_families(
+ 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;
+ Iterator i;
+ void *af;
+ int r;
+
+ 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->address_families_whitelist);
+ if (r < 0)
+ return r;
+
+ SET_FOREACH(af, c->address_families, i) {
+ const char *name;
+
+ name = af_to_name(PTR_TO_INT(af));
+ if (!name)
+ continue;
+
+ r = strv_extend(&l, name);
+ if (r < 0)
+ return -ENOMEM;
+ }
+
+ strv_sort(l);
+
+ r = sd_bus_message_append_strv(reply, l);
+ if (r < 0)
+ return r;
+
+ return sd_bus_message_close_container(reply);
+}
+
const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_PROPERTY("Environment", "as", NULL, offsetof(ExecContext, environment), SD_BUS_VTABLE_PROPERTY_CONST),
@@ -585,6 +634,7 @@ const sd_bus_vtable bus_exec_vtable[] = {
SD_BUS_PROPERTY("SystemCallArchitectures", "as", property_get_syscall_archs, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("SystemCallErrorNumber", "i", property_get_syscall_errno, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_PROPERTY("Personality", "s", property_get_personality, 0, SD_BUS_VTABLE_PROPERTY_CONST),
+ SD_BUS_PROPERTY("RestrictAddressFamilies", "(bas)", property_get_address_families, 0, SD_BUS_VTABLE_PROPERTY_CONST),
SD_BUS_VTABLE_END
};
diff --git a/src/core/execute.c b/src/core/execute.c
index aeddd2e412..fff25c2b23 100644
--- a/src/core/execute.c
+++ b/src/core/execute.c
@@ -81,6 +81,7 @@
#include "async.h"
#include "selinux-util.h"
#include "errno-list.h"
+#include "af-list.h"
#include "apparmor-util.h"
#ifdef HAVE_SECCOMP
@@ -994,9 +995,130 @@ static int apply_seccomp(ExecContext *c) {
finish:
seccomp_release(seccomp);
+ return r;
+}
+
+static int apply_address_families(ExecContext *c) {
+ scmp_filter_ctx *seccomp;
+ Iterator i;
+ int r;
+
+ assert(c);
+
+ seccomp = seccomp_init(SCMP_ACT_ALLOW);
+ if (!seccomp)
+ return -ENOMEM;
+
+ r = seccomp_add_secondary_archs(seccomp);
+ if (r < 0)
+ goto finish;
+
+ if (c->address_families_whitelist) {
+ int af, first = 0, last = 0;
+ void *afp;
+
+ /* If this is a whitelist, we first block the address
+ * families that are out of range and then everything
+ * that is not in the set. First, we find the lowest
+ * and highest address family in the set. */
+
+ SET_FOREACH(afp, c->address_families, i) {
+ af = PTR_TO_INT(afp);
+ if (af <= 0 || af >= af_max())
+ continue;
+
+ if (first == 0 || af < first)
+ first = af;
+
+ if (last == 0 || af > last)
+ last = af;
+ }
+
+ assert((first == 0) == (last == 0));
+
+ if (first == 0) {
+
+ /* No entries in the valid range, block everything */
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPROTONOSUPPORT),
+ SCMP_SYS(socket),
+ 0);
+ if (r < 0)
+ goto finish;
+
+ } else {
+
+ /* Block everything below the first entry */
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPROTONOSUPPORT),
+ SCMP_SYS(socket),
+ 1,
+ SCMP_A0(SCMP_CMP_LT, first));
+ if (r < 0)
+ goto finish;
+
+ /* Block everything above the last entry */
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPROTONOSUPPORT),
+ SCMP_SYS(socket),
+ 1,
+ SCMP_A0(SCMP_CMP_GT, last));
+ if (r < 0)
+ goto finish;
+
+ /* Block everything between the first and last
+ * entry */
+ for (af = 1; af < af_max(); af++) {
+
+ if (set_contains(c->address_families, INT_TO_PTR(af)))
+ continue;
+
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPROTONOSUPPORT),
+ SCMP_SYS(socket),
+ 1,
+ SCMP_A0(SCMP_CMP_EQ, af));
+ if (r < 0)
+ goto finish;
+ }
+ }
+
+ } else {
+ void *af;
+
+ /* If this is a blacklist, then generate one rule for
+ * each address family that are then combined in OR
+ * checks. */
+
+ SET_FOREACH(af, c->address_families, i) {
+
+ r = seccomp_rule_add(
+ seccomp,
+ SCMP_ACT_ERRNO(EPROTONOSUPPORT),
+ SCMP_SYS(socket),
+ 1,
+ SCMP_A0(SCMP_CMP_EQ, PTR_TO_INT(af)));
+ if (r < 0)
+ goto finish;
+ }
+ }
+
+ r = seccomp_attr_set(seccomp, SCMP_FLTATR_CTL_NNP, 0);
+ if (r < 0)
+ goto finish;
+
+ r = seccomp_load(seccomp);
+
+finish:
+ seccomp_release(seccomp);
return r;
}
+
#endif
static void do_idle_pipe_dance(int idle_pipe[4]) {
@@ -1584,6 +1706,14 @@ int exec_spawn(ExecCommand *command,
}
#ifdef HAVE_SECCOMP
+ if (context->address_families) {
+ err = apply_address_families(context);
+ if (err < 0) {
+ r = EXIT_ADDRESS_FAMILIES;
+ goto fail_child;
+ }
+ }
+
if (context->syscall_filter || context->syscall_archs) {
err = apply_seccomp(context);
if (err < 0) {
@@ -1777,13 +1907,14 @@ void exec_context_done(ExecContext *c) {
free(c->apparmor_profile);
c->apparmor_profile = NULL;
-#ifdef HAVE_SECCOMP
set_free(c->syscall_filter);
c->syscall_filter = NULL;
set_free(c->syscall_archs);
c->syscall_archs = NULL;
-#endif
+
+ set_free(c->address_families);
+ c->address_families = NULL;
}
void exec_command_done(ExecCommand *c) {
diff --git a/src/core/execute.h b/src/core/execute.h
index 2bfe227145..3c905cefae 100644
--- a/src/core/execute.h
+++ b/src/core/execute.h
@@ -178,6 +178,9 @@ struct ExecContext {
int syscall_errno;
bool syscall_whitelist:1;
+ Set *address_families;
+ bool address_families_whitelist:1;
+
bool oom_score_adjust_set:1;
bool nice_set:1;
bool ioprio_set:1;
diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4
index 26146b1927..beff290251 100644
--- a/src/core/load-fragment-gperf.gperf.m4
+++ b/src/core/load-fragment-gperf.gperf.m4
@@ -52,10 +52,12 @@ $1.NoNewPrivileges, config_parse_bool, 0,
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.SystemCallErrorNumber, config_parse_syscall_errno, 0, offsetof($1, exec_context)
+$1.RestrictAddressFamilies, config_parse_address_families, 0, offsetof($1, exec_context)',
`$1.SystemCallFilter, config_parse_warn_compat, 0, 0
$1.SystemCallArchitectures, config_parse_warn_compat, 0, 0
-$1.SystemCallErrorNumber, config_parse_warn_compat, 0, 0')
+$1.SystemCallErrorNumber, config_parse_warn_compat, 0, 0
+$1.RestrictAddressFamilies, config_parse_warn_compat, 0, 0')
$1.LimitCPU, config_parse_limit, RLIMIT_CPU, offsetof($1, exec_context.rlimit)
$1.LimitFSIZE, config_parse_limit, RLIMIT_FSIZE, offsetof($1, exec_context.rlimit)
$1.LimitDATA, config_parse_limit, RLIMIT_DATA, offsetof($1, exec_context.rlimit)
diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c
index 82aed1eb92..478d22c4b5 100644
--- a/src/core/load-fragment.c
+++ b/src/core/load-fragment.c
@@ -56,6 +56,7 @@
#include "bus-util.h"
#include "bus-error.h"
#include "errno-list.h"
+#include "af-list.h"
#ifdef HAVE_SECCOMP
#include "seccomp-util.h"
@@ -2216,6 +2217,81 @@ int config_parse_syscall_errno(
c->syscall_errno = e;
return 0;
}
+
+int config_parse_address_families(
+ 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;
+ Unit *u = userdata;
+ bool invert = false;
+ char *w, *state;
+ size_t l;
+ int r;
+
+ assert(filename);
+ assert(lvalue);
+ assert(rvalue);
+ assert(u);
+
+ if (isempty(rvalue)) {
+ /* Empty assignment resets the list */
+ set_free(c->address_families);
+ c->address_families = NULL;
+ c->address_families_whitelist = false;
+ return 0;
+ }
+
+ if (rvalue[0] == '~') {
+ invert = true;
+ rvalue++;
+ }
+
+ if (!c->address_families) {
+ c->address_families = set_new(trivial_hash_func, trivial_compare_func);
+ if (!c->address_families)
+ return log_oom();
+
+ c->address_families_whitelist = !invert;
+ }
+
+ FOREACH_WORD_QUOTED(w, l, rvalue, state) {
+ _cleanup_free_ char *t = NULL;
+ int af;
+
+ t = strndup(w, l);
+ if (!t)
+ return log_oom();
+
+ af = af_from_name(t);
+ if (af <= 0) {
+ log_syntax(unit, LOG_ERR, filename, line, EINVAL, "Failed to parse address family, ignoring: %s", t);
+ continue;
+ }
+
+ /* If we previously wanted to forbid an address family and now
+ * we want to allow it, then remove it from the list
+ */
+ if (!invert == c->address_families_whitelist) {
+ r = set_put(c->address_families, INT_TO_PTR(af));
+ if (r == -EEXIST)
+ continue;
+ if (r < 0)
+ return log_oom();
+ } else
+ set_remove(c->address_families, INT_TO_PTR(af));
+ }
+
+ return 0;
+}
#endif
int config_parse_unit_slice(
@@ -3024,6 +3100,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_address_families, "FAMILIES" },
#endif
{ config_parse_cpu_shares, "SHARES" },
{ config_parse_memory_limit, "LIMIT" },
@@ -3039,6 +3116,7 @@ void unit_dump_config_items(FILE *f) {
#endif
{ config_parse_job_mode, "MODE" },
{ config_parse_job_mode_isolate, "BOOLEAN" },
+ { config_parse_personality, "PERSONALITY" },
};
const char *prev = NULL;
diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h
index 4a5ec35cbe..c5dbe6157e 100644
--- a/src/core/load-fragment.h
+++ b/src/core/load-fragment.h
@@ -90,6 +90,7 @@ int config_parse_job_mode_isolate(const char *unit, const char *filename, unsign
int config_parse_exec_selinux_context(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);
int config_parse_personality(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);
int config_parse_exec_apparmor_profile(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);
+int config_parse_address_families(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);
/* gperf prototypes */
const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length);