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.

Change-Id: I47c16ccb910ac730c092cb3ab977c59cb8197ce0
Signed-off-by: Stephen Smalley <sds@tycho.nsa.gov>
This commit is contained in:
Stephen Smalley 2014-10-07 13:46:59 -04:00 committed by dcashman
parent afd2760392
commit 3a1eb33be6
2 changed files with 496 additions and 5 deletions

View file

@ -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.

View file

@ -13,9 +13,12 @@
#include <sepol/policydb/util.h>
#include <stdbool.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 +428,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 +898,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 +933,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;
}