From: Serge Hallyn Date: Wed, 12 Feb 2014 21:50:20 +0000 (-0600) Subject: seccomp: introduce v2 policy (v2) X-Git-Tag: lxc-1.0.0.rc1~28 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=507981388495a0bb338178e48b993bae753841d0;p=thirdparty%2Flxc.git seccomp: introduce v2 policy (v2) v2 allows specifying system calls by name, and specifying architecture. A policy looks like: 2 whitelist open read write close mount [x86] open read Also use SCMP_ACT_KILL by default rather than SCMP_ACT_ERRNO(31) - which confusingly returns 'EMLINK' on x86_64. Note this change is also done for v1 as I think it is worthwhile. With this patch, I can in fact use a seccomp policy like: 2 blacklist mknod errno 0 after which 'sudo mknod null c 1 3' silently succeeds without creating the null device. changelog v2: add blacklist support support default action support per-rule action Signed-off-by: Serge Hallyn Acked-by: Stéphane Graber --- diff --git a/src/lxc/seccomp.c b/src/lxc/seccomp.c index ea23b3ab4..4c01be73b 100644 --- a/src/lxc/seccomp.c +++ b/src/lxc/seccomp.c @@ -34,6 +34,233 @@ lxc_log_define(lxc_seccomp, lxc); +static int parse_config_v1(FILE *f, struct lxc_conf *conf) +{ + char line[1024]; + int ret; + + while (fgets(line, 1024, f)) { + int nr; + ret = sscanf(line, "%d", &nr); + if (ret != 1) + return -1; + ret = seccomp_rule_add( +#if HAVE_SCMP_FILTER_CTX + conf->seccomp_ctx, +#endif + SCMP_ACT_ALLOW, nr, 0); + if (ret < 0) { + ERROR("failed loading allow rule for %d", nr); + return ret; + } + } + return 0; +} + +static void remove_trailing_newlines(char *l) +{ + char *p = l; + + while (*p) + p++; + while (--p >= l && *p == '\n') + *p = '\0'; +} + +static uint32_t get_v2_default_action(char *line) +{ + uint32_t ret_action = -1; + + while (*line == ' ') line++; + // after 'whitelist' or 'blacklist' comes default behavior + if (strncmp(line, "kill", 4) == 0) + ret_action = SCMP_ACT_KILL; + else if (strncmp(line, "errno", 5) == 0) { + int e; + if (sscanf(line+5, "%d", &e) != 1) { + ERROR("Bad errno value in %s", line); + return -2; + } + ret_action = SCMP_ACT_ERRNO(e); + } else if (strncmp(line, "allow", 5) == 0) + ret_action = SCMP_ACT_ALLOW; + else if (strncmp(line, "trap", 4) == 0) + ret_action = SCMP_ACT_TRAP; + return ret_action; +} + +static uint32_t get_and_clear_v2_action(char *line, uint32_t def_action) +{ + char *p = strchr(line, ' '); + uint32_t ret; + + if (!p) + return def_action; + *p = '\0'; + p++; + while (*p == ' ') + p++; + if (!*p || *p == '#') + return def_action; + ret = get_v2_default_action(p); + switch(ret) { + case -2: return -1; + case -1: return def_action; + default: return ret; + } +} + +/* + * v2 consists of + * [x86] + * open + * read + * write + * close + * # a comment + * [x86_64] + * open + * read + * write + * close + */ +static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf) +{ +#if HAVE_SCMP_FILTER_CTX + char *p; + int ret; + scmp_filter_ctx *ctx = NULL; + bool blacklist = false; + uint32_t default_policy_action = -1, default_rule_action = -1, action; + uint32_t arch = SCMP_ARCH_NATIVE; + + if (strncmp(line, "blacklist", 9) == 0) + blacklist = true; + else if (strncmp(line, "whitelist", 9) != 0) { + ERROR("Bad seccomp policy style: %s", line); + return -1; + } + + if ((p = strchr(line, ' '))) { + default_policy_action = get_v2_default_action(p+1); + if (default_policy_action == -2) + return -1; + } + + /* for blacklist, allow any syscall which has no rule */ + if (blacklist) { + if (default_policy_action == -1) + default_policy_action = SCMP_ACT_ALLOW; + if (default_rule_action == -1) + default_rule_action = SCMP_ACT_KILL; + } else { + if (default_policy_action == -1) + default_policy_action = SCMP_ACT_KILL; + if (default_rule_action == -1) + default_rule_action = SCMP_ACT_ALLOW; + } + + if (default_policy_action != SCMP_ACT_KILL) { + ret = seccomp_reset(conf->seccomp_ctx, default_policy_action); + if (ret != 0) { + ERROR("Error re-initializing seccomp"); + return -1; + } + if (seccomp_attr_set(conf->seccomp_ctx, SCMP_FLTATR_CTL_NNP, 0)) { + ERROR("failed to turn off n-new-privs"); + return -1; + } + } + + while (fgets(line, 1024, f)) { + int nr; + + if (line[0] == '#') + continue; + if (strlen(line) == 0) + continue; + remove_trailing_newlines(line); + INFO("processing: .%s.", line); + if (line[0] == '[') { + // read the architecture for next set of rules + if (strcmp(line, "[x86]") == 0 || + strcmp(line, "[X86]") == 0) + arch = SCMP_ARCH_X86; + else if (strcmp(line, "[X86_64]") == 0 || + strcmp(line, "[x86_64]") == 0) + arch = SCMP_ARCH_X86_64; + else if (strcmp(line, "[arm]") == 0 || + strcmp(line, "[ARM]") == 0) + arch = SCMP_ARCH_ARM; + else + goto bad_arch; + if (ctx) { + ERROR("Only two arch sections per policy supported"); + goto bad_arch; + } + if ((ctx = seccomp_init(default_policy_action)) == NULL) { + ERROR("Error initializing seccomp context"); + return -1; + } + if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0)) { + ERROR("failed to turn off n-new-privs"); + seccomp_release(ctx); + return -1; + } + ret = seccomp_arch_add(ctx, arch); + if (ret == -EEXIST) { + seccomp_release(ctx); + ctx = NULL; + continue; + } + if (ret != 0) { + ERROR("Error %d adding arch: %s", ret, line); + goto bad_arch; + } + if (seccomp_arch_remove(ctx, SCMP_ARCH_NATIVE) != 0) { + ERROR("Error removing native arch from %s", line); + goto bad_arch; + } + continue; + } + + action = get_and_clear_v2_action(line, default_rule_action); + if (action == -1) { + ERROR("Failed to interpret action"); + goto bad_rule; + } + nr = seccomp_syscall_resolve_name_arch(arch, line); + if (nr < 0) { + ERROR("Failed to resolve syscall: %s", line); + goto bad_rule; + } + ret = seccomp_rule_add(ctx ? ctx : conf->seccomp_ctx, + action, nr, 0); + if (ret < 0) { + ERROR("failed (%d) loading rule for %s", ret, line); + goto bad_rule; + } + } + if (ctx) { + if (seccomp_merge(conf->seccomp_ctx, ctx) != 0) { + seccomp_release(ctx); + ERROR("Error merging seccomp contexts"); + return -1; + } + } + return 0; + +bad_arch: + ERROR("Unsupported arch: %s", line); +bad_rule: + if (ctx) + seccomp_release(ctx); + return -1; +#else + return -1; +#endif +} + /* * The first line of the config file has a policy language version * the second line has some directives @@ -48,7 +275,7 @@ static int parse_config(FILE *f, struct lxc_conf *conf) int ret, version; ret = fscanf(f, "%d\n", &version); - if (ret != 1 || version != 1) { + if (ret != 1 || (version != 1 && version != 2)) { ERROR("invalid version"); return -1; } @@ -56,31 +283,19 @@ static int parse_config(FILE *f, struct lxc_conf *conf) ERROR("invalid config file"); return -1; } - if (!strstr(line, "whitelist")) { + if (version == 1 && !strstr(line, "whitelist")) { ERROR("only whitelist policy is supported"); return -1; } + if (strstr(line, "debug")) { ERROR("debug not yet implemented"); return -1; } - /* now read in the whitelist entries one per line */ - while (fgets(line, 1024, f)) { - int nr; - ret = sscanf(line, "%d", &nr); - if (ret != 1) - return -1; - ret = seccomp_rule_add( -#if HAVE_SCMP_FILTER_CTX - conf->seccomp_ctx, -#endif - SCMP_ACT_ALLOW, nr, 0); - if (ret < 0) { - ERROR("failed loading allow rule for %d", nr); - return ret; - } - } - return 0; + + if (version == 1) + return parse_config_v1(f, conf); + return parse_config_v2(f, line, conf); } int lxc_read_seccomp_config(struct lxc_conf *conf) @@ -93,10 +308,10 @@ int lxc_read_seccomp_config(struct lxc_conf *conf) #if HAVE_SCMP_FILTER_CTX /* XXX for debug, pass in SCMP_ACT_TRAP */ - conf->seccomp_ctx = seccomp_init(SCMP_ACT_ERRNO(31)); + conf->seccomp_ctx = seccomp_init(SCMP_ACT_KILL); ret = !conf->seccomp_ctx; #else - ret = seccomp_init(SCMP_ACT_ERRNO(31)) < 0; + ret = seccomp_init(SCMP_ACT_KILL) < 0; #endif if (ret) { ERROR("failed initializing seccomp");