From c423b1aae888296edc70dc4367d93a1314c61fa9 Mon Sep 17 00:00:00 2001
From: Stephen Smalley <sds@tycho.nsa.gov>
Date: Tue, 7 Oct 2014 13:46:59 -0400
Subject: [PATCH] Add neverallow checking to sepolicy-analyze.

See NEVERALLOW CHECKING in tools/README for documentation.

Depends on change I45b3502ff96b1d093574e1fecff93a582f8d00bd
for libsepol to support reporting all neverallow failures.

Cherry-pick of commit: 59906bf893c6372c91f19ca7e76a6468ddd14bd7
with build-fix from commit: 74bbf703df6ddedbd7aab9ae099fd7c90dc04048
added manually.

Bug: 19191637

Change-Id: I1c18fa854b3c5f5e05d5dc42d9006c5fdacebdc3
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
---
 tools/README             |  34 +++
 tools/sepolicy-analyze.c | 468 ++++++++++++++++++++++++++++++++++++++-
 2 files changed, 497 insertions(+), 5 deletions(-)

diff --git a/tools/README b/tools/README
index 8a8dce1a0..2aa520a58 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/<board>/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/<board>/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 c9dab8121..afebd6970 100644
--- a/tools/sepolicy-analyze.c
+++ b/tools/sepolicy-analyze.c
@@ -12,10 +12,14 @@
 #include <sepol/policydb/expand.h>
 #include <sepol/policydb/util.h>
 #include <stdbool.h>
+#include <ctype.h>
+
+static int debug;
+static int warn;
 
 void usage(char *arg0)
 {
-    fprintf(stderr, "%s [-e|--equiv] [-d|--diff] [-D|--dups] [-p|--permissive] -P <policy file>\n", arg0);
+    fprintf(stderr, "%s [-w|--warn] [-z|--debug] [-e|--equiv] [-d|--diff] [-D|--dups] [-p|--permissive] [-n|--neverallow <neverallow file>] -P <policy file>\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;
 }
-- 
GitLab