seccomp: handle arch inversion II

LXC generates and loads the seccomp-bpf filter in the host/container which
spawn the new container. In other words, userspace N is responsible for
generating and loading the seccomp-bpf filter which restricts userspace N + 1.
Assume 64bit kernel and 32bit userspace running a 64bit container. In this case
the 32-bit x86 userspace is used to create a seccomp-bpf filter for a 64-bit
userspace. Unless one explicitly adds the 64-bit ABI to the libseccomp filter,
or adjusts the default behavior for "BAD_ARCH", *all* 64-bit x86 syscalls will
be blocked.

Signed-off-by: Christian Brauner <christian.brauner@ubuntu.com>
Suggested-by: Paul Moore <paul@paul-moore.com>
This commit is contained in:
Christian Brauner 2018-04-18 15:20:21 +02:00
parent 7e84441ec3
commit eca6736eb0
No known key found for this signature in database
GPG Key ID: 8EB056D53EECB12D

View File

@ -319,7 +319,7 @@ int get_hostarch(void)
return lxc_seccomp_arch_unknown; return lxc_seccomp_arch_unknown;
} }
scmp_filter_ctx get_new_ctx(enum lxc_hostarch_t n_arch, uint32_t default_policy_action) scmp_filter_ctx get_new_ctx(enum lxc_hostarch_t n_arch, uint32_t default_policy_action, bool *needs_merge)
{ {
scmp_filter_ctx ctx; scmp_filter_ctx ctx;
int ret; int ret;
@ -397,7 +397,10 @@ scmp_filter_ctx get_new_ctx(enum lxc_hostarch_t n_arch, uint32_t default_policy_
return NULL; return NULL;
} }
TRACE("Removed native arch from main seccomp context"); TRACE("Removed native arch from main seccomp context");
*needs_merge = true;
} else { } else {
*needs_merge = false;
TRACE("Arch %d already present in main seccomp context", (int)n_arch); TRACE("Arch %d already present in main seccomp context", (int)n_arch);
} }
@ -490,13 +493,16 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
{ {
char *p; char *p;
int ret; int ret;
scmp_filter_ctx contexts[3] = {NULL, NULL};
bool blacklist = false; bool blacklist = false;
uint32_t default_policy_action = -1, default_rule_action = -1; uint32_t default_policy_action = -1, default_rule_action = -1;
enum lxc_hostarch_t native_arch = get_hostarch(), enum lxc_hostarch_t native_arch = get_hostarch(),
cur_rule_arch = native_arch; cur_rule_arch = native_arch;
uint32_t architectures[2] = {SCMP_ARCH_NATIVE, SCMP_ARCH_NATIVE};
struct seccomp_v2_rule rule; struct seccomp_v2_rule rule;
struct scmp_ctx_info {
uint32_t architectures[3];
scmp_filter_ctx contexts[3];
bool needs_merge[3];
} ctx;
if (strncmp(line, "blacklist", 9) == 0) if (strncmp(line, "blacklist", 9) == 0)
blacklist = true; blacklist = true;
@ -524,54 +530,115 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
default_rule_action = SCMP_ACT_ALLOW; default_rule_action = SCMP_ACT_ALLOW;
} }
memset(&ctx, 0, sizeof(ctx));
ctx.architectures[0] = SCMP_ARCH_NATIVE;
ctx.architectures[1] = SCMP_ARCH_NATIVE;
ctx.architectures[2] = SCMP_ARCH_NATIVE;
if (native_arch == lxc_seccomp_arch_amd64) { if (native_arch == lxc_seccomp_arch_amd64) {
cur_rule_arch = lxc_seccomp_arch_all; cur_rule_arch = lxc_seccomp_arch_all;
architectures[0] = SCMP_ARCH_X86;
contexts[0] = get_new_ctx(lxc_seccomp_arch_i386, ctx.architectures[0] = SCMP_ARCH_X86;
default_policy_action); ctx.contexts[0] = get_new_ctx(lxc_seccomp_arch_i386,
architectures[1] = SCMP_ARCH_X32; default_policy_action,
contexts[1] = get_new_ctx(lxc_seccomp_arch_x32, &ctx.needs_merge[0]);
default_policy_action); if (!ctx.contexts[0])
if (!contexts[0] || !contexts[1]) goto bad;
ctx.architectures[1] = SCMP_ARCH_X32;
ctx.contexts[1] = get_new_ctx(lxc_seccomp_arch_x32,
default_policy_action,
&ctx.needs_merge[1]);
if (!ctx.contexts[1])
goto bad;
ctx.architectures[2] = SCMP_ARCH_X86_64;
ctx.contexts[2] = get_new_ctx(lxc_seccomp_arch_amd64,
default_policy_action,
&ctx.needs_merge[2]);
if (!ctx.contexts[2])
goto bad; goto bad;
#ifdef SCMP_ARCH_PPC #ifdef SCMP_ARCH_PPC
} else if (native_arch == lxc_seccomp_arch_ppc64) { } else if (native_arch == lxc_seccomp_arch_ppc64) {
cur_rule_arch = lxc_seccomp_arch_all; cur_rule_arch = lxc_seccomp_arch_all;
architectures[0] = SCMP_ARCH_PPC;
contexts[0] = get_new_ctx(lxc_seccomp_arch_ppc, ctx.architectures[0] = SCMP_ARCH_PPC;
default_policy_action); ctx.contexts[0] = get_new_ctx(lxc_seccomp_arch_ppc,
if (!contexts[0]) default_policy_action,
&ctx.needs_merge[0]);
if (!ctx.contexts[0])
goto bad;
ctx.architectures[2] = SCMP_ARCH_PPC64;
ctx.contexts[2] = get_new_ctx(lxc_seccomp_arch_ppc64,
default_policy_action,
&ctx.needs_merge[2]);
if (!ctx.contexts[2])
goto bad; goto bad;
#endif #endif
#ifdef SCMP_ARCH_ARM #ifdef SCMP_ARCH_ARM
} else if (native_arch == lxc_seccomp_arch_arm64) { } else if (native_arch == lxc_seccomp_arch_arm64) {
cur_rule_arch = lxc_seccomp_arch_all; cur_rule_arch = lxc_seccomp_arch_all;
architectures[0] = SCMP_ARCH_ARM;
contexts[0] = get_new_ctx(lxc_seccomp_arch_arm, ctx.architectures[0] = SCMP_ARCH_ARM;
default_policy_action); ctx.contexts[0] = get_new_ctx(lxc_seccomp_arch_arm,
if (!contexts[0]) default_policy_action,
&ctx.needs_merge[0]);
if (!ctx.contexts[0])
goto bad;
ctx.architectures[2] = SCMP_ARCH_AARCH64;
ctx.contexts[2] = get_new_ctx(lxc_seccomp_arch_arm64,
default_policy_action,
&ctx.needs_merge[2]);
if (!ctx.contexts[2])
goto bad; goto bad;
#endif #endif
#ifdef SCMP_ARCH_MIPS #ifdef SCMP_ARCH_MIPS
} else if (native_arch == lxc_seccomp_arch_mips64) { } else if (native_arch == lxc_seccomp_arch_mips64) {
cur_rule_arch = lxc_seccomp_arch_all; cur_rule_arch = lxc_seccomp_arch_all;
architectures[0] = SCMP_ARCH_MIPS;
architectures[1] = SCMP_ARCH_MIPS64N32; ctx.architectures[0] = SCMP_ARCH_MIPS;
contexts[0] = get_new_ctx(lxc_seccomp_arch_mips, ctx.contexts[0] = get_new_ctx(lxc_seccomp_arch_mips,
default_policy_action); default_policy_action,
contexts[1] = get_new_ctx(lxc_seccomp_arch_mips64n32, &ctx.needs_merge[0]);
default_policy_action); if (!ctx.contexts[0])
if (!contexts[0] || !contexts[1]) goto bad;
ctx.architectures[1] = SCMP_ARCH_MIPS64N32;
ctx.contexts[1] = get_new_ctx(lxc_seccomp_arch_mips64n32,
default_policy_action,
&ctx.needs_merge[1]);
if (!ctx.contexts[1])
goto bad;
ctx.architectures[2] = SCMP_ARCH_MIPS64;
ctx.contexts[2] = get_new_ctx(lxc_seccomp_arch_mips64,
default_policy_action,
&ctx.needs_merge[2]);
if (!ctx.contexts[2])
goto bad; goto bad;
} else if (native_arch == lxc_seccomp_arch_mipsel64) { } else if (native_arch == lxc_seccomp_arch_mipsel64) {
cur_rule_arch = lxc_seccomp_arch_all; cur_rule_arch = lxc_seccomp_arch_all;
architectures[0] = SCMP_ARCH_MIPSEL;
architectures[1] = SCMP_ARCH_MIPSEL64N32; ctx.architectures[0] = SCMP_ARCH_MIPSEL;
contexts[0] = get_new_ctx(lxc_seccomp_arch_mipsel, ctx.contexts[0] = get_new_ctx(lxc_seccomp_arch_mipsel,
default_policy_action); default_policy_action,
contexts[1] = get_new_ctx(lxc_seccomp_arch_mipsel64n32, &ctx.needs_merge[0]);
default_policy_action); if (!ctx.contexts[0])
if (!contexts[0] || !contexts[1]) goto bad;
ctx.architectures[1] = SCMP_ARCH_MIPSEL64N32;
ctx.contexts[1] = get_new_ctx(lxc_seccomp_arch_mipsel64n32,
default_policy_action,
&ctx.needs_merge[1]);
if (!ctx.contexts[1])
goto bad;
ctx.architectures[2] = SCMP_ARCH_MIPSEL64;
ctx.contexts[2] = get_new_ctx(lxc_seccomp_arch_mipsel64,
default_policy_action,
&ctx.needs_merge[2]);
if (!ctx.contexts[2])
goto bad; goto bad;
#endif #endif
} }
@ -756,61 +823,45 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
goto bad_rule; goto bad_rule;
} }
if (cur_rule_arch == native_arch || if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line,
cur_rule_arch == lxc_seccomp_arch_native || conf->seccomp_ctx, &rule))
architectures[0] == SCMP_ARCH_NATIVE) { goto bad_rule;
if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line, INFO("Added native rule for arch %d for %s action %d(%s)",
conf->seccomp_ctx, &rule)) SCMP_ARCH_NATIVE, line, rule.action,
get_action_name(rule.action));
if (ctx.architectures[0] != SCMP_ARCH_NATIVE) {
if (!do_resolve_add_rule(ctx.architectures[0], line,
ctx.contexts[0], &rule))
goto bad_rule;
INFO("Added compat rule for arch %d for %s action %d(%s)",
ctx.architectures[0], line, rule.action,
get_action_name(rule.action));
}
if (ctx.architectures[1] != SCMP_ARCH_NATIVE) {
if (!do_resolve_add_rule(ctx.architectures[1], line,
ctx.contexts[1], &rule))
goto bad_rule;
INFO("Added compat rule for arch %d for %s action %d(%s)",
ctx.architectures[1], line, rule.action,
get_action_name(rule.action));
}
if (ctx.architectures[2] != SCMP_ARCH_NATIVE) {
if (!do_resolve_add_rule(ctx.architectures[2], line,
ctx.contexts[2], &rule))
goto bad_rule; goto bad_rule;
INFO("Added native rule for arch %d for %s action %d(%s)", INFO("Added native rule for arch %d for %s action %d(%s)",
SCMP_ARCH_NATIVE, line, rule.action, ctx.architectures[2], line, rule.action,
get_action_name(rule.action)); get_action_name(rule.action));
} else if (cur_rule_arch != lxc_seccomp_arch_all) {
int arch_index = 0;
if ((cur_rule_arch == lxc_seccomp_arch_mips64n32) ||
(cur_rule_arch == lxc_seccomp_arch_mipsel64n32))
arch_index = 1;
if (!do_resolve_add_rule(architectures[arch_index], line,
contexts[arch_index], &rule))
goto bad_rule;
INFO("Added compat-only rule for arch %d for %s action %d(%s)",
architectures[arch_index], line, rule.action,
get_action_name(rule.action));
} else {
if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line,
conf->seccomp_ctx, &rule))
goto bad_rule;
INFO("Added native rule for arch %d for %s action %d(%s)",
SCMP_ARCH_NATIVE, line, rule.action,
get_action_name(rule.action));
if (architectures[0] != SCMP_ARCH_NATIVE) {
if (!do_resolve_add_rule(architectures[0], line,
contexts[0], &rule))
goto bad_rule;
INFO("Added compat rule for arch %d for %s action %d(%s)",
architectures[0], line, rule.action,
get_action_name(rule.action));
}
if (architectures[1] != SCMP_ARCH_NATIVE) {
if (!do_resolve_add_rule(architectures[1], line,
contexts[1], &rule))
goto bad_rule;
INFO("Added compat rule for arch %d for %s action %d(%s)",
architectures[1], line, rule.action,
get_action_name(rule.action));
}
} }
} }
INFO("Merging compat seccomp contexts into main context"); INFO("Merging compat seccomp contexts into main context");
if (contexts[0]) { if (ctx.contexts[0]) {
if ((architectures[0] != native_arch) && if (ctx.needs_merge[0]) {
(architectures[0] != seccomp_arch_native())) { ret = seccomp_merge(conf->seccomp_ctx, ctx.contexts[0]);
ret = seccomp_merge(conf->seccomp_ctx, contexts[0]);
if (ret < 0) { if (ret < 0) {
ERROR("Failed to merge first compat seccomp " ERROR("Failed to merge first compat seccomp "
"context into main context"); "context into main context");
@ -818,15 +869,14 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
} }
TRACE("Merged first compat seccomp context into main context"); TRACE("Merged first compat seccomp context into main context");
} else { } else {
seccomp_release(contexts[0]); seccomp_release(ctx.contexts[0]);
contexts[0] = NULL; ctx.contexts[0] = NULL;
} }
} }
if (contexts[1]) { if (ctx.contexts[1]) {
if ((architectures[1] != native_arch) && if (ctx.needs_merge[1]) {
(architectures[1] != seccomp_arch_native())) { ret = seccomp_merge(conf->seccomp_ctx, ctx.contexts[1]);
ret = seccomp_merge(conf->seccomp_ctx, contexts[1]);
if (ret < 0) { if (ret < 0) {
ERROR("Failed to merge first compat seccomp " ERROR("Failed to merge first compat seccomp "
"context into main context"); "context into main context");
@ -834,8 +884,23 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
} }
TRACE("Merged second compat seccomp context into main context"); TRACE("Merged second compat seccomp context into main context");
} else { } else {
seccomp_release(contexts[1]); seccomp_release(ctx.contexts[1]);
contexts[1] = NULL; ctx.contexts[1] = NULL;
}
}
if (ctx.contexts[2]) {
if (ctx.needs_merge[2]) {
ret = seccomp_merge(conf->seccomp_ctx, ctx.contexts[2]);
if (ret < 0) {
ERROR("Failed to merge third compat seccomp "
"context into main context");
goto bad;
}
TRACE("Merged third compat seccomp context into main context");
} else {
seccomp_release(ctx.contexts[2]);
ctx.contexts[2] = NULL;
} }
} }
@ -845,10 +910,13 @@ bad_arch:
ERROR("Unsupported arch: %s.", line); ERROR("Unsupported arch: %s.", line);
bad_rule: bad_rule:
bad: bad:
if (contexts[0]) if (ctx.contexts[0])
seccomp_release(contexts[0]); seccomp_release(ctx.contexts[0]);
if (contexts[1]) if (ctx.contexts[1])
seccomp_release(contexts[1]); seccomp_release(ctx.contexts[1]);
if (ctx.contexts[2])
seccomp_release(ctx.contexts[2]);
return -1; return -1;
} }
#else /* HAVE_DECL_SECCOMP_SYSCALL_RESOLVE_NAME_ARCH */ #else /* HAVE_DECL_SECCOMP_SYSCALL_RESOLVE_NAME_ARCH */