summaryrefslogtreecommitdiff
path: root/security/apparmor/policy_unpack.c
diff options
context:
space:
mode:
Diffstat (limited to 'security/apparmor/policy_unpack.c')
-rw-r--r--security/apparmor/policy_unpack.c336
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;