diff --git a/sys/security/mac_do/mac_do.c b/sys/security/mac_do/mac_do.c --- a/sys/security/mac_do/mac_do.c +++ b/sys/security/mac_do/mac_do.c @@ -5,9 +5,11 @@ */ #include +#include #include #include #include +#include #include #include #include @@ -35,20 +37,20 @@ #define MAC_RULE_STRING_LEN 1024 -static unsigned mac_do_osd_jail_slot; +static unsigned osd_jail_slot; -#define RULE_INVALID 0 /* Must stay 0. */ -#define RULE_UID 1 -#define RULE_GID 2 -#define RULE_ANY 3 +#define IT_INVALID 0 /* Must stay 0. */ +#define IT_UID 1 +#define IT_GID 2 +#define IT_ANY 3 +#define IT_LAST IT_ANY static const char *id_type_to_str[] = { - [RULE_INVALID] = "invalid", - [RULE_UID] = "uid", - [RULE_GID] = "gid", + [IT_INVALID] = "invalid", + [IT_UID] = "uid", + [IT_GID] = "gid", /* See also parse_id_type(). */ - [RULE_ANY] = "*", - NULL + [IT_ANY] = "*", }; /* @@ -59,19 +61,235 @@ sizeof(gid_t) == sizeof(int) && (int)(gid_t)-1 == -1, "mac_do(4) currently assumes that 'uid_t' and 'gid_t' are aliases to 'int'"); -struct rule { - int from_type; - int from_id; - int to_type; - int to_id; - TAILQ_ENTRY(rule) r_entries; +/* + * Internal flags. + * + * They either apply as per-type (t) or per-ID (i) but are conflated because all + * per-ID flags are also valid as per-type ones to qualify the "current" (".") + * per-type flag. Also, some of them are in fact exclusive, but we use one-hot + * encoding for simplicity. + * + * There is currently room for "only" 16 bits. As these flags are purely + * internal, they can be renumbered and/or their type changed as needed. + * + * See also the check_*() functions below. + */ +typedef uint16_t flags_t; + +/* (i,gid) Specification concerns primary groups. */ +#define MDF_PRIMARY 1 +/* (i,gid) Specification concerns supplementary groups. */ +#define MDF_SUPP_ALLOW 2 +/* (i,gid) Group must appear as a supplementary group. */ +#define MDF_SUPP_MUST 4 +/* (i,gid) Group must not appear as a supplementary group. */ +#define MDF_SUPP_DONT 8 +#define MDF_SUPP_MASK (MDF_SUPP_ALLOW | MDF_SUPP_MUST | MDF_SUPP_DONT) +#define MDF_ID_MASK (MDF_PRIMARY | MDF_SUPP_MASK) + +/* + * (t) All IDs allowed. + * + * For GIDs, MDF_ANY only concerns primary groups. The MDF_PRIMARY and + * MDF_SUPP_* flags never apply to MDF_ANY, but can be present if MDF_CURRENT is + * present also, as usual. + */ +#define MDF_ANY (1u << 8) +/* (t) Current IDs allowed. */ +#define MDF_CURRENT (1u << 9) +#define MDF_TYPE_COMMON_MASK (MDF_ANY | MDF_CURRENT) +/* (t,gid) All IDs allowed as supplementary groups. */ +#define MDF_ANY_SUPP (1u << 10) +/* (t,gid) Some ID has MDF_SUPP_MUST or MDF_SUPP_DONT. */ +#define MDF_MAY_REJ_SUPP (1u << 11) +/* (t,gid) Whether some target clause concerns primary groups. Used during + * parsing only. */ +#define MDF_HAS_PRIMARY_CLAUSE (1u << 12) +/* (t,gid) Whether some target clause concerns supplementary groups. Used + * during parsing only. */ +#define MDF_HAS_SUPP_CLAUSE (1u << 13) +#define MDF_TYPE_GID_MASK (MDF_ANY_SUPP | MDF_MAY_REJ_SUPP | \ + MDF_HAS_PRIMARY_CLAUSE | MDF_HAS_SUPP_CLAUSE) +#define MDF_TYPE_MASK (MDF_TYPE_COMMON_MASK | MDF_TYPE_GID_MASK) + +/* + * Persistent structures. + */ + +struct id_spec { + int id; + flags_t flags; /* See MDF_* above. */ }; +/* + * This limits the number of target clauses per type to 65535. With the current + * value of MAC_RULE_STRING_LEN (1024), this is way more than enough anyway. + */ +typedef uint16_t id_nb_t; +/* We only have a few IT_* types. */ +typedef uint16_t id_type_t; + +struct rule { + TAILQ_ENTRY(rule) r_entries; + id_type_t from_type; + int from_id; + flags_t uid_flags; /* See MDF_* above. */ + id_nb_t uids_nb; + flags_t gid_flags; /* See MDF_* above. */ + id_nb_t gids_nb; + struct id_spec *uids; + struct id_spec *gids; +}; + +TAILQ_HEAD(rulehead, rule); + struct rules { char string[MAC_RULE_STRING_LEN]; - TAILQ_HEAD(rulehead, rule) head; + struct rulehead head; }; +/* + * Temporary structures used to build a 'struct rule' above. + */ + +struct id_elem { + TAILQ_ENTRY(id_elem) ie_entries; + struct id_spec spec; +}; + +TAILQ_HEAD(id_list, id_elem); + +#ifdef INVARIANTS +static void +check_type(const id_type_t type) +{ + if (type > IT_LAST) + panic("Invalid type number %u", type); +} + +static void +panic_for_unexpected_flags(const id_type_t type, const flags_t flags, + const char *const str) +{ + panic("ID type %s: Unexpected flags %u (%s), ", id_type_to_str[type], + flags, str); +} + +static void +check_type_and_id_flags(const id_type_t type, const flags_t flags) +{ + const char *str; + + check_type(type); + switch (type) { + case IT_UID: + if (flags != 0) { + str = "only 0 allowed"; + goto unexpected_flags; + } + break; + case IT_GID: + if (flags & ~MDF_ID_MASK) { + str = "only bits in MDF_ID_MASK allowed"; + goto unexpected_flags; + } + if (!powerof2(flags & MDF_SUPP_MASK)) { + str = "only a single flag in MDF_SUPP_MASK allowed"; + goto unexpected_flags; + } + break; + default: + __assert_unreachable(); + } + return; + +unexpected_flags: + panic_for_unexpected_flags(type, flags, str); +} + +static void +check_type_and_id_spec(const id_type_t type, const struct id_spec *const is) +{ + check_type_and_id_flags(type, is->flags); + if (is->id < 0) + panic("ID type %s: Negative id %d", id_type_to_str[type], + is->id); +} + +static void +check_type_and_type_flags(const id_type_t type, const flags_t flags) +{ + const char *str; + + check_type_and_id_flags(type, flags & MDF_ID_MASK); + if (flags & ~MDF_ID_MASK & ~MDF_TYPE_MASK) { + str = "only MDF_ID_MASK | MDF_TYPE_MASK bits allowed"; + goto unexpected_flags; + } + if ((flags & MDF_ANY) && (flags & MDF_CURRENT) && + (type == IT_UID || (flags & MDF_PRIMARY))) { + str = "MDF_ANY and MDF_CURRENT are exclusive for UIDs " + "or primary group GIDs"; + goto unexpected_flags; + } + if ((flags & MDF_ANY_SUPP) && (flags & MDF_CURRENT) && + (flags & MDF_SUPP_MASK)) { + str = "MDF_SUPP_ANY and MDF_CURRENT with supplementary " + "groups specification are exclusive"; + goto unexpected_flags; + } + if (((flags & MDF_PRIMARY) || (flags & MDF_ANY)) && + !(flags & MDF_HAS_PRIMARY_CLAUSE)) { + str = "Presence of folded primary clause not reflected " + "by presence of MDF_HAS_PRIMARY_CLAUSE"; + goto unexpected_flags; + } + if (((flags & MDF_SUPP_MASK) || (flags & MDF_ANY_SUPP)) && + !(flags & MDF_HAS_SUPP_CLAUSE)) { + str = "Presence of folded supplementary clause not reflected " + "by presence of MDF_HAS_SUPP_CLAUSE"; + goto unexpected_flags; + } + return; + +unexpected_flags: + panic_for_unexpected_flags(type, flags, str); +} +#else /* !INVARIANTS */ +#define check_type_and_id_flags(...) +#define check_type_and_id_spec(...) +#define check_type_and_type_flags(...) +#endif /* INVARIANTS */ + +/* + * Returns EALREADY if both flags have some overlap, or EINVAL if flags are + * incompatible, else 0 with flags successfully merged into 'dest'. + */ +static int +coalesce_id_flags(const flags_t src, flags_t *const dest) +{ + flags_t res; + + if ((src & *dest) != 0) + return (EALREADY); + + res = src | *dest; + + /* Check for compatibility of supplementary flags, and coalesce. */ + if (res & MDF_SUPP_MASK) { + /* MDF_SUPP_DONT incompatible with the rest. */ + if ((res & MDF_SUPP_DONT) && (res & MDF_SUPP_MASK & + ~MDF_SUPP_DONT)) + return (EINVAL); + /* Coalesce MDF_SUPP_ALLOW and MDF_SUPP_MUST into MDF_SUPP_MUST. */ + if ((res & MDF_SUPP_ALLOW) && (res & MDF_SUPP_MUST)) + res &= ~MDF_SUPP_ALLOW; + } + + *dest = res; + return (0); +} + static void toast_rules(struct rules *const rules) { @@ -80,6 +298,8 @@ while ((rule = TAILQ_FIRST(head)) != NULL) { TAILQ_REMOVE(head, rule, r_entries); + free(rule->uids, M_DO); + free(rule->gids, M_DO); free(rule, M_DO); } free(rules, M_DO); @@ -96,102 +316,451 @@ return (rules); } +static bool +is_null_or_empty(const char *s) +{ + return (s == NULL || s[0] == 0); +} + /* * String to non-negative int. * - * Returns minus an error (EINVAL or EOVERFLOW) in case of failure. + * Returns minus an error (EINVAL or EOVERFLOW) in case of failure. Rejects an + * explicit sign at start, as well as whitespaces. */ static int -strtonni(const char *const restrict s, char **const restrict endptr, int base) +strtonni(const char *const restrict s, const char **const restrict endptr, + int base) { - const long l = strtol(s, endptr, base); + long l; + char *ep; + if (s[0] != 0 && !isdigit(s[0])) { + if (endptr != NULL) + *endptr = s; + return (-EINVAL); + } + + l = strtol(s, &ep, base); + if (endptr != NULL) + *endptr = ep; if (l < 0) return (-EINVAL); else if (l == LONG_MAX || l > INT_MAX) return (-EOVERFLOW); - return ((int)l); } static int -parse_id_type(const char *const string, int *const type) +parse_id_type(const char *const string, id_type_t *const type) { /* - * Special case for "any", as the canonical form for RULE_ANY in + * Special case for "any", as the canonical form for IT_ANY in * id_type_to_str[] is "*". */ if (strcmp(string, "any") == 0) { - *type = RULE_ANY; + *type = IT_ANY; return (0); } /* Start at 1 to avoid parsing "invalid". */ - for (size_t i = 1; id_type_to_str[i] != NULL; ++i) { + for (size_t i = 1; i <= IT_LAST; ++i) { if (strcmp(string, id_type_to_str[i]) == 0) { *type = i; return (0); } } - *type = RULE_INVALID; + *type = IT_INVALID; + return (EINVAL); +} + +static bool +parse_any(const char *const string) +{ + return (strcmp(string, "*") == 0 || strcmp(string, "any") == 0); +} + +static bool +has_clauses(const id_nb_t nb, const flags_t type_flags) +{ + return ((type_flags & MDF_TYPE_MASK) || nb != 0); +} + +static int +parse_target_clause(char *to, struct rule *const rule, + struct id_list *const uid_list, struct id_list *const gid_list) +{ + char *to_type, *to_id; + const char *p; + struct id_list *list; + id_nb_t *nb; + flags_t *tflags; + struct id_elem *ie; + struct id_spec is = {}; + u_int gid_flags = 0; + id_type_t type; + int error; + + to_type = strsep(&to, "="); + if (to_type == NULL) + goto einval; + error = parse_id_type(to_type, &type); + if (error != 0) + goto einval; + + to_id = strsep(&to, ""); + switch (type) { + case IT_GID: + if (to_id == NULL) + goto einval; + + /* + * This is easily wrapped into a loop for when/if we allow + * multiple flags to be specified at once. + */ + switch (*to_id) { + case '+': + is.flags |= MDF_SUPP_ALLOW; + goto has_supp_clause; + case '!': + is.flags |= MDF_SUPP_MUST; + gid_flags |= MDF_MAY_REJ_SUPP; + goto has_supp_clause; + case '-': + is.flags |= MDF_SUPP_DONT; + gid_flags |= MDF_MAY_REJ_SUPP; + goto has_supp_clause; + has_supp_clause: + gid_flags |= MDF_HAS_SUPP_CLAUSE; + ++to_id; + break; + default: + is.flags |= MDF_PRIMARY; + gid_flags |= MDF_HAS_PRIMARY_CLAUSE; + break; + } + + list = gid_list; + nb = &rule->gids_nb; + tflags = &rule->gid_flags; + + /* "*" or "any"? */ + if (parse_any(to_id)) { + /* + * We check that we have not seen any other clause of + * the same category (i.e., concerning primary or + * supplementary groups). + */ + if (is.flags & MDF_PRIMARY) { + if (*tflags & MDF_HAS_PRIMARY_CLAUSE) + goto einval; + *tflags |= gid_flags | MDF_ANY; + } else { + /* + * If a supplementary group flag was present, it + * must be MDF_SUPP_ALLOW ("+"). + */ + if ((is.flags & MDF_SUPP_MASK) != MDF_SUPP_ALLOW || + (*tflags & MDF_HAS_SUPP_CLAUSE)) + goto einval; + *tflags |= gid_flags | MDF_ANY_SUPP; + } + goto check_type_and_finish; + } else { + /* + * Check that we haven't already seen "any" for the same + * category. + */ + if ((is.flags & MDF_PRIMARY)) { + if (*tflags & MDF_ANY) + goto einval; + } else if (*tflags & MDF_ANY_SUPP) + goto einval; + *tflags |= gid_flags; + } + break; + + case IT_UID: + if (to_id == NULL) + goto einval; + + list = uid_list; + nb = &rule->uids_nb; + tflags = &rule->uid_flags; + + /* "*" or "any"? */ + if (parse_any(to_id)) { + /* There must not be any other clause. */ + if (has_clauses(*nb, *tflags)) + goto einval; + *tflags |= MDF_ANY; + goto check_type_and_finish; + } else { + /* + * Check that we haven't already seen "any" for the same + * category. + */ + if (*tflags & MDF_ANY) + goto einval; + } + break; + + case IT_ANY: + /* No ID allowed. */ + if (to_id != NULL) + goto einval; + /* + * We can't have IT_ANY after any other IT_*, it must be the + * only one. + */ + if (has_clauses(rule->uids_nb, rule->uid_flags) || + has_clauses(rule->gids_nb, rule->gid_flags)) + goto einval; + rule->uid_flags |= MDF_ANY; + rule->gid_flags |= MDF_ANY | MDF_ANY_SUPP | + MDF_HAS_PRIMARY_CLAUSE | MDF_HAS_SUPP_CLAUSE; + goto finish; + + default: + /* parse_id_type() returns no other types currently. */ + __assert_unreachable(); + } + + /* These cases have been treated above. */ + MPASS((type == IT_UID || type == IT_GID) && !parse_any(to_id)); + + /* "."? */ + if (strcmp(to_id, ".") == 0) { + if (*tflags & MDF_CURRENT) { + /* Duplicate "." . Try to coalesce. */ + error = coalesce_id_flags(is.flags, tflags); + if (error != 0) + goto einval; + } else + *tflags |= MDF_CURRENT | is.flags; + goto check_type_and_finish; + } + + /* Parse an ID. */ + is.id = strtonni(to_id, &p, 10); + if (is.id < 0 || *p != '\0') + goto einval; + + /* + * We check for duplicate IDs and coalesce their 'struct id_spec' only + * at end of parse_single_rule() because it is much more performant then + * (using sorted arrays). + */ + ++*nb; + if (*nb == 0) + return (EOVERFLOW); + ie = malloc(sizeof(*ie), M_DO, M_WAITOK); + ie->spec = is; + TAILQ_INSERT_TAIL(list, ie, ie_entries); + check_type_and_id_spec(type, &is); +finish: + return (0); +check_type_and_finish: + check_type_and_type_flags(type, *tflags); + return (0); +einval: return (EINVAL); } static int -parse_rule_element(char *element, struct rule **rule) +int_cmp(const int i1, const int i2) { - const char *from_type, *from_id, *to; - char *p; + return ((i1 > i2) - (i1 < i2)); +} + +static int +id_spec_cmp(const void *const p1, const void *const p2) +{ + const struct id_spec *const is1 = p1; + const struct id_spec *const is2 = p2; + + return (int_cmp(is1->id, is2->id)); +} + +/* + * Transfer content of 'list' into 'array', freeing and emptying list. + * + * 'nb' must be 'list''s length and not be greater than 'array''s size. The + * destination array is sorted by ID. Structures 'struct id_spec' with same IDs + * are coalesced if that makes sense (not including duplicate clauses), else + * EINVAL is returned. On success, 'nb' is updated (lowered) to account for + * coalesced specifications. The parameter 'type' is only for testing purposes + * (INVARIANTS). + */ +static int +pour_list_into_rule(const id_type_t type, struct id_list *const list, + struct id_spec *const array, id_nb_t *const nb) +{ + struct id_elem *ie, *ie_next; + size_t idx = 0; + + /* Fill the array. */ + TAILQ_FOREACH_SAFE(ie, list, ie_entries, ie_next) { + MPASS(idx < *nb); + array[idx] = ie->spec; + free(ie, M_DO); + ++idx; + } + MPASS(idx == *nb); + TAILQ_INIT(list); + + /* Sort it (by ID). */ + qsort(array, *nb, sizeof(*array), id_spec_cmp); + + /* Coalesce same IDs. */ + if (*nb != 0) { + size_t ref_idx = 0; + + for (idx = 1; idx < *nb; ++idx) { + const int id = array[idx].id; + + if (id == array[ref_idx].id) { + const int error = + coalesce_id_flags(array[idx].flags, + &array[ref_idx].flags); + + if (error != 0) + return (EINVAL); + check_type_and_id_flags(type, + array[ref_idx].flags); + } + else { + ++ref_idx; + if (ref_idx != idx) + array[ref_idx] = array[idx]; + } + } + *nb = ref_idx + 1; + } + + return (0); +} + +/* + * See also first comments for parse_rule() below. + * + * The second part of a rule, called (or ), is a comma-separated + * (',') list of '=' clauses similar to that of the + * part, with the extensions that may also be "*" or "any" or ".", and that + * may contain at most one of the '+', '-' and '!' characters when + * is "gid" (no flags are allowed for "uid"). No two clauses in a single + * list may list the same . "*" and "any" both designate any ID for + * the , and are aliases to each other. In front of "any" (or "*"; only + * for GIDs), only the '+' flags is allowed. "." designates the process' + * current IDs for the . The precise meaning of flags and "." is + * explained in functions checking privileges below. + */ +static int +parse_single_rule(char *rule, struct rules *const rules) +{ + const char *from_type, *from_id, *p; + char *to_list; + struct id_list uid_list, gid_list; + struct id_elem *ie, *ie_next; struct rule *new; int error; - new = malloc(sizeof(*new), M_DO, M_ZERO|M_WAITOK); + MPASS(rule != NULL); + TAILQ_INIT(&uid_list); + TAILQ_INIT(&gid_list); - from_type = strsep(&element, "="); - if (from_type == NULL) - goto einval; + /* Freed when the 'struct rules' container is freed. */ + new = malloc(sizeof(*new), M_DO, M_WAITOK | M_ZERO); + from_type = strsep(&rule, "="); + MPASS(from_type != NULL); /* Because 'rule' was not NULL. */ error = parse_id_type(from_type, &new->from_type); if (error != 0) goto einval; switch (new->from_type) { - case RULE_UID: - case RULE_GID: + case IT_UID: + case IT_GID: break; default: goto einval; } - from_id = strsep(&element, ":"); - if (from_id == NULL || *from_id == '\0') + from_id = strsep(&rule, ":"); + if (is_null_or_empty(from_id)) goto einval; new->from_id = strtonni(from_id, &p, 10); - if (*p != '\0' || new->from_id < 0) + if (new->from_id < 0 || *p != '\0') goto einval; - if (*p != '\0') + /* + * We will now parse the "to" list. + * + * In order to ease parsing, we will begin by building lists of target + * UIDs and GIDs in local variables 'uid_list' and 'gid_list'. The + * number of each type of IDs will be filled directly in 'new'. At end + * of parse, we will allocate both arrays of IDs to be placed into the + * 'uids' and 'gids' members, sort them, and discard the tail queues + * used to build them. This conversion to sorted arrays at end of parse + * allows to minimize memory allocations and enables searching IDs in + * O(log(n)) instead of linearly. + */ + to_list = strsep(&rule, ","); + if (to_list == NULL) goto einval; + do { + error = parse_target_clause(to_list, new, &uid_list, &gid_list); + if (error != 0) + goto einval; - to = element; - if (to == NULL || *to == '\0') - goto einval; + to_list = strsep(&rule, ","); + } while (to_list != NULL); - if (strcmp(to, "any") == 0 || strcmp(to, "*") == 0) - new->to_type = RULE_ANY; - else { - new->to_type = RULE_UID; - new->to_id = strtonni(to, &p, 10); - if (*p != '\0' || new->to_id < 0) + if (new->uids_nb != 0) { + new->uids = malloc(sizeof(*new->uids) * new->uids_nb, M_DO, + M_WAITOK); + error = pour_list_into_rule(IT_UID, &uid_list, new->uids, + &new->uids_nb); + if (error != 0) goto einval; } + MPASS(TAILQ_EMPTY(&uid_list)); + if (!has_clauses(new->uids_nb, new->uid_flags)) { + /* No UID specified, default is "uid=.". */ + MPASS(new->uid_flags == 0); + new->uid_flags = MDF_CURRENT; + check_type_and_type_flags(IT_UID, new->uid_flags); + } - *rule = new; + if (new->gids_nb != 0) { + new->gids = malloc(sizeof(*new->gids) * new->gids_nb, M_DO, + M_WAITOK); + error = pour_list_into_rule(IT_GID, &gid_list, new->gids, + &new->gids_nb); + if (error != 0) + goto einval; + } + MPASS(TAILQ_EMPTY(&gid_list)); + if (!has_clauses(new->gids_nb, new->gid_flags)) { + /* No GID specified, default is "gid=.,gid=!.". */ + MPASS(new->gid_flags == 0); + new->gid_flags |= MDF_CURRENT | MDF_PRIMARY | MDF_SUPP_MUST | + MDF_HAS_PRIMARY_CLAUSE | MDF_HAS_SUPP_CLAUSE; + check_type_and_type_flags(IT_GID, new->gid_flags); + } + + TAILQ_INSERT_TAIL(&rules->head, new, r_entries); return (0); + einval: + free(new->gids, M_DO); + free(new->uids, M_DO); free(new, M_DO); - *rule = NULL; + TAILQ_FOREACH_SAFE(ie, &gid_list, ie_entries, ie_next) + free(ie, M_DO); + TAILQ_FOREACH_SAFE(ie, &uid_list, ie_entries, ie_next) + free(ie, M_DO); return (EINVAL); } @@ -202,19 +771,25 @@ * representing the rules. On error, the returned value is non-zero and * '*rulesp' is unchanged. If 'string' has length greater or equal to * MAC_RULE_STRING_LEN, ENAMETOOLONG is returned. If it is not in the expected - * format (comma-separated list of clauses of the form "=:", - * where is "uid" or "gid", an UID or GID (depending on ) and - * is "*", "any" or some UID), EINVAL is returned. + * format, EINVAL is returned. + * + * Expected format: A semi-colon-separated list of rules of the form + * ":". The part is of the form "=" where + * is "uid" or "gid", an UID or GID (depending on ) and is + * "*", "any" or a comma-separated list of '=' clauses (see the + * comment for parse_single_rule() for more details). For convenience, empty + * rules are allowed (and do nothing). + * + * Examples: + * - "uid=1001:uid=1010,gid=1010;uid=1002:any" + * - "gid=1010:gid=1011,gid=1012,gid=1013" */ static int parse_rules(const char *const string, struct rules **const rulesp) { const size_t len = strlen(string); - char *copy; - char *p; - char *element; + char *copy, *p, *rule; struct rules *rules; - struct rule *new; int error = 0; if (len >= MAC_RULE_STRING_LEN) @@ -229,15 +804,14 @@ MPASS(copy[len] == '\0'); /* Catch some races. */ p = copy; - while ((element = strsep(&p, ",")) != NULL) { - if (element[0] == '\0') + while ((rule = strsep(&p, ";")) != NULL) { + if (rule[0] == '\0') continue; - error = parse_rule_element(element, &new); + error = parse_single_rule(rule, rules); if (error != 0) { toast_rules(rules); goto out; } - TAILQ_INSERT_TAIL(&rules->head, new, r_entries); } *rulesp = rules; @@ -263,7 +837,7 @@ cpr = pr; for (;;) { prison_lock(cpr); - rules = osd_jail_get(cpr, mac_do_osd_jail_slot); + rules = osd_jail_get(cpr, osd_jail_slot); if (rules != NULL) break; prison_unlock(cpr); @@ -272,13 +846,13 @@ MPASS(ppr != NULL); /* prison0 always has rules. */ cpr = ppr; } - *aprp = cpr; + *aprp = cpr; return (rules); } /* - * OSD destructor for slot 'mac_do_osd_jail_slot'. + * OSD destructor for slot 'osd_jail_slot'. * * Called with 'value' not NULL. */ @@ -296,14 +870,14 @@ * In practice, this means that the rules become inherited (from the closest * ascendant that has some). * - * Destroys the 'mac_do_osd_jail_slot' slot of the passed jail. + * Destroys the 'osd_jail_slot' slot of the passed jail. */ static void remove_rules(struct prison *const pr) { prison_lock(pr); /* This calls destructor dealloc_osd(). */ - osd_jail_del(pr, mac_do_osd_jail_slot); + osd_jail_del(pr, osd_jail_slot); prison_unlock(pr); } @@ -316,11 +890,11 @@ struct rules *old_rules; void **rsv; - rsv = osd_reserve(mac_do_osd_jail_slot); + rsv = osd_reserve(osd_jail_slot); prison_lock(pr); - old_rules = osd_jail_get(pr, mac_do_osd_jail_slot); - osd_jail_set_reserved(pr, mac_do_osd_jail_slot, rsv, rules); + old_rules = osd_jail_get(pr, osd_jail_slot); + osd_jail_set_reserved(pr, osd_jail_slot, rsv, rules); prison_unlock(pr); if (old_rules != NULL) toast_rules(old_rules); @@ -589,7 +1163,7 @@ { struct prison *pr; - mac_do_osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods); + osd_jail_slot = osd_jail_register(dealloc_osd, osd_methods); set_empty_rules(&prison0); sx_slock(&allprison_lock); TAILQ_FOREACH(pr, &allprison, pr_list) @@ -600,15 +1174,15 @@ static void mac_do_destroy(struct mac_policy_conf *mpc) { - osd_jail_deregister(mac_do_osd_jail_slot); + osd_jail_deregister(osd_jail_slot); } static bool rule_applies(struct ucred *cred, struct rule *r) { - if (r->from_type == RULE_UID && r->from_id == cred->cr_uid) + if (r->from_type == IT_UID && r->from_id == cred->cr_uid) return (true); - if (r->from_type == RULE_GID && groupmember(r->from_id, cred)) + if (r->from_type == IT_GID && groupmember(r->from_id, cred)) return (true); return (false); } @@ -678,11 +1252,12 @@ mac_do_check_setuid(struct ucred *cred, uid_t uid) { struct rule *r; - int error; char *fullpath = NULL; char *freebuf = NULL; struct prison *pr; struct rules *rule; + struct id_spec uid_is = {.id = uid, 0}; + int error; if (do_enabled == 0) return (0); @@ -700,29 +1275,17 @@ error = EPERM; rule = find_rules(cred->cr_prison, &pr); TAILQ_FOREACH(r, &rule->head, r_entries) { - if (r->from_type == RULE_UID) { - if (cred->cr_uid != r->from_id) - continue; - if (r->to_type == RULE_ANY) { - error = 0; - break; - } - if (r->to_type == RULE_UID && uid == r->to_id) { - error = 0; - break; - } - } - if (r->from_type == RULE_GID) { - if (!groupmember(r->from_id, cred)) - continue; - if (r->to_type == RULE_ANY) { - error = 0; - break; - } - if (r->to_type == RULE_UID && uid == r->to_id) { - error = 0; - break; - } + if (!((r->from_type == IT_UID && cred->cr_uid == r->from_id) || + (r->from_type == IT_GID && groupmember(r->from_id, cred)))) + continue; + + if (r->uid_flags & MDF_ANY || + ((r->uid_flags & MDF_CURRENT) && (uid == cred->cr_uid || + uid == cred->cr_ruid || uid == cred->cr_svuid)) || + bsearch(&uid_is, r->uids, r->uids_nb, sizeof(*r->uids), + id_spec_cmp) != NULL) { + error = 0; + break; } } prison_unlock(pr);