diff --git a/tools/README b/tools/README index 8a8dce1a..2aa520a5 100644 --- a/tools/README +++ b/tools/README @@ -94,3 +94,37 @@ sepolicy-analyze -foo -bar is expanded to individual allow rules by the policy compiler). Domains with unconfineddomain will typically have such duplicate rules as a natural side effect and can be ignored. + + PERMISSIVE DOMAINS + sepolicy-analyze -p -P out/target/product//root/sepolicy + + Displays domains in the policy that are permissive, i.e. avc + denials are logged but not enforced for these domains. While + permissive domains can be helpful during development, they + should not be present in a final -user build. + + NEVERALLOW CHECKING + sepolicy-analyze [-w] [-z] -n neverallows.conf -P out/target/product//root/sepolicy + + Check whether the sepolicy file violates any of the neverallow rules + from neverallows.conf. neverallows.conf is a file containing neverallow + statements in the same format as the SELinux policy.conf file, i.e. after + m4 macro expansion of the rules from a .te file. You can use an entire + policy.conf file as the neverallows.conf file and sepolicy-analyze will + ignore everything except for the neverallows within it. If there are + no violations, sepolicy-analyze will exit successfully with no output. + Otherwise, sepolicy-analyze will report all violations and exit + with a non-zero exit status. + + The -w or --warn option may be used to warn on any types, attributes, + classes, or permissions from a neverallow rule that could not be resolved + within the sepolicy file. This can be normal due to differences between + the policy from which the neverallow rules were taken and the policy + being checked. Such values are ignored for the purposes of neverallow + checking. + + The -z (-d was already taken!) or --debug option may be used to cause + sepolicy-analyze to emit the neverallow rules as it parses them from + the neverallows.conf file. This is principally a debugging facility + for the parser but could also be used to extract neverallow rules from + a full policy.conf file and output them in a more easily parsed format. diff --git a/tools/sepolicy-analyze.c b/tools/sepolicy-analyze.c index c9dab812..afebd697 100644 --- a/tools/sepolicy-analyze.c +++ b/tools/sepolicy-analyze.c @@ -12,10 +12,14 @@ #include #include #include +#include + +static int debug; +static int warn; void usage(char *arg0) { - fprintf(stderr, "%s [-e|--equiv] [-d|--diff] [-D|--dups] [-p|--permissive] -P \n", arg0); + fprintf(stderr, "%s [-w|--warn] [-z|--debug] [-e|--equiv] [-d|--diff] [-D|--dups] [-p|--permissive] [-n|--neverallow ] -P \n", arg0); exit(1); } @@ -425,24 +429,466 @@ static int list_permissive(policydb_t * policydb) return 0; } +static int read_typeset(policydb_t *policydb, char **ptr, char *end, + type_set_t *typeset, uint32_t *flags) +{ + const char *keyword = "self"; + size_t keyword_size = strlen(keyword), len; + char *p = *ptr; + unsigned openparens = 0; + char *start, *id; + type_datum_t *type; + struct ebitmap_node *n; + unsigned int bit; + bool negate = false; + int rc; + + do { + while (p < end && isspace(*p)) + p++; + + if (p == end) + goto err; + + if (*p == '~') { + if (debug) + printf(" ~"); + typeset->flags = TYPE_COMP; + p++; + while (p < end && isspace(*p)) + p++; + if (p == end) + goto err; + } + + if (*p == '{') { + if (debug && !openparens) + printf(" {"); + openparens++; + p++; + continue; + } + + if (*p == '}') { + if (debug && openparens == 1) + printf(" }"); + if (openparens == 0) + goto err; + openparens--; + p++; + continue; + } + + if (*p == '*') { + if (debug) + printf(" *"); + typeset->flags = TYPE_STAR; + p++; + continue; + } + + if (*p == '-') { + if (debug) + printf(" -"); + negate = true; + p++; + continue; + } + + if (*p == '#') { + while (p < end && *p != '\n') + p++; + continue; + } + + start = p; + while (p < end && !isspace(*p) && *p != ':' && *p != ';' && *p != '{' && *p != '}' && *p != '#') + p++; + + if (p == start) + goto err; + + len = p - start; + if (len == keyword_size && !strncmp(start, keyword, keyword_size)) { + if (debug) + printf(" self"); + *flags |= RULE_SELF; + continue; + } + + id = calloc(1, len + 1); + if (!id) + goto err; + memcpy(id, start, len); + if (debug) + printf(" %s", id); + type = hashtab_search(policydb->p_types.table, id); + if (!type) { + if (warn) + fprintf(stderr, "Warning! Type or attribute %s used in neverallow undefined in policy being checked.\n", id); + negate = false; + continue; + } + free(id); + + if (type->flavor == TYPE_ATTRIB) { + if (negate) + rc = ebitmap_union(&typeset->negset, &policydb->attr_type_map[type->s.value - 1]); + else + rc = ebitmap_union(&typeset->types, &policydb->attr_type_map[type->s.value - 1]); + } else if (negate) { + rc = ebitmap_set_bit(&typeset->negset, type->s.value - 1, 1); + } else { + rc = ebitmap_set_bit(&typeset->types, type->s.value - 1, 1); + } + + negate = false; + + if (rc) + goto err; + + } while (p < end && openparens); + + if (p == end) + goto err; + + if (typeset->flags & TYPE_STAR) { + for (bit = 0; bit < policydb->p_types.nprim; bit++) { + if (ebitmap_get_bit(&typeset->negset, bit)) + continue; + if (policydb->type_val_to_struct[bit] && + policydb->type_val_to_struct[bit]->flavor == TYPE_ATTRIB) + continue; + if (ebitmap_set_bit(&typeset->types, bit, 1)) + goto err; + } + } + + ebitmap_for_each_bit(&typeset->negset, n, bit) { + if (!ebitmap_node_get_bit(n, bit)) + continue; + if (ebitmap_set_bit(&typeset->types, bit, 0)) + goto err; + } + + if (typeset->flags & TYPE_COMP) { + for (bit = 0; bit < policydb->p_types.nprim; bit++) { + if (policydb->type_val_to_struct[bit] && + policydb->type_val_to_struct[bit]->flavor == TYPE_ATTRIB) + continue; + if (ebitmap_get_bit(&typeset->types, bit)) + ebitmap_set_bit(&typeset->types, bit, 0); + else { + if (ebitmap_set_bit(&typeset->types, bit, 1)) + goto err; + } + } + } + + if (warn && ebitmap_length(&typeset->types) == 0 && !(*flags)) + fprintf(stderr, "Warning! Empty type set\n"); + + *ptr = p; + return 0; +err: + return -1; +} + +static int read_classperms(policydb_t *policydb, char **ptr, char *end, + class_perm_node_t **perms) +{ + char *p = *ptr; + unsigned openparens = 0; + char *id, *start; + class_datum_t *cls = NULL; + perm_datum_t *perm = NULL; + class_perm_node_t *classperms = NULL, *node = NULL; + bool complement = false; + + while (p < end && isspace(*p)) + p++; + + if (p == end || *p != ':') + goto err; + p++; + + if (debug) + printf(" :"); + + do { + while (p < end && isspace(*p)) + p++; + + if (p == end) + goto err; + + if (*p == '{') { + if (debug && !openparens) + printf(" {"); + openparens++; + p++; + continue; + } + + if (*p == '}') { + if (debug && openparens == 1) + printf(" }"); + if (openparens == 0) + goto err; + openparens--; + p++; + continue; + } + + if (*p == '#') { + while (p < end && *p != '\n') + p++; + continue; + } + + start = p; + while (p < end && !isspace(*p) && *p != '{' && *p != '}' && *p != ';' && *p != '#') + p++; + + if (p == start) + goto err; + + id = calloc(1, p - start + 1); + if (!id) + goto err; + memcpy(id, start, p - start); + if (debug) + printf(" %s", id); + cls = hashtab_search(policydb->p_classes.table, id); + if (!cls) { + if (warn) + fprintf(stderr, "Warning! Class %s used in neverallow undefined in policy being checked.\n", id); + continue; + } + + node = calloc(1, sizeof *node); + if (!node) + goto err; + node->class = cls->s.value; + node->next = classperms; + classperms = node; + free(id); + } while (p < end && openparens); + + if (p == end) + goto err; + + if (warn && !classperms) + fprintf(stderr, "Warning! Empty class set\n"); + + do { + while (p < end && isspace(*p)) + p++; + + if (p == end) + goto err; + + if (*p == '~') { + if (debug) + printf(" ~"); + complement = true; + p++; + while (p < end && isspace(*p)) + p++; + if (p == end) + goto err; + } + + if (*p == '{') { + if (debug && !openparens) + printf(" {"); + openparens++; + p++; + continue; + } + + if (*p == '}') { + if (debug && openparens == 1) + printf(" }"); + if (openparens == 0) + goto err; + openparens--; + p++; + continue; + } + + if (*p == '#') { + while (p < end && *p != '\n') + p++; + continue; + } + + start = p; + while (p < end && !isspace(*p) && *p != '{' && *p != '}' && *p != ';' && *p != '#') + p++; + + if (p == start) + goto err; + + id = calloc(1, p - start + 1); + if (!id) + goto err; + memcpy(id, start, p - start); + if (debug) + printf(" %s", id); + + if (!strcmp(id, "*")) { + for (node = classperms; node; node = node->next) + node->data = ~0; + continue; + } + + for (node = classperms; node; node = node->next) { + cls = policydb->class_val_to_struct[node->class-1]; + perm = hashtab_search(cls->permissions.table, id); + if (cls->comdatum && !perm) + perm = hashtab_search(cls->comdatum->permissions.table, id); + if (!perm) { + if (warn) + fprintf(stderr, "Warning! Permission %s used in neverallow undefined in class %s in policy being checked.\n", id, policydb->p_class_val_to_name[node->class-1]); + continue; + } + node->data |= 1U << (perm->s.value - 1); + } + free(id); + } while (p < end && openparens); + + if (p == end) + goto err; + + if (complement) { + for (node = classperms; node; node = node->next) + node->data = ~node->data; + } + + if (warn) { + for (node = classperms; node; node = node->next) + if (!node->data) + fprintf(stderr, "Warning! Empty permission set\n"); + } + + *perms = classperms; + *ptr = p; + return 0; +err: + return -1; +} + +static int check_neverallows(policydb_t *policydb, const char *filename) +{ + const char *keyword = "neverallow"; + size_t keyword_size = strlen(keyword), len; + struct avrule *neverallows = NULL, *avrule; + int fd; + struct stat sb; + char *text, *end, *start; + char *p; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + fprintf(stderr, "Could not open %s: %s\n", filename, strerror(errno)); + return -1; + } + if (fstat(fd, &sb) < 0) { + fprintf(stderr, "Can't stat '%s': %s\n", filename, strerror(errno)); + close(fd); + return -1; + } + text = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + end = text + sb.st_size; + if (text == MAP_FAILED) { + fprintf(stderr, "Can't mmap '%s': %s\n", filename, strerror(errno)); + close(fd); + return -1; + } + close(fd); + + p = text; + while (p < end) { + while (p < end && isspace(*p)) + p++; + + if (*p == '#') { + while (p < end && *p != '\n') + p++; + continue; + } + + start = p; + while (p < end && !isspace(*p)) + p++; + + len = p - start; + if (len != keyword_size || strncmp(start, keyword, keyword_size)) + continue; + + if (debug) + printf("neverallow"); + + avrule = calloc(1, sizeof *avrule); + if (!avrule) + goto err; + + avrule->specified = AVRULE_NEVERALLOW; + + if (read_typeset(policydb, &p, end, &avrule->stypes, &avrule->flags)) + goto err; + + if (read_typeset(policydb, &p, end, &avrule->ttypes, &avrule->flags)) + goto err; + + if (read_classperms(policydb, &p, end, &avrule->perms)) + goto err; + + while (p < end && *p != ';') + p++; + + if (p == end || *p != ';') + goto err; + + if (debug) + printf(";\n"); + + avrule->next = neverallows; + neverallows = avrule; + } + + return check_assertions(NULL, policydb, neverallows); +err: + if (errno == ENOMEM) { + fprintf(stderr, "Out of memory while parsing %s\n", filename); + } else + fprintf(stderr, "Error while parsing %s\n", filename); + return -1; +} + int main(int argc, char **argv) { - char *policy = NULL; + char *policy = NULL, *neverallows = NULL; struct policy_file pf; policydb_t policydb; char ch; char equiv = 0, diff = 0, dups = 0, permissive = 0; + int rc = 0; struct option long_options[] = { {"equiv", no_argument, NULL, 'e'}, + {"debug", no_argument, NULL, 'z'}, {"diff", no_argument, NULL, 'd'}, {"dups", no_argument, NULL, 'D'}, + {"neverallow", required_argument, NULL, 'n'}, {"permissive", no_argument, NULL, 'p'}, {"policy", required_argument, NULL, 'P'}, + {"warn", no_argument, NULL, 'w'}, {NULL, 0, NULL, 0} }; - while ((ch = getopt_long(argc, argv, "edDpP:", long_options, NULL)) != -1) { + while ((ch = getopt_long(argc, argv, "edDpn:P:wz", long_options, NULL)) != -1) { switch (ch) { case 'e': equiv = 1; @@ -453,18 +899,27 @@ int main(int argc, char **argv) case 'D': dups = 1; break; + case 'n': + neverallows = optarg; + break; case 'p': permissive = 1; break; case 'P': policy = optarg; break; + case 'w': + warn = 1; + break; + case 'z': + debug = 1; + break; default: usage(argv[0]); } } - if (!policy || (!equiv && !diff && !dups && !permissive)) + if (!policy || (!equiv && !diff && !dups && !permissive && !neverallows)) usage(argv[0]); if (load_policy(policy, &policydb, &pf)) @@ -479,7 +934,10 @@ int main(int argc, char **argv) if (permissive) list_permissive(&policydb); + if (neverallows) + rc |= check_neverallows(&policydb, neverallows); + policydb_destroy(&policydb); - return 0; + return rc; }