diff options
Diffstat (limited to 'security/apparmor/policy_unpack.c')
| -rw-r--r-- | security/apparmor/policy_unpack.c | 336 |
1 files changed, 291 insertions, 45 deletions
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 7523971e37d9..dc908e1f5a88 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -450,20 +450,73 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e, int flags) return dfa; } +static int process_strs_entry(char *str, int size, bool multi) +{ + int c = 1; + + if (size <= 0) + return -1; + if (multi) { + if (size < 2) + return -2; + /* multi ends with double \0 */ + if (str[size - 2]) + return -3; + } + + char *save = str; + char *pos = str; + char *end = multi ? str + size - 2 : str + size - 1; + /* count # of internal \0 */ + while (str < end) { + if (str == pos) { + /* starts with ... */ + if (!*str) { + AA_DEBUG(DEBUG_UNPACK, + "starting with null save=%lu size %d c=%d", + (unsigned long)(str - save), size, c); + return -4; + } + if (isspace(*str)) + return -5; + if (*str == ':') { + /* :ns_str\0str\0 + * first character after : must be valid + */ + if (!str[1]) + return -6; + } + } else if (!*str) { + if (*pos == ':') + *str = ':'; + else + c++; + pos = str + 1; + } + str++; + } /* while */ + + return c; +} + /** - * unpack_trans_table - unpack a profile transition table + * unpack_strs_table - unpack a profile transition table * @e: serialized data extent information (NOT NULL) + * @name: name of table (MAY BE NULL) + * @multi: allow multiple strings on a single entry * @strs: str table to unpack to (NOT NULL) * - * Returns: true if table successfully unpacked or not present + * Returns: 0 if table successfully unpacked or not present, else error */ -static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) +static int unpack_strs_table(struct aa_ext *e, const char *name, bool multi, + struct aa_str_table *strs) { void *saved_pos = e->pos; - char **table = NULL; + struct aa_str_table_ent *table = NULL; + int error = -EPROTO; /* exec table is optional */ - if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) { + if (aa_unpack_nameX(e, AA_STRUCT, name)) { u16 size; int i; @@ -475,61 +528,47 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs) * for size check here */ goto fail; - table = kcalloc(size, sizeof(char *), GFP_KERNEL); - if (!table) + table = kcalloc(size, sizeof(struct aa_str_table_ent), + GFP_KERNEL); + if (!table) { + error = -ENOMEM; goto fail; - + } strs->table = table; strs->size = size; for (i = 0; i < size; i++) { char *str; - int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL); + int c, size2 = aa_unpack_strdup(e, &str, NULL); /* aa_unpack_strdup verifies that the last character is * null termination byte. */ - if (!size2) - goto fail; - table[i] = str; - /* verify that name doesn't start with space */ - if (isspace(*str)) + c = process_strs_entry(str, size2, multi); + if (c <= 0) { + AA_DEBUG(DEBUG_UNPACK, "process_strs %d i %d pos %ld", + c, i, + (unsigned long)(e->pos - saved_pos)); goto fail; - - /* count internal # of internal \0 */ - for (c = j = 0; j < size2 - 1; j++) { - if (!str[j]) { - pos = j; - c++; - } } - if (*str == ':') { - /* first character after : must be valid */ - if (!str[1]) - goto fail; - /* beginning with : requires an embedded \0, - * verify that exactly 1 internal \0 exists - * trailing \0 already verified by aa_unpack_strdup - * - * convert \0 back to : for label_parse - */ - if (c == 1) - str[pos] = ':'; - else if (c > 1) - goto fail; - } else if (c) + if (!multi && c > 1) { + AA_DEBUG(DEBUG_UNPACK, "!multi && c > 1"); /* fail - all other cases with embedded \0 */ goto fail; + } + table[i].strs = str; + table[i].count = c; + table[i].size = size2; } if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) goto fail; if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - return true; + return 0; fail: - aa_free_str_table(strs); + aa_destroy_str_table(strs); e->pos = saved_pos; - return false; + return error; } static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile) @@ -644,6 +683,204 @@ fail: return false; } + +static bool verify_tags(struct aa_tags_struct *tags, const char **info) +{ + if ((tags->hdrs.size && !tags->hdrs.table) || + (!tags->hdrs.size && tags->hdrs.table)) { + *info = "failed verification tag.hdrs disagree"; + return false; + } + if ((tags->sets.size && !tags->sets.table) || + (!tags->sets.size && tags->sets.table)) { + *info = "failed verification tag.sets disagree"; + return false; + } + if ((tags->strs.size && !tags->strs.table) || + (!tags->strs.size && tags->strs.table)) { + *info = "failed verification tags->strs disagree"; + return false; + } + /* no data present */ + if (!tags->sets.size && !tags->hdrs.size && !tags->strs.size) { + return true; + } else if (!(tags->sets.size && tags->hdrs.size && tags->strs.size)) { + /* some data present but not all */ + *info = "failed verification tags partial data present"; + return false; + } + + u32 i; + + for (i = 0; i < tags->sets.size; i++) { + /* count followed by count indexes into hdrs */ + u32 cnt = tags->sets.table[i]; + + if (i+cnt >= tags->sets.size) { + AA_DEBUG(DEBUG_UNPACK, + "tagset too large %d+%d > sets.table[%d]", + i, cnt, tags->sets.size); + *info = "failed verification tagset too large"; + return false; + } + for (; cnt; cnt--) { + if (tags->sets.table[++i] >= tags->hdrs.size) { + AA_DEBUG(DEBUG_UNPACK, + "tagsets idx out of bounds cnt %d sets.table[%d] >= %d", + cnt, i-1, tags->hdrs.size); + *info = "failed verification tagsets idx out of bounds"; + return false; + } + } + } + for (i = 0; i < tags->hdrs.size; i++) { + u32 idx = tags->hdrs.table[i].tags; + + if (idx >= tags->strs.size) { + AA_DEBUG(DEBUG_UNPACK, + "tag.hdrs idx oob idx %d > tags->strs.size=%d", + idx, tags->strs.size); + *info = "failed verification tags.hdrs idx out of bounds"; + return false; + } + if (tags->hdrs.table[i].count != tags->strs.table[idx].count) { + AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].count=%d != tags->strs.table[%d]=%d", + i, tags->hdrs.table[i].count, idx, tags->strs.table[idx].count); + *info = "failed verification tagd.hdrs[idx].count"; + return false; + } + if (tags->hdrs.table[i].size != tags->strs.table[idx].size) { + AA_DEBUG(DEBUG_UNPACK, "hdrs.table[%d].size=%d != strs.table[%d].size=%d", + i, tags->hdrs.table[i].size, idx, tags->strs.table[idx].size); + *info = "failed verification tagd.hdrs[idx].size"; + return false; + } + } + + return true; +} + +static int unpack_tagsets(struct aa_ext *e, struct aa_tags_struct *tags) +{ + u32 *sets; + u16 i, size; + int error = -EPROTO; + void *pos = e->pos; + + if (!aa_unpack_array(e, "sets", &size)) + goto fail_reset; + sets = kcalloc(size, sizeof(u32), GFP_KERNEL); + if (!sets) { + error = -ENOMEM; + goto fail_reset; + } + for (i = 0; i < size; i++) { + if (!aa_unpack_u32(e, &sets[i], NULL)) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + + tags->sets.size = size; + tags->sets.table = sets; + + return 0; + +fail: + kfree_sensitive(sets); +fail_reset: + e->pos = pos; + return error; +} + +static bool unpack_tag_header_ent(struct aa_ext *e, struct aa_tags_header *h) +{ + return aa_unpack_u32(e, &h->mask, NULL) && + aa_unpack_u32(e, &h->count, NULL) && + aa_unpack_u32(e, &h->size, NULL) && + aa_unpack_u32(e, &h->tags, NULL); +} + +static int unpack_tag_headers(struct aa_ext *e, struct aa_tags_struct *tags) +{ + struct aa_tags_header *hdrs; + u16 i, size; + int error = -EPROTO; + void *pos = e->pos; + + if (!aa_unpack_array(e, "hdrs", &size)) + goto fail_reset; + hdrs = kcalloc(size, sizeof(struct aa_tags_header), GFP_KERNEL); + if (!hdrs) { + error = -ENOMEM; + goto fail_reset; + } + for (i = 0; i < size; i++) { + if (!unpack_tag_header_ent(e, &hdrs[i])) + goto fail; + } + if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL)) + goto fail; + + tags->hdrs.size = size; + tags->hdrs.table = hdrs; + AA_DEBUG(DEBUG_UNPACK, "headers %ld size %d", (long) hdrs, size); + return true; + +fail: + kfree_sensitive(hdrs); +fail_reset: + e->pos = pos; + return error; +} + + +static int unpack_tags(struct aa_ext *e, struct aa_tags_struct *tags, + const char **info) +{ + int error = -EPROTO; + void *pos = e->pos; + + AA_BUG(!tags); + /* policy tags are optional */ + if (aa_unpack_nameX(e, AA_STRUCT, "tags")) { + u32 version; + + if (!aa_unpack_u32(e, &version, "version") || version != 1) { + *info = "invalid tags version"; + goto fail_reset; + } + error = unpack_strs_table(e, "strs", true, &tags->strs); + if (error) { + *info = "failed to unpack profile tag.strs"; + goto fail; + } + error = unpack_tag_headers(e, tags); + if (error) { + *info = "failed to unpack profile tag.headers"; + goto fail; + } + error = unpack_tagsets(e, tags); + if (error) { + *info = "failed to unpack profile tag.sets"; + goto fail; + } + if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) + goto fail; + + if (!verify_tags(tags, info)) + goto fail; + } + + return 0; + +fail: + aa_destroy_tags(tags); +fail_reset: + e->pos = pos; + return error; +} + static bool unpack_perm(struct aa_ext *e, u32 version, struct aa_perms *perm) { u32 reserved; @@ -687,8 +924,10 @@ static ssize_t unpack_perms_table(struct aa_ext *e, struct aa_perms **perms) if (!aa_unpack_array(e, NULL, &size)) goto fail_reset; *perms = kcalloc(size, sizeof(struct aa_perms), GFP_KERNEL); - if (!*perms) - goto fail_reset; + if (!*perms) { + e->pos = pos; + return -ENOMEM; + } for (i = 0; i < size; i++) { if (!unpack_perm(e, version, &(*perms)[i])) goto fail; @@ -723,6 +962,11 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, if (!pdb) return -ENOMEM; + AA_DEBUG(DEBUG_UNPACK, "unpacking tags"); + if (unpack_tags(e, &pdb->tags, info) < 0) + goto fail; + AA_DEBUG(DEBUG_UNPACK, "done unpacking tags"); + size = unpack_perms_table(e, &pdb->perms); if (size < 0) { error = size; @@ -795,13 +1039,14 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, * transition table may be present even when the dfa is * not. For compatibility reasons unpack and discard. */ - if (!unpack_trans_table(e, &pdb->trans) && required_trans) { + error = unpack_strs_table(e, "xtable", false, &pdb->trans); + if (error && required_trans) { *info = "failed to unpack profile transition table"; goto fail; } if (!pdb->dfa && pdb->trans.table) - aa_free_str_table(&pdb->trans); + aa_destroy_str_table(&pdb->trans); /* TODO: * - move compat mapping here, requires dfa merging first @@ -1058,6 +1303,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } else if (rules->file->dfa) { if (!rules->file->perms) { + AA_DEBUG(DEBUG_UNPACK, "compat mapping perms"); error = aa_compat_map_file(rules->file); if (error) { info = "failed to remap file permission table"; @@ -1260,7 +1506,7 @@ static bool verify_perms(struct aa_policydb *pdb) if (xmax < xidx) xmax = xidx; } - if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->trans.size) + if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->tags.sets.size) return false; if (pdb->perms[i].label && pdb->perms[i].label >= pdb->trans.size) @@ -1268,7 +1514,7 @@ static bool verify_perms(struct aa_policydb *pdb) } /* deal with incorrectly constructed string tables */ if (xmax == -1) { - aa_free_str_table(&pdb->trans); + aa_destroy_str_table(&pdb->trans); } else if (pdb->trans.size > xmax + 1) { if (!aa_resize_str_table(&pdb->trans, xmax + 1, GFP_KERNEL)) return false; |
