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:
parent
afd2760392
commit
3a1eb33be6
2 changed files with 496 additions and 5 deletions
34
tools/README
34
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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue