diff options
| author | John Johansen <john.johansen@canonical.com> | 2025-04-01 15:51:41 -0700 |
|---|---|---|
| committer | John Johansen <john.johansen@canonical.com> | 2026-01-29 01:27:47 -0800 |
| commit | 3d28e2397af7a89ac3de33c686ed404cda59b5d5 (patch) | |
| tree | 0b0fe894927b882385ea87b4a20e2f8a01224da3 | |
| parent | c140dcd1246bfe705921ca881bbb247ff1ba2bca (diff) | |
apparmor: add support loading per permission tagging
Add support for the per permission tag index for a given permission
set. This will be used by both meta-data tagging, to allow annotating
accept states with context and debug information. As well as by rule
tainting and triggers to specify the taint or trigger to be applied.
Since these are low frequency ancillary data items they are stored
in a tighter packed format to that allows for sharing and reuse of the
strings between permissions and accept states. Reducing the amount of
kernel memory use at the cost of having to go through a couple if
index based indirections.
The tags are just strings that has no meaning with out context. When
used as meta-data for auditing and debugging its entirely information
for userspace, but triggers, and tainting can be used to affect the
domain. However they all exist in the same packed data set and can
be shared between different uses.
Signed-off-by: John Johansen <john.johansen@canonical.com>
| -rw-r--r-- | security/apparmor/file.c | 1 | ||||
| -rw-r--r-- | security/apparmor/include/audit.h | 2 | ||||
| -rw-r--r-- | security/apparmor/include/lib.h | 5 | ||||
| -rw-r--r-- | security/apparmor/include/policy.h | 32 | ||||
| -rw-r--r-- | security/apparmor/lib.c | 2 | ||||
| -rw-r--r-- | security/apparmor/policy.c | 8 | ||||
| -rw-r--r-- | security/apparmor/policy_compat.c | 10 | ||||
| -rw-r--r-- | security/apparmor/policy_unpack.c | 227 |
8 files changed, 269 insertions, 18 deletions
diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 919dbbbc87ab..b69fece45ade 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -103,6 +103,7 @@ int aa_audit_file(const struct cred *subj_cred, ad.subj_cred = subj_cred; ad.request = request; + ad.tags = perms->tag; ad.name = name; ad.fs.target = target; ad.peer = tlabel; diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 1a71a94ea19c..aa00b34404f9 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -119,6 +119,8 @@ struct apparmor_audit_data { const char *info; u32 request; u32 denied; + u32 tags; + union { /* these entries require a custom callback fn */ struct { diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 194be85e7fff..7ca8a92c449c 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -30,9 +30,10 @@ extern struct aa_dfa *stacksplitdfa; #define DEBUG_DOMAIN 4 #define DEBUG_POLICY 8 #define DEBUG_INTERFACE 0x10 -#define DEBUG_UNPACK 0x40 +#define DEBUG_UNPACK 0x20 +#define DEBUG_TAGS 0x40 -#define DEBUG_ALL 0x1f /* update if new DEBUG_X added */ +#define DEBUG_ALL 0x7f /* update if new DEBUG_X added */ #define DEBUG_PARSE_ERROR (-1) #define DEBUG_ON (aa_g_debug != DEBUG_NONE) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 4c50875c9d13..5115ebae2661 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -79,11 +79,33 @@ enum profile_mode { }; +struct aa_tags_header { + u32 mask; /* bit mask matching permissions */ + u32 count; /* number of strings per entry */ + u32 size; /* size of all strings covered by count */ + u32 tags; /* index into string table */ +}; + +struct aa_tags_struct { + struct { + u32 size; /* number of entries in tagsets */ + u32 *table; /* indexes into headers & strs */ + } sets; + struct { + u32 size; /* number of headers == num of strs */ + struct aa_tags_header *table; + } hdrs; + struct aa_str_table strs; +}; + /* struct aa_policydb - match engine for a policy - * count: refcount for the pdb - * dfa: dfa pattern match - * perms: table of permissions - * strs: table of strings, index by x + * @count: refcount for the pdb + * @dfa: dfa pattern match + * @perms: table of permissions + * @size: number of entries in @perms + * @trans: table of strings, index by x + * @tags: table of tags that perms->tag indexes + * @start:_states to start in for each class * start: set of start states for the different classes of data */ struct aa_policydb { @@ -94,11 +116,13 @@ struct aa_policydb { u32 size; }; struct aa_str_table trans; + struct aa_tags_struct tags; aa_state_t start[AA_CLASS_LAST + 1]; }; extern struct aa_policydb *nullpdb; +void aa_destroy_tags(struct aa_tags_struct *tags); struct aa_policydb *aa_alloc_pdb(gfp_t gfp); void aa_pdb_free_kref(struct kref *kref); diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7ef1b9ba7fb6..d0b82771df01 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -45,6 +45,7 @@ static struct val_table_ent debug_values_table[] = { { "policy", DEBUG_POLICY }, { "interface", DEBUG_INTERFACE }, { "unpack", DEBUG_UNPACK }, + { "tags", DEBUG_TAGS }, { NULL, 0 } }; @@ -511,3 +512,4 @@ void aa_policy_destroy(struct aa_policy *policy) /* don't free name as its a subset of hname */ aa_put_str(policy->hname); } + diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 2a8a0e4a19fc..f2cef22ed729 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -98,6 +98,13 @@ const char *const aa_profile_mode_names[] = { "user", }; +void aa_destroy_tags(struct aa_tags_struct *tags) +{ + kfree_sensitive(tags->hdrs.table); + kfree_sensitive(tags->sets.table); + aa_destroy_str_table(&tags->strs); + memset(tags, 0, sizeof(*tags)); +} static void aa_free_pdb(struct aa_policydb *pdb) { @@ -105,6 +112,7 @@ static void aa_free_pdb(struct aa_policydb *pdb) aa_put_dfa(pdb->dfa); kvfree(pdb->perms); aa_destroy_str_table(&pdb->trans); + aa_destroy_tags(&pdb->tags); kfree(pdb); } } diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index cfc2207e5a12..c863fc10a6f7 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -263,9 +263,15 @@ static struct aa_perms *compute_perms(struct aa_dfa *dfa, u32 version, *size = state_count; /* zero init so skip the trap state (state == 0) */ - for (state = 1; state < state_count; state++) + for (state = 1; state < state_count; state++) { table[state] = compute_perms_entry(dfa, state, version); - + AA_DEBUG(DEBUG_UNPACK, + "[%d]: (0x%x/0x%x/0x%x//0x%x/0x%x//0x%x), converted from accept1: 0x%x, accept2: 0x%x", + state, table[state].allow, table[state].deny, + table[state].prompt, table[state].audit, + table[state].quiet, table[state].xindex, + ACCEPT_TABLE(dfa)[state], ACCEPT_TABLE2(dfa)[state]); + } return table; } diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 4f7cb42073e4..b6e18ddff331 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -506,13 +506,14 @@ static int process_strs_entry(char *str, int size, bool multi) * @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_strs_table(struct aa_ext *e, const char *name, bool multi, +static int unpack_strs_table(struct aa_ext *e, const char *name, bool multi, struct aa_str_table *strs) { void *saved_pos = e->pos; struct aa_str_table_ent *table = NULL; + int error = -EPROTO; /* exec table is optional */ if (aa_unpack_nameX(e, AA_STRUCT, name)) { @@ -529,9 +530,10 @@ static bool unpack_strs_table(struct aa_ext *e, const char *name, bool multi, goto fail; table = kcalloc(size, sizeof(struct aa_str_table_ent), GFP_KERNEL); - if (!table) + if (!table) { + error = -ENOMEM; goto fail; - + } strs->table = table; strs->size = size; for (i = 0; i < size; i++) { @@ -542,7 +544,8 @@ static bool unpack_strs_table(struct aa_ext *e, const char *name, bool multi, */ c = process_strs_entry(str, size2, multi); if (c <= 0) { - AA_DEBUG(DEBUG_UNPACK, "process_strs %d", c); + AA_DEBUG(DEBUG_UNPACK, "process_strs %d i %d pos %ld", + c, i, e->pos - saved_pos); goto fail; } if (!multi && c > 1) { @@ -559,12 +562,12 @@ static bool unpack_strs_table(struct aa_ext *e, const char *name, bool multi, if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) goto fail; } - return true; + return 0; fail: 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) @@ -679,6 +682,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 size_t 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; @@ -758,6 +959,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; @@ -830,8 +1036,8 @@ 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_strs_table(e, "xtable", false, &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; } @@ -1094,6 +1300,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"; @@ -1296,7 +1503,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) |
