]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
apparmor: make str table more generic and be able to have multiple entries
authorJohn Johansen <john.johansen@canonical.com>
Fri, 1 Aug 2025 09:21:44 +0000 (02:21 -0700)
committerJohn Johansen <john.johansen@canonical.com>
Thu, 22 Jan 2026 12:56:39 +0000 (04:56 -0800)
The strtable is currently limited to a single entry string on unpack
even though domain has the concept of multiple entries within it. Make
this a reality as it will be used for tags and more advanced domain
transitions.

Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
security/apparmor/domain.c
security/apparmor/include/lib.h
security/apparmor/lib.c
security/apparmor/policy.c
security/apparmor/policy_unpack.c

index 267da82afb14ab3c7bce5a17b3945f12c16db8fc..ee2df25f355d2781cb33ac7a7e853c135202c4ac 100644 (file)
@@ -529,7 +529,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
        /* TODO: move lookup parsing to unpack time so this is a straight
         *       index into the resultant label
         */
-       for (next = rules->file->trans.table[index]; next;
+       for (next = rules->file->trans.table[index].strs; next;
             next = next_name(xtype, next)) {
                const char *lookup = (*next == '&') ? next + 1 : next;
                *name = next;
index 444197075fd6b57928fbec39bb094622a544be8c..194be85e7fffefe783fc0d0b5b08132f9fe20f47 100644 (file)
@@ -30,6 +30,7 @@ extern struct aa_dfa *stacksplitdfa;
 #define DEBUG_DOMAIN 4
 #define DEBUG_POLICY 8
 #define DEBUG_INTERFACE 0x10
+#define DEBUG_UNPACK 0x40
 
 #define DEBUG_ALL 0x1f         /* update if new DEBUG_X added */
 #define DEBUG_PARSE_ERROR (-1)
@@ -119,13 +120,19 @@ static inline bool path_mediated_fs(struct dentry *dentry)
        return !(dentry->d_sb->s_flags & SB_NOUSER);
 }
 
+struct aa_str_table_ent {
+       int count;
+       int size;
+       char *strs;
+};
+
 struct aa_str_table {
        int size;
-       char **table;
+       struct aa_str_table_ent *table;
 };
 
-void aa_free_str_table(struct aa_str_table *table);
 bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp);
+void aa_destroy_str_table(struct aa_str_table *table);
 
 struct counted_str {
        struct kref count;
index acf7f5189beca79e7ee575fbd88f5bc30e75e0ed..7ef1b9ba7fb67e38c1121a3a55b0b2512462f171 100644 (file)
@@ -44,6 +44,7 @@ static struct val_table_ent debug_values_table[] = {
        { "domain", DEBUG_DOMAIN },
        { "policy", DEBUG_POLICY },
        { "interface", DEBUG_INTERFACE },
+       { "unpack", DEBUG_UNPACK },
        { NULL, 0 }
 };
 
@@ -118,7 +119,7 @@ int aa_print_debug_params(char *buffer)
 
 bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
 {
-       char **n;
+       struct aa_str_table_ent *n;
        int i;
 
        if (t->size == newsize)
@@ -129,7 +130,7 @@ bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
        for (i = 0; i < min(t->size, newsize); i++)
                n[i] = t->table[i];
        for (; i < t->size; i++)
-               kfree_sensitive(t->table[i]);
+               kfree_sensitive(t->table[i].strs);
        if (newsize > t->size)
                memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n));
        kfree_sensitive(t->table);
@@ -140,10 +141,10 @@ bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp)
 }
 
 /**
- * aa_free_str_table - free entries str table
+ * aa_destroy_str_table - free entries str table
  * @t: the string table to free  (MAYBE NULL)
  */
-void aa_free_str_table(struct aa_str_table *t)
+void aa_destroy_str_table(struct aa_str_table *t)
 {
        int i;
 
@@ -152,7 +153,7 @@ void aa_free_str_table(struct aa_str_table *t)
                        return;
 
                for (i = 0; i < t->size; i++)
-                       kfree_sensitive(t->table[i]);
+                       kfree_sensitive(t->table[i].strs);
                kfree_sensitive(t->table);
                t->table = NULL;
                t->size = 0;
index b09323867feaae728f1bea543fd5d311da1773cd..2a8a0e4a19fcb8d50175ebd540e738ae524971b2 100644 (file)
@@ -104,7 +104,7 @@ static void aa_free_pdb(struct aa_policydb *pdb)
        if (pdb) {
                aa_put_dfa(pdb->dfa);
                kvfree(pdb->perms);
-               aa_free_str_table(&pdb->trans);
+               aa_destroy_str_table(&pdb->trans);
                kfree(pdb);
        }
 }
index 7523971e37d9c0672bc09062d38522eb5803d241..4f7cb42073e45b27596481b377deaeb11d249d82 100644 (file)
@@ -450,20 +450,72 @@ 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",
+                                        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
  */
-static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs)
+static bool 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;
 
        /* 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,7 +527,8 @@ 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);
+               table = kcalloc(size, sizeof(struct aa_str_table_ent),
+                               GFP_KERNEL);
                if (!table)
                        goto fail;
 
@@ -483,41 +536,23 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs)
                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)
+                       c = process_strs_entry(str, size2, multi);
+                       if (c <= 0) {
+                               AA_DEBUG(DEBUG_UNPACK, "process_strs %d", c);
                                goto fail;
-                       table[i] = str;
-                       /* verify that name doesn't start with space */
-                       if (isspace(*str))
-                               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;
@@ -527,7 +562,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_str_table *strs)
        return true;
 
 fail:
-       aa_free_str_table(strs);
+       aa_destroy_str_table(strs);
        e->pos = saved_pos;
        return false;
 }
@@ -795,13 +830,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) {
+       if (!unpack_strs_table(e, "xtable", false, &pdb->trans) &&
+           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
@@ -1268,7 +1304,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;