]> git.ipfire.org Git - thirdparty/lxc.git/commitdiff
seccomp: filter syscalls based on arguments
authorLiFeng <lifeng68@huawei.com>
Thu, 23 Nov 2017 19:15:23 +0000 (14:15 -0500)
committerChristian Brauner <christian.brauner@ubuntu.com>
Mon, 27 Nov 2017 21:07:24 +0000 (22:07 +0100)
In order to support filtering syscalls based on arguments the seccomp version 2
specification is extended to the following form:

syscall_name action [index,value,op,valueTwo] [index,value,op]...

where the arguments of the tuple [index,value,valueTwo,op] have the following
meaning:
1. index (uint32_t):
   The index of the syscall argument.
2. value (uint64_t):
   The value for the syscall argument specified by "index".
3. valueTwo (uint64_t, optional):
   The value for the syscall argument specified by "index". This optional value
   is only valid in conjunction with SCMP_CMP_MASKED_EQ.
4. op (string):
   The operator for the syscall argument. Valid operators are the constants
   - SCMP_CMP_NE        (!=)
   - SCMP_CMP_LE        (<=)
   - SCMP_CMP_EQ        (==)
   - SCMP_CMP_GE        (>=)
   - SCMP_CMP_GT        (>)
   - SCMP_CMP_MASKED_EQ (&=)
   as defined by libseccomp >= v2.3.2.
   For convenience liblxc also understands the standard operator notation
   indicated in brackets after the libseccomp constants above as an equivalent
   notation.
Note that it is legal to specify multiple entries for the same syscall.

An example for an extended seccomp version 2 profile is:

2
blacklist allow
reject_force_umount  # comment this to allow umount -f;  not recommended
[all]
kexec_load errno 1 [0,1,SCMP_CMP_LE][3,1,==][5,1,SCMP_CMP_MASKED_EQ,1]
open_by_handle_at errno 1
init_module errno 1
finit_module errno 1
delete_module errno 1
unshare errno 9 [0,0x10000000,SCMP_CMP_EQ]
unshare errno 2 [0,0x20000000,SCMP_CMP_EQ]

Closes #1564.

Signed-off-by: LiFeng <lifeng68@huawei.com>
Reviewed-by: Christian Brauner <christian.brauner@ubuntu.com>
src/lxc/seccomp.c

index deacd12173cb5e3ccfe58d4de042fa90d7118856..a4b088ed8f0334df4d6f023fb4bd3db0d37f3d03 100644 (file)
@@ -51,7 +51,7 @@ static int parse_config_v1(FILE *f, struct lxc_conf *conf)
 #endif
                    SCMP_ACT_ALLOW, nr, 0);
                if (ret < 0) {
-                       ERROR("Failed loading allow rule for %d.", nr);
+                       ERROR("Failed loading allow rule for %d", nr);
                        return ret;
                }
        }
@@ -81,7 +81,7 @@ static uint32_t get_v2_default_action(char *line)
        else if (strncmp(line, "errno", 5) == 0) {
                int e;
                if (sscanf(line + 5, "%d", &e) != 1) {
-                       ERROR("Bad errno value in %s.", line);
+                       ERROR("Bad errno value in %s", line);
                        return -2;
                }
                ret_action = SCMP_ACT_ERRNO(e);
@@ -109,14 +109,13 @@ static const char *get_action_name(uint32_t action)
        }
 }
 
-static uint32_t get_and_clear_v2_action(char *line, uint32_t def_action)
+static uint32_t get_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++;
@@ -129,6 +128,138 @@ static uint32_t get_and_clear_v2_action(char *line, uint32_t def_action)
        default: return ret;
        }
 }
+
+struct v2_rule_args {
+       uint32_t index;
+       uint64_t value;
+       uint64_t mask;
+       enum scmp_compare op;
+};
+
+struct seccomp_v2_rule {
+       uint32_t action;
+       uint32_t args_num;
+       struct v2_rule_args args_value[6];
+};
+
+static enum scmp_compare parse_v2_rule_op(char *s)
+{
+       enum scmp_compare ret;
+
+       if (strcmp(s, "SCMP_CMP_NE") == 0 || strcmp(s, "!=") == 0)
+               ret = SCMP_CMP_NE;
+       else if (strcmp(s, "SCMP_CMP_LT") == 0 || strcmp(s, "<") == 0)
+               ret = SCMP_CMP_LT;
+       else if (strcmp(s, "SCMP_CMP_LE") == 0 || strcmp(s, "<=") == 0)
+               ret = SCMP_CMP_LE;
+       else if (strcmp(s, "SCMP_CMP_EQ") == 0 || strcmp(s, "==") == 0)
+               ret = SCMP_CMP_EQ;
+       else if (strcmp(s, "SCMP_CMP_GE") == 0 || strcmp(s, ">=") == 0)
+               ret = SCMP_CMP_GE;
+       else if (strcmp(s, "SCMP_CMP_GT") == 0 || strcmp(s, ">") == 0)
+               ret = SCMP_CMP_GT;
+       else if (strcmp(s, "SCMP_CMP_MASKED_EQ") == 0 || strcmp(s, "&=") == 0)
+               ret = SCMP_CMP_MASKED_EQ;
+       else
+               ret = _SCMP_CMP_MAX;
+
+       return ret;
+}
+
+/* This function is used to parse the args string into the structure.
+ * args string format:[index,value,op,valueTwo] or [index,value,op]
+ * For one arguments, [index,value,valueTwo,op]
+ * index: the index for syscall arguments (type uint)
+ * value: the value for syscall arguments (type uint64)
+ * op: the operator for syscall arguments(string),
+        a valid list of constants as of libseccomp v2.3.2 is
+        SCMP_CMP_NE,SCMP_CMP_LE,SCMP_CMP_LE, SCMP_CMP_EQ, SCMP_CMP_GE,
+        SCMP_CMP_GT, SCMP_CMP_MASKED_EQ, or !=,<=,==,>=,>,&=
+ * valueTwo: the value for syscall arguments only used for mask eq (type uint64, optional)
+ * Returns 0 on success, < 0 otherwise.
+ */
+static int get_seccomp_arg_value(char *key, struct v2_rule_args *rule_args)
+{
+       int ret = 0;
+       uint64_t value = 0;
+       uint64_t mask = 0;
+       enum scmp_compare op = 0;
+       uint32_t index = 0;
+       char s[30] = {0};
+       char *tmp = NULL;
+
+       memset(s, 0, sizeof(s));
+       tmp = strchr(key, '[');
+       if (!tmp) {
+               ERROR("Failed to interpret args");
+               return -1;
+       }
+       ret = sscanf(tmp, "[%i,%lli,%30[^0-9^,],%lli", &index, (long long unsigned int *)&value, s, (long long unsigned int *)&mask);
+       if ((ret != 3 && ret != 4) || index >= 6) {
+               ERROR("Failed to interpret args value");
+               return -1;
+       }
+
+       op = parse_v2_rule_op(s);
+       if (op == _SCMP_CMP_MAX) {
+               ERROR("Failed to interpret args operator value");
+               return -1;
+       }
+
+       rule_args->index = index;
+       rule_args->value = value;
+       rule_args->mask = mask;
+       rule_args->op = op;
+       return 0;
+}
+
+/* This function is used to parse the seccomp rule entry.
+ * @line       : seccomp rule entry string.
+ * @def_action : default action used in the case if the 'line' contain non valid action.
+ * @rules      : output struct.
+ * Returns 0 on success, < 0 otherwise.
+ */
+static int parse_v2_rules(char *line, uint32_t def_action, struct seccomp_v2_rule *rules)
+{
+       int ret = 0 ;
+       int i = 0;
+       char *tmp = NULL;
+       char *key = NULL;
+       char *saveptr = NULL;
+
+       tmp = strdup(line);
+       if (!tmp)
+               return -1;
+
+       /* read optional action which follows the syscall */
+       rules->action = get_v2_action(tmp, def_action);
+       if (rules->action == -1) {
+               ERROR("Failed to interpret action");
+               ret = -1;
+               goto out;
+       }
+
+       rules->args_num = 0;
+       if (!strchr(tmp, '[')) {
+               ret = 0;
+               goto out;
+       }
+
+       for ((key = strtok_r(tmp, "]", &saveptr)), i = 0; key && i < 6; (key = strtok_r(NULL, "]", &saveptr)), i++) {
+               ret = get_seccomp_arg_value(key, &rules->args_value[i]);
+               if (ret < 0) {
+                       ret = -1;
+                       goto out;
+               }
+               rules->args_num++;
+       }
+
+       ret = 0;
+out:
+       free(tmp);
+       return ret;
+}
+
 #endif
 
 #if HAVE_DECL_SECCOMP_SYSCALL_RESOLVE_NAME_ARCH
@@ -165,7 +296,7 @@ int get_hostarch(void)
 {
        struct utsname uts;
        if (uname(&uts) < 0) {
-               SYSERROR("Failed to read host arch.");
+               SYSERROR("Failed to read host arch");
                return -1;
        }
        if (strcmp(uts.machine, "i686") == 0)
@@ -230,11 +361,11 @@ scmp_filter_ctx get_new_ctx(enum lxc_hostarch_t n_arch, uint32_t default_policy_
        }
 
        if ((ctx = seccomp_init(default_policy_action)) == NULL) {
-               ERROR("Error initializing seccomp context.");
+               ERROR("Error initializing seccomp context");
                return NULL;
        }
        if (seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0)) {
-               ERROR("Failed to turn off no-new-privs.");
+               ERROR("Failed to turn off no-new-privs");
                seccomp_release(ctx);
                return NULL;
        }
@@ -260,25 +391,33 @@ scmp_filter_ctx get_new_ctx(enum lxc_hostarch_t n_arch, uint32_t default_policy_
 }
 
 bool do_resolve_add_rule(uint32_t arch, char *line, scmp_filter_ctx ctx,
-                       uint32_t action)
+                       struct seccomp_v2_rule *rule)
 {
-       int nr, ret;
+       int nr, ret, i;
+       struct scmp_arg_cmp arg_cmp[6];
+
+       memset(arg_cmp, 0 ,sizeof(arg_cmp));
 
        ret = seccomp_arch_exist(ctx, arch);
        if (arch && ret != 0) {
                ERROR("BUG: Seccomp: rule and context arch do not match (arch "
-                     "%d): %s.",
+                     "%d): %s",
                      arch, strerror(-ret));
                return false;
        }
 
+       /*get the syscall name*/
+       char *p = strchr(line, ' ');
+       if (p)
+               *p = '\0';
+
        if (strncmp(line, "reject_force_umount", 19) == 0) {
-               INFO("Setting Seccomp rule to reject force umounts.");
+               INFO("Setting Seccomp rule to reject force umounts");
                ret = seccomp_rule_add_exact(ctx, SCMP_ACT_ERRNO(EACCES), SCMP_SYS(umount2),
                                1, SCMP_A1(SCMP_CMP_MASKED_EQ , MNT_FORCE , MNT_FORCE ));
                if (ret < 0) {
                        ERROR("Failed (%d) loading rule to reject force "
-                             "umount: %s.",
+                             "umount: %s",
                              ret, strerror(-ret));
                        return false;
                }
@@ -287,19 +426,33 @@ bool do_resolve_add_rule(uint32_t arch, char *line, scmp_filter_ctx ctx,
 
        nr = seccomp_syscall_resolve_name(line);
        if (nr == __NR_SCMP_ERROR) {
-               WARN("Seccomp: failed to resolve syscall: %s.", line);
-               WARN("This syscall will NOT be blacklisted.");
+               WARN("Seccomp: failed to resolve syscall: %s", line);
+               WARN("This syscall will NOT be blacklisted");
                return true;
        }
        if (nr < 0) {
-               WARN("Seccomp: got negative for syscall: %d: %s.", nr, line);
-               WARN("This syscall will NOT be blacklisted.");
+               WARN("Seccomp: got negative for syscall: %d: %s", nr, line);
+               WARN("This syscall will NOT be blacklisted");
                return true;
        }
-       ret = seccomp_rule_add_exact(ctx, action, nr, 0);
+
+       for (i = 0; i < rule->args_num; i++) {
+               INFO("arg_cmp[%d]:SCMP_CMP(%u, %llu, %llu, %llu)", i,
+                     rule->args_value[i].index,
+                     (long long unsigned int)rule->args_value[i].op,
+                     (long long unsigned int)rule->args_value[i].mask,
+                     (long long unsigned int)rule->args_value[i].value);
+
+               if (SCMP_CMP_MASKED_EQ == rule->args_value[i].op)
+                       arg_cmp[i] = SCMP_CMP(rule->args_value[i].index, rule->args_value[i].op, rule->args_value[i].mask, rule->args_value[i].value);
+               else
+                       arg_cmp[i] = SCMP_CMP(rule->args_value[i].index, rule->args_value[i].op, rule->args_value[i].value);
+       }
+
+       ret = seccomp_rule_add_exact_array(ctx, rule->action, nr, rule->args_num, arg_cmp);
        if (ret < 0) {
-               ERROR("Failed (%d) loading rule for %s (nr %d action %d(%s)): %s.",
-                     ret, line, nr, action, get_action_name(action), strerror(-ret));
+               ERROR("Failed (%d) loading rule for %s (nr %d action %d(%s)): %s",
+                     ret, line, nr, rule->action, get_action_name(rule->action), strerror(-ret));
                return false;
        }
        return true;
@@ -325,15 +478,16 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
        int ret;
        scmp_filter_ctx compat_ctx[2] = {NULL, NULL};
        bool blacklist = false;
-       uint32_t default_policy_action = -1, default_rule_action = -1, action;
+       uint32_t default_policy_action = -1, default_rule_action = -1;
        enum lxc_hostarch_t native_arch = get_hostarch(),
                            cur_rule_arch = native_arch;
        uint32_t compat_arch[2] = {SCMP_ARCH_NATIVE, SCMP_ARCH_NATIVE};
+       struct seccomp_v2_rule rule;
 
        if (strncmp(line, "blacklist", 9) == 0)
                blacklist = true;
        else if (strncmp(line, "whitelist", 9) != 0) {
-               ERROR("Bad seccomp policy style: %s.", line);
+               ERROR("Bad seccomp policy style: %s", line);
                return -1;
        }
 
@@ -411,11 +565,11 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
        if (default_policy_action != SCMP_ACT_KILL) {
                ret = seccomp_reset(conf->seccomp_ctx, default_policy_action);
                if (ret != 0) {
-                       ERROR("Error re-initializing Seccomp.");
+                       ERROR("Error re-initializing Seccomp");
                        return -1;
                }
                if (seccomp_attr_set(conf->seccomp_ctx, SCMP_FLTATR_CTL_NNP, 0)) {
-                       ERROR("Failed to turn off no-new-privs.");
+                       ERROR("Failed to turn off no-new-privs");
                        return -1;
                }
 #ifdef SCMP_FLTATR_ATL_TSKIP
@@ -432,7 +586,7 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
                if (strlen(line) == 0)
                        continue;
                remove_trailing_newlines(line);
-               INFO("processing: .%s.", line);
+               INFO("processing: .%s", line);
                if (line[0] == '[') {
                        /* Read the architecture for next set of rules. */
                        if (strcmp(line, "[x86]") == 0 ||
@@ -580,19 +734,20 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
                if (cur_rule_arch == lxc_seccomp_arch_unknown)
                        continue;
 
+               memset(&rule, 0, sizeof(rule));
                /* read optional action which follows the syscall */
-               action = get_and_clear_v2_action(line, default_rule_action);
-               if (action == -1) {
-                       ERROR("Failed to interpret action.");
+               ret = parse_v2_rules(line, default_rule_action, &rule);
+               if (ret != 0) {
+                       ERROR("Failed to interpret seccomp rule");
                        goto bad_rule;
                }
 
                if (cur_rule_arch == native_arch ||
                    cur_rule_arch == lxc_seccomp_arch_native ||
                    compat_arch[0] == SCMP_ARCH_NATIVE) {
-                       INFO("Adding native rule for %s action %d(%s).", line, action,
-                            get_action_name(action));
-                       if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line, conf->seccomp_ctx, action))
+                       INFO("Adding native rule for %s action %d(%s)", line, rule.action,
+                            get_action_name(rule.action));
+                       if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line, conf->seccomp_ctx, &rule))
                                goto bad_rule;
                }
                else if (cur_rule_arch != lxc_seccomp_arch_all) {
@@ -600,31 +755,31 @@ static int parse_config_v2(FILE *f, char *line, struct lxc_conf *conf)
                                cur_rule_arch == lxc_seccomp_arch_mips64n32 ||
                                cur_rule_arch == lxc_seccomp_arch_mipsel64n32 ? 1 : 0;
 
-                       INFO("Adding compat-only rule for %s action %d(%s).", line, action,
-                            get_action_name(action));
-                       if (!do_resolve_add_rule(compat_arch[arch_index], line, compat_ctx[arch_index], action))
+                       INFO("Adding compat-only rule for %s action %d(%s)", line, rule.action,
+                            get_action_name(rule.action));
+                       if (!do_resolve_add_rule(compat_arch[arch_index], line, compat_ctx[arch_index], &rule))
                                goto bad_rule;
                }
                else {
-                       INFO("Adding native rule for %s action %d(%s).", line, action,
-                            get_action_name(action));
-                       if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line, conf->seccomp_ctx, action))
+                       INFO("Adding native rule for %s action %d(%s)", line, rule.action,
+                            get_action_name(rule.action));
+                       if (!do_resolve_add_rule(SCMP_ARCH_NATIVE, line, conf->seccomp_ctx, &rule))
                                goto bad_rule;
-                       INFO("Adding compat rule for %s action %d(%s).", line, action,
-                            get_action_name(action));
-                       if (!do_resolve_add_rule(compat_arch[0], line, compat_ctx[0], action))
+                       INFO("Adding compat rule for %s action %d(%s)", line, rule.action,
+                            get_action_name(rule.action));
+                       if (!do_resolve_add_rule(compat_arch[0], line, compat_ctx[0], &rule))
                                goto bad_rule;
                        if (compat_arch[1] != SCMP_ARCH_NATIVE &&
-                               !do_resolve_add_rule(compat_arch[1], line, compat_ctx[1], action))
+                               !do_resolve_add_rule(compat_arch[1], line, compat_ctx[1], &rule))
                                goto bad_rule;
                }
        }
 
        if (compat_ctx[0]) {
-               INFO("Merging in the compat Seccomp ctx into the main one.");
+               INFO("Merging in the compat Seccomp ctx into the main one");
                if (seccomp_merge(conf->seccomp_ctx, compat_ctx[0]) != 0 ||
                        (compat_ctx[1] != NULL && seccomp_merge(conf->seccomp_ctx, compat_ctx[1]) != 0)) {
-                       ERROR("Error merging compat Seccomp contexts.");
+                       ERROR("Error merging compat Seccomp contexts");
                        goto bad;
                }
        }
@@ -663,20 +818,20 @@ static int parse_config(FILE *f, struct lxc_conf *conf)
 
        ret = fscanf(f, "%d\n", &version);
        if (ret != 1 || (version != 1 && version != 2)) {
-               ERROR("Invalid version.");
+               ERROR("Invalid version");
                return -1;
        }
        if (!fgets(line, 1024, f)) {
-               ERROR("Invalid config file.");
+               ERROR("Invalid config file");
                return -1;
        }
        if (version == 1 && !strstr(line, "whitelist")) {
-               ERROR("Only whitelist policy is supported.");
+               ERROR("Only whitelist policy is supported");
                return -1;
        }
 
        if (strstr(line, "debug")) {
-               ERROR("Debug not yet implemented.");
+               ERROR("Debug not yet implemented");
                return -1;
        }
 
@@ -715,11 +870,11 @@ static bool use_seccomp(void)
 
        fclose(f);
        if (!found) { /* no Seccomp line, no seccomp in kernel */
-               INFO("Seccomp is not enabled in the kernel.");
+               INFO("Seccomp is not enabled in the kernel");
                return false;
        }
        if (already_enabled) { /* already seccomp-confined */
-               INFO("Already seccomp-confined, not loading new policy.");
+               INFO("Already seccomp-confined, not loading new policy");
                return false;
        }
        return true;
@@ -744,7 +899,7 @@ int lxc_read_seccomp_config(struct lxc_conf *conf)
        ret = seccomp_init(SCMP_ACT_KILL) < 0;
 #endif
        if (ret) {
-               ERROR("Failed initializing seccomp.");
+               ERROR("Failed initializing seccomp");
                return -1;
        }
 
@@ -756,7 +911,7 @@ int lxc_read_seccomp_config(struct lxc_conf *conf)
        check_seccomp_attr_set = seccomp_attr_set(SCMP_FLTATR_CTL_NNP, 0);
 #endif
        if (check_seccomp_attr_set) {
-               ERROR("Failed to turn off no-new-privs.");
+               ERROR("Failed to turn off no-new-privs");
                return -1;
        }
 #ifdef SCMP_FLTATR_ATL_TSKIP
@@ -767,7 +922,7 @@ int lxc_read_seccomp_config(struct lxc_conf *conf)
 
        f = fopen(conf->seccomp, "r");
        if (!f) {
-               SYSERROR("Failed to open seccomp policy file %s.", conf->seccomp);
+               SYSERROR("Failed to open seccomp policy file %s", conf->seccomp);
                return -1;
        }
        ret = parse_config(f, conf);
@@ -788,7 +943,7 @@ int lxc_seccomp_load(struct lxc_conf *conf)
 #endif
            );
        if (ret < 0) {
-               ERROR("Error loading the seccomp policy: %s.", strerror(-ret));
+               ERROR("Error loading the seccomp policy: %s", strerror(-ret));
                return -1;
        }
 
@@ -800,7 +955,7 @@ int lxc_seccomp_load(struct lxc_conf *conf)
                ret = seccomp_export_pfc(conf->seccomp_ctx, lxc_log_fd);
                /* Just give an warning when export error */
                if (ret < 0)
-                       WARN("Failed to export seccomp filter to log file: %s.", strerror(-ret));
+                       WARN("Failed to export seccomp filter to log file: %s", strerror(-ret));
        }
 #endif
        return 0;