From c602537de3c137e55582d7fccfb18e50f1cd9c83 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Fri, 20 Dec 2024 20:22:42 +0100 Subject: [PATCH 01/60] apparmor: Use str_yes_no() helper function Remove hard-coded strings by using the str_yes_no() helper function. Fix a typo in a comment: s/unpritable/unprintable/ Signed-off-by: Thorsten Blum Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 2c0185ebc900..1bce9a7d2129 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -997,7 +997,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v) switch (fs_file->v_type) { case AA_SFS_TYPE_BOOLEAN: - seq_printf(seq, "%s\n", fs_file->v.boolean ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(fs_file->v.boolean)); break; case AA_SFS_TYPE_STRING: seq_printf(seq, "%s\n", fs_file->v.string); @@ -1006,7 +1006,7 @@ static int aa_sfs_seq_show(struct seq_file *seq, void *v) seq_printf(seq, "%#08lx\n", fs_file->v.u64); break; default: - /* Ignore unpritable entry types. */ + /* Ignore unprintable entry types. */ break; } @@ -1152,7 +1152,7 @@ static int seq_ns_stacked_show(struct seq_file *seq, void *v) struct aa_label *label; label = begin_current_label_crit_section(); - seq_printf(seq, "%s\n", label->size > 1 ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(label->size > 1)); end_current_label_crit_section(label); return 0; @@ -1175,7 +1175,7 @@ static int seq_ns_nsstacked_show(struct seq_file *seq, void *v) } } - seq_printf(seq, "%s\n", count > 1 ? "yes" : "no"); + seq_printf(seq, "%s\n", str_yes_no(count > 1)); end_current_label_crit_section(label); return 0; From 71e6cff3e0dde6f6a3355d6c73ca3e176567995e Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 23 Sep 2022 16:36:10 -0700 Subject: [PATCH 02/60] apparmor: Improve debug print infrastructure Make it so apparmor debug output can be controlled by class flags as well as the debug flag on labels. This provides much finer control at what is being output so apparmor doesn't flood the logs with information that is not needed, making it hard to find what is important. Signed-off-by: John Johansen --- security/apparmor/domain.c | 19 +++--- security/apparmor/include/apparmor.h | 2 +- security/apparmor/include/lib.h | 37 +++++++---- security/apparmor/label.c | 12 ++-- security/apparmor/lib.c | 91 ++++++++++++++++++++++++++++ security/apparmor/lsm.c | 36 ++++++++++- security/apparmor/policy.c | 6 +- security/apparmor/policy_ns.c | 2 +- security/apparmor/procattr.c | 6 +- 9 files changed, 177 insertions(+), 34 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 5939bd9a9b9b..c906ab98f53a 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -652,7 +652,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, if (error) { if (profile_unconfined(profile) || (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { - AA_DEBUG("name lookup ix on error"); + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; new = aa_get_newest_label(&profile->label); } @@ -664,10 +664,10 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, new = find_attach(bprm, profile->ns, &profile->ns->base.profiles, name, &info); if (new) { - AA_DEBUG("unconfined attached to new label"); + AA_DEBUG(DEBUG_DOMAIN, "unconfined attached to new label"); return new; } - AA_DEBUG("unconfined exec no attachment"); + AA_DEBUG(DEBUG_DOMAIN, "unconfined exec no attachment"); return aa_get_newest_label(&profile->label); } @@ -766,7 +766,7 @@ static int profile_onexec(const struct cred *subj_cred, if (error) { if (profile_unconfined(profile) || (profile->label.flags & FLAG_IX_ON_NAME_ERROR)) { - AA_DEBUG("name lookup ix on error"); + AA_DEBUG(DEBUG_DOMAIN, "name lookup ix on error"); error = 0; } xname = bprm->filename; @@ -1216,7 +1216,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) if (task_no_new_privs(current) && !unconfined(label) && !aa_label_is_unconfined_subset(new, ctx->nnp)) { /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); error = -EPERM; goto out; } @@ -1237,7 +1238,8 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) if (task_no_new_privs(current) && !unconfined(label) && !aa_label_is_unconfined_subset(previous, ctx->nnp)) { /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); error = -EPERM; goto out; } @@ -1343,7 +1345,7 @@ int aa_change_profile(const char *fqname, int flags) if (!fqname || !*fqname) { aa_put_label(label); - AA_DEBUG("no profile name"); + AA_DEBUG(DEBUG_DOMAIN, "no profile name"); return -EINVAL; } @@ -1462,7 +1464,8 @@ check: if (task_no_new_privs(current) && !unconfined(label) && !aa_label_is_unconfined_subset(new, ctx->nnp)) { /* not an apparmor denial per se, so don't log it */ - AA_DEBUG("no_new_privs - change_hat denied"); + AA_DEBUG(DEBUG_DOMAIN, + "no_new_privs - change_hat denied"); error = -EPERM; goto out; } diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index f83934913b0f..56767b1a8f06 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -43,7 +43,7 @@ /* Control parameters settable through module/boot flags */ extern enum audit_mode aa_g_audit; extern bool aa_g_audit_header; -extern bool aa_g_debug; +extern int aa_g_debug; extern bool aa_g_hash_policy; extern bool aa_g_export_binary; extern int aa_g_rawdata_compression_level; diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index f11a0db7f51d..256f4577c653 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -18,23 +18,35 @@ extern struct aa_dfa *stacksplitdfa; -/* - * DEBUG remains global (no per profile flag) since it is mostly used in sysctl - * which is not related to profile accesses. - */ - -#define DEBUG_ON (aa_g_debug) /* * split individual debug cases out in preparation for finer grained * debug controls in the future. */ -#define AA_DEBUG_LABEL DEBUG_ON #define dbg_printk(__fmt, __args...) pr_debug(__fmt, ##__args) -#define AA_DEBUG(fmt, args...) \ + +#define DEBUG_NONE 0 +#define DEBUG_LABEL_ABS_ROOT 1 +#define DEBUG_LABEL 2 +#define DEBUG_DOMAIN 4 +#define DEBUG_POLICY 8 +#define DEBUG_INTERFACE 0x10 + +#define DEBUG_ALL 0x1f /* update if new DEBUG_X added */ +#define DEBUG_PARSE_ERROR (-1) + +#define DEBUG_ON (aa_g_debug != DEBUG_NONE) +#define DEBUG_ABS_ROOT (aa_g_debug & DEBUG_LABEL_ABS_ROOT) + +#define AA_DEBUG(opt, fmt, args...) \ do { \ - if (DEBUG_ON) \ - pr_debug_ratelimited("AppArmor: " fmt, ##args); \ + if (aa_g_debug & opt) \ + pr_warn_ratelimited("%s: " fmt, __func__, ##args); \ } while (0) +#define AA_DEBUG_LABEL(LAB, X, fmt, args) \ +do { \ + if ((LAB)->flags & FLAG_DEBUG1) \ + AA_DEBUG(X, fmt, args); \ +} while (0) #define AA_WARN(X) WARN((X), "APPARMOR WARN %s: %s\n", __func__, #X) @@ -51,6 +63,9 @@ extern struct aa_dfa *stacksplitdfa; #define AA_BUG_FMT(X, fmt, args...) no_printk(fmt, ##args) #endif +int aa_parse_debug_params(const char *str); +int aa_print_debug_params(char *buffer); + #define AA_ERROR(fmt, args...) \ pr_err_ratelimited("AppArmor: " fmt, ##args) @@ -281,7 +296,7 @@ __do_cleanup: \ } \ __done: \ if (!__new_) \ - AA_DEBUG("label build failed\n"); \ + AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ (__new_); \ }) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 91483ecacc16..f950dcc1842b 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -431,7 +431,7 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) /* + 1 for null terminator entry on vec */ new = kzalloc(struct_size(new, vec, size + 1), gfp); - AA_DEBUG("%s (%p)\n", __func__, new); + AA_DEBUG(DEBUG_LABEL, "%s (%p)\n", __func__, new); if (!new) goto fail; @@ -1617,7 +1617,7 @@ int aa_label_snxprint(char *str, size_t size, struct aa_ns *ns, AA_BUG(!str && size != 0); AA_BUG(!label); - if (AA_DEBUG_LABEL && (flags & FLAG_ABS_ROOT)) { + if (DEBUG_ABS_ROOT && (flags & FLAG_ABS_ROOT)) { ns = root_ns; len = snprintf(str, size, "_"); update_for_len(total, len, size, str); @@ -1731,7 +1731,7 @@ void aa_label_xaudit(struct audit_buffer *ab, struct aa_ns *ns, display_mode(ns, label, flags)) { len = aa_label_asxprint(&name, ns, label, flags, gfp); if (len < 0) { - AA_DEBUG("label print error"); + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } str = name; @@ -1759,7 +1759,7 @@ void aa_label_seq_xprint(struct seq_file *f, struct aa_ns *ns, len = aa_label_asxprint(&str, ns, label, flags, gfp); if (len < 0) { - AA_DEBUG("label print error"); + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } seq_puts(f, str); @@ -1782,7 +1782,7 @@ void aa_label_xprintk(struct aa_ns *ns, struct aa_label *label, int flags, len = aa_label_asxprint(&str, ns, label, flags, gfp); if (len < 0) { - AA_DEBUG("label print error"); + AA_DEBUG(DEBUG_LABEL, "label print error"); return; } pr_info("%s", str); @@ -1865,7 +1865,7 @@ struct aa_label *aa_label_strn_parse(struct aa_label *base, const char *str, AA_BUG(!str); str = skipn_spaces(str, n); - if (str == NULL || (AA_DEBUG_LABEL && *str == '_' && + if (str == NULL || (DEBUG_ABS_ROOT && *str == '_' && base != &root_ns->unconfined->label)) return ERR_PTR(-EINVAL); diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7db62213e352..dd5dcbe5daf7 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -25,6 +25,97 @@ struct aa_perms allperms = { .allow = ALL_PERMS_MASK, .quiet = ALL_PERMS_MASK, .hide = ALL_PERMS_MASK }; +struct val_table_ent { + const char *str; + int value; +}; + +struct val_table_ent debug_values_table[] = { + { "N", DEBUG_NONE }, + { "none", DEBUG_NONE }, + { "n", DEBUG_NONE }, + { "0", DEBUG_NONE }, + { "all", DEBUG_ALL }, + { "Y", DEBUG_ALL }, + { "y", DEBUG_ALL }, + { "1", DEBUG_ALL }, + { "abs_root", DEBUG_LABEL_ABS_ROOT }, + { "label", DEBUG_LABEL }, + { "domain", DEBUG_DOMAIN }, + { "policy", DEBUG_POLICY }, + { "interface", DEBUG_INTERFACE }, + { NULL, 0 } +}; + +static struct val_table_ent *val_table_find_ent(struct val_table_ent *table, + const char *name, size_t len) +{ + struct val_table_ent *entry; + + for (entry = table; entry->str != NULL; entry++) { + if (strncmp(entry->str, name, len) == 0 && + strlen(entry->str) == len) + return entry; + } + return NULL; +} + +int aa_parse_debug_params(const char *str) +{ + struct val_table_ent *ent; + const char *next; + int val = 0; + + do { + size_t n = strcspn(str, "\r\n,"); + + next = str + n; + ent = val_table_find_ent(debug_values_table, str, next - str); + if (ent) + val |= ent->value; + else + AA_DEBUG(DEBUG_INTERFACE, "unknown debug type '%.*s'", + (int)(next - str), str); + str = next + 1; + } while (*next != 0); + return val; +} + +/** + * aa_mask_to_str - convert a perm mask to its short string + * @str: character buffer to store string in (at least 10 characters) + * @str_size: size of the @str buffer + * @chrs: NUL-terminated character buffer of permission characters + * @mask: permission mask to convert + */ +static int val_mask_to_str(char *str, size_t size, + const struct val_table_ent *table, u32 mask) +{ + const struct val_table_ent *ent; + int total = 0; + + for (ent = table; ent->str; ent++) { + if (ent->value && (ent->value & mask) == ent->value) { + int len = scnprintf(str, size, "%s%s", total ? "," : "", + ent->str); + size -= len; + str += len; + total += len; + mask &= ~ent->value; + } + } + + return total; +} + +int aa_print_debug_params(char *buffer) +{ + if (!aa_g_debug) + return sprintf(buffer, "N"); + return val_mask_to_str(buffer, PAGE_SIZE, debug_values_table, + aa_g_debug); +} + /** * aa_free_str_table - free entries str table * @t: the string table to free (MAYBE NULL) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 1edc12862a7d..72c3d1536f69 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1571,6 +1571,9 @@ static const struct kernel_param_ops param_ops_aalockpolicy = { .get = param_get_aalockpolicy }; +static int param_set_debug(const char *val, const struct kernel_param *kp); +static int param_get_debug(char *buffer, const struct kernel_param *kp); + static int param_set_audit(const char *val, const struct kernel_param *kp); static int param_get_audit(char *buffer, const struct kernel_param *kp); @@ -1604,8 +1607,9 @@ module_param_named(rawdata_compression_level, aa_g_rawdata_compression_level, aacompressionlevel, 0400); /* Debug mode */ -bool aa_g_debug = IS_ENABLED(CONFIG_SECURITY_APPARMOR_DEBUG_MESSAGES); -module_param_named(debug, aa_g_debug, aabool, S_IRUSR | S_IWUSR); +int aa_g_debug; +module_param_call(debug, param_set_debug, param_get_debug, + &aa_g_debug, 0600); /* Audit mode */ enum audit_mode aa_g_audit; @@ -1798,6 +1802,34 @@ static int param_get_aacompressionlevel(char *buffer, return param_get_int(buffer, kp); } +static int param_get_debug(char *buffer, const struct kernel_param *kp) +{ + if (!apparmor_enabled) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_view_capable(NULL)) + return -EPERM; + return aa_print_debug_params(buffer); +} + +static int param_set_debug(const char *val, const struct kernel_param *kp) +{ + int i; + + if (!apparmor_enabled) + return -EINVAL; + if (!val) + return -EINVAL; + if (apparmor_initialized && !aa_current_policy_admin_capable(NULL)) + return -EPERM; + + i = aa_parse_debug_params(val); + if (i == DEBUG_PARSE_ERROR) + return -EINVAL; + + aa_g_debug = i; + return 0; +} + static int param_get_audit(char *buffer, const struct kernel_param *kp) { if (!apparmor_enabled) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index d0244fab0653..25cb34e43786 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -280,7 +280,7 @@ void aa_free_profile(struct aa_profile *profile) struct aa_ruleset *rule, *tmp; struct rhashtable *rht; - AA_DEBUG("%s(%p)\n", __func__, profile); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile); if (!profile) return; @@ -833,8 +833,8 @@ bool aa_policy_admin_capable(const struct cred *subj_cred, bool capable = policy_ns_capable(subj_cred, label, user_ns, CAP_MAC_ADMIN) == 0; - AA_DEBUG("cap_mac_admin? %d\n", capable); - AA_DEBUG("policy locked? %d\n", aa_g_lock_policy); + AA_DEBUG(DEBUG_POLICY, "cap_mac_admin? %d\n", capable); + AA_DEBUG(DEBUG_POLICY, "policy locked? %d\n", aa_g_lock_policy); return aa_policy_view_capable(subj_cred, label, ns) && capable && !aa_g_lock_policy; diff --git a/security/apparmor/policy_ns.c b/security/apparmor/policy_ns.c index 1f02cfe1d974..64783ca3b0f2 100644 --- a/security/apparmor/policy_ns.c +++ b/security/apparmor/policy_ns.c @@ -107,7 +107,7 @@ static struct aa_ns *alloc_ns(const char *prefix, const char *name) struct aa_ns *ns; ns = kzalloc(sizeof(*ns), GFP_KERNEL); - AA_DEBUG("%s(%p)\n", __func__, ns); + AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, ns); if (!ns) return NULL; if (!aa_policy_init(&ns->base, prefix, name, GFP_KERNEL)) diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index e3857e3d7c6c..ce40f15d4952 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -125,12 +125,14 @@ int aa_setprocattr_changehat(char *args, size_t size, int flags) for (count = 0; (hat < end) && count < 16; ++count) { char *next = hat + strlen(hat) + 1; hats[count] = hat; - AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d hat '%s'\n" , __func__, current->pid, token, count, hat); hat = next; } } else - AA_DEBUG("%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", + AA_DEBUG(DEBUG_DOMAIN, + "%s: (pid %d) Magic 0x%llx count %d Hat '%s'\n", __func__, current->pid, token, count, ""); return aa_change_hat(hats, count, token, flags); From 280799f724088ceea409564f4412181e354aba22 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 16 Nov 2022 22:17:09 -0800 Subject: [PATCH 03/60] apparmor: cleanup: attachment perm lookup to use lookup_perms() Remove another case of code duplications. Switch to using the generic routine instead of the current custom checks. Signed-off-by: John Johansen --- security/apparmor/domain.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index c906ab98f53a..b1bf1a0b29bb 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -323,7 +323,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i], &value, value_size, GFP_KERNEL); if (size >= 0) { - u32 index, perm; + struct aa_perms *perms; /* * Check the xattr presence before value. This ensure @@ -335,9 +335,8 @@ static int aa_xattrs_match(const struct linux_binprm *bprm, /* Check xattr value */ state = aa_dfa_match_len(attach->xmatch->dfa, state, value, size); - index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; - perm = attach->xmatch->perms[index].allow; - if (!(perm & MAY_EXEC)) { + perms = aa_lookup_perms(attach->xmatch, state); + if (!(perms->allow & MAY_EXEC)) { ret = -EINVAL; goto out; } @@ -415,15 +414,14 @@ restart: if (attach->xmatch->dfa) { unsigned int count; aa_state_t state; - u32 index, perm; + struct aa_perms *perms; state = aa_dfa_leftmatch(attach->xmatch->dfa, attach->xmatch->start[AA_CLASS_XMATCH], name, &count); - index = ACCEPT_TABLE(attach->xmatch->dfa)[state]; - perm = attach->xmatch->perms[index].allow; + perms = aa_lookup_perms(attach->xmatch, state); /* any accepting state means a valid match. */ - if (perm & MAY_EXEC) { + if (perms->allow & MAY_EXEC) { int ret = 0; if (count < candidate_len) From 46b9b994dd554099b3ca74a20a0d1fb392c83a87 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 29 Jan 2023 01:55:03 -0800 Subject: [PATCH 04/60] apparmor: remove redundant unconfined check. profile_af_perm and profile_af_sk_perm are only ever called after checking that the profile is not unconfined. So we can drop these redundant checks. Signed-off-by: John Johansen --- security/apparmor/net.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 77413a519117..8b7a63c08ba1 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -118,9 +118,8 @@ int aa_profile_af_perm(struct aa_profile *profile, AA_BUG(family >= AF_MAX); AA_BUG(type < 0 || type >= SOCK_MAX); + AA_BUG(profile_unconfined(profile)); - if (profile_unconfined(profile)) - return 0; state = RULE_MEDIATES(rules, AA_CLASS_NET); if (!state) return 0; From 0bc8c6862faaa80a2c89c73cc3936cbe2d35235c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 29 Jan 2023 02:13:56 -0800 Subject: [PATCH 05/60] apparmor: switch signal mediation to use RULE_MEDIATES Currently signal mediation is using a hard coded form of the RULE_MEDIATES check. This hides the intended semantics, and means this specific check won't pickup any changes or improvements made in the RULE_MEDIATES check. Switch to using RULE_MEDIATES(). Signed-off-by: John Johansen --- security/apparmor/ipc.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 0cdf4340b02d..3566d875645e 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -85,16 +85,16 @@ static int profile_signal_perm(const struct cred *cred, struct aa_perms perms; aa_state_t state; - if (profile_unconfined(profile) || - !ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_SIGNAL)) + if (profile_unconfined(profile)) return 0; ad->subj_cred = cred; ad->peer = peer; /* TODO: secondary cache check */ - state = aa_dfa_next(rules->policy->dfa, - rules->policy->start[AA_CLASS_SIGNAL], - ad->signal); + state = RULE_MEDIATES(rules, AA_CLASS_SIGNAL); + if (!state) + return 0; + state = aa_dfa_next(rules->policy->dfa, state, ad->signal); aa_label_match(profile, rules, peer, state, false, request, &perms); aa_apply_modes_to_perms(profile, &perms); return aa_check_perms(profile, &perms, request, ad, audit_signal_cb); From cd769b05cc87fb527dbab547e65b934b45705d6b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 19 Jan 2024 00:12:16 -0800 Subject: [PATCH 06/60] apparmor: ensure labels with more than one entry have correct flags labels containing more than one entry need to accumulate flag info from profiles that the label is constructed from. This is done correctly for labels created by a merge but is not being done for labels created by an update or directly created via a parse. This technically is a bug fix, however the effect in current code is to cause early unconfined bail out to not happen (ie. without the fix it is slower) on labels that were created via update or a parse. Signed-off-by: John Johansen --- security/apparmor/label.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index f950dcc1842b..868874ef3d35 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -645,6 +645,7 @@ static bool __label_replace(struct aa_label *old, struct aa_label *new) rb_replace_node(&old->node, &new->node, &ls->root); old->flags &= ~FLAG_IN_TREE; new->flags |= FLAG_IN_TREE; + new->flags |= accum_vec_flags(new->vec, new->size); return true; } @@ -705,6 +706,7 @@ static struct aa_label *__label_insert(struct aa_labelset *ls, rb_link_node(&label->node, parent, new); rb_insert_color(&label->node, &ls->root); label->flags |= FLAG_IN_TREE; + label->flags |= accum_vec_flags(label->vec, label->size); return aa_get_label(label); } @@ -1085,7 +1087,6 @@ static struct aa_label *label_merge_insert(struct aa_label *new, else if (k == b->size) return aa_get_label(b); } - new->flags |= accum_vec_flags(new->vec, new->size); ls = labels_set(new); write_lock_irqsave(&ls->lock, flags); label = __label_insert(labels_set(new), new, false); From 35fad5b462224e0da3764f68b69827281eeaac8c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 19 Jan 2024 00:24:03 -0800 Subject: [PATCH 07/60] apparmor: remove explicit restriction that unconfined cannot use change_hat There does not need to be an explicit restriction that unconfined can't use change_hat. Traditionally unconfined doesn't have hats so change_hat could not be used. But newer unconfined profiles have the potential of having hats, and even system unconfined will be able to be replaced with a profile that allows for hats. To remain backwards compitible with expected return codes, continue to return -EPERM if the unconfined profile does not have any hats. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 1 + security/apparmor/domain.c | 20 +++++++++++++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 1bce9a7d2129..e42ac7aadd31 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2332,6 +2332,7 @@ static struct aa_sfs_entry aa_sfs_entry_attach[] = { static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("change_hat", 1), AA_SFS_FILE_BOOLEAN("change_hatv", 1), + AA_SFS_FILE_BOOLEAN("unconfined_allowed_children", 1), AA_SFS_FILE_BOOLEAN("change_onexec", 1), AA_SFS_FILE_BOOLEAN("change_profile", 1), AA_SFS_FILE_BOOLEAN("stack", 1), diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b1bf1a0b29bb..af196005d5ee 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -1186,10 +1186,24 @@ int aa_change_hat(const char *hats[], int count, u64 token, int flags) if (task_no_new_privs(current) && !unconfined(label) && !ctx->nnp) ctx->nnp = aa_get_label(label); + /* return -EPERM when unconfined doesn't have children to avoid + * changing the traditional error code for unconfined. + */ if (unconfined(label)) { - info = "unconfined can not change_hat"; - error = -EPERM; - goto fail; + struct label_it i; + bool empty = true; + + rcu_read_lock(); + label_for_each_in_ns(i, labels_ns(label), label, profile) { + empty &= list_empty(&profile->base.profiles); + } + rcu_read_unlock(); + + if (empty) { + info = "unconfined can not change_hat"; + error = -EPERM; + goto fail; + } } if (count) { From 34d31f23385b018890295414acaee31d786cf73d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 19 Jan 2024 01:23:55 -0800 Subject: [PATCH 08/60] apparmor: cleanup: refactor file_perm() to doc semantics of some checks Provide semantics, via fn names, for some checks being done in file_perm(). This is a preparatory patch for improvements to both permission caching and delegation, where the check will become more involved. Signed-off-by: John Johansen --- security/apparmor/file.c | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d52a5b14dad4..81c54ffd63cb 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -557,6 +557,19 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred, return error; } +/* wrapper fn to indicate semantics of the check */ +static bool __subj_label_is_cached(struct aa_label *subj_label, + struct aa_label *obj_label) +{ + return aa_label_is_subset(obj_label, subj_label); +} + +/* for now separate fn to indicate semantics of the check */ +static bool __file_is_delegated(struct aa_label *obj_label) +{ + return unconfined(obj_label); +} + /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked @@ -594,8 +607,8 @@ int aa_file_perm(const char *op, const struct cred *subj_cred, * delegation from unconfined tasks */ denied = request & ~fctx->allow; - if (unconfined(label) || unconfined(flabel) || - (!denied && aa_label_is_subset(flabel, label))) { + if (unconfined(label) || __file_is_delegated(flabel) || + (!denied && __subj_label_is_cached(label, flabel))) { rcu_read_unlock(); goto done; } From de4754c801f4ceefc6ce0d13480c506e0a91b449 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 27 Oct 2023 10:31:06 -0700 Subject: [PATCH 09/60] apparmor: carry mediation check on label In order to speed up the mediated check, precompute and store the result as a bit per class type. This will not only allow us to speed up the mediation check but is also a step to removing the unconfined special cases as the unconfined check can be replaced with the generic label_mediates() check. Note: label check does not currently work for capabilities and resources which need to have their mediation updated first. Signed-off-by: John Johansen --- security/apparmor/include/apparmor.h | 1 + security/apparmor/include/label.h | 24 +++++++++++------------- security/apparmor/include/policy.h | 13 +++++++++++++ security/apparmor/label.c | 24 ++++++++++++++---------- security/apparmor/policy.c | 28 +++++++++++++++++++++++++++- security/apparmor/policy_unpack.c | 2 ++ 6 files changed, 68 insertions(+), 24 deletions(-) diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index 56767b1a8f06..dd12cba8139d 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -38,6 +38,7 @@ #define AA_CLASS_X 31 #define AA_CLASS_DBUS 32 +/* NOTE: if AA_CLASS_LAST > 63 need to update label->mediates */ #define AA_CLASS_LAST AA_CLASS_DBUS /* Control parameters settable through module/boot flags */ diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 93290ae300bb..5e7d199c15e2 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -129,6 +129,7 @@ struct aa_label { long flags; u32 secid; int size; + u64 mediates; struct aa_profile *vec[]; }; @@ -231,20 +232,17 @@ int aa_label_next_confined(struct aa_label *l, int i); #define fn_for_each_not_in_set(L1, L2, P, FN) \ fn_for_each2_XXX((L1), (L2), P, FN, _not_in_set) -#define LABEL_MEDIATES(L, C) \ -({ \ - struct aa_profile *profile; \ - struct label_it i; \ - int ret = 0; \ - label_for_each(i, (L), profile) { \ - if (RULE_MEDIATES(&profile->rules, (C))) { \ - ret = 1; \ - break; \ - } \ - } \ - ret; \ -}) +static inline bool label_mediates(struct aa_label *L, unsigned char C) +{ + return (L)->mediates & (((u64) 1) << (C)); +} +static inline bool label_mediates_safe(struct aa_label *L, unsigned char C) +{ + if (C > AA_CLASS_LAST) + return false; + return label_mediates(L, C); +} void aa_labelset_destroy(struct aa_labelset *ls); void aa_labelset_init(struct aa_labelset *ls); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 757e3c232c57..256fb27e5c3a 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -318,6 +318,19 @@ static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, return RULE_MEDIATES(rule, class); } +void aa_compute_profile_mediates(struct aa_profile *profile); +static inline bool profile_mediates(struct aa_profile *profile, + unsigned char class) +{ + return label_mediates(&profile->label, class); +} + +static inline bool profile_mediates_safe(struct aa_profile *profile, + unsigned char class) +{ + return label_mediates_safe(&profile->label, class); +} + /** * aa_get_profile - increment refcount on profile @p * @p: profile (MAYBE NULL) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 868874ef3d35..afded9996f61 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -198,21 +198,25 @@ static bool vec_is_stale(struct aa_profile **vec, int n) return false; } -static long accum_vec_flags(struct aa_profile **vec, int n) +static void accum_label_info(struct aa_label *new) { long u = FLAG_UNCONFINED; int i; - AA_BUG(!vec); + AA_BUG(!new->vec); - for (i = 0; i < n; i++) { - u |= vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | - FLAG_STALE); - if (!(u & vec[i]->label.flags & FLAG_UNCONFINED)) + /* size == 1 is a profile and flags must be set as part of creation */ + if (new->size == 1) + return; + + for (i = 0; i < new->size; i++) { + u |= new->vec[i]->label.flags & (FLAG_DEBUG1 | FLAG_DEBUG2 | + FLAG_STALE); + if (!(u & new->vec[i]->label.flags & FLAG_UNCONFINED)) u &= ~FLAG_UNCONFINED; + new->mediates |= new->vec[i]->label.mediates; } - - return u; + new->flags |= u; } static int sort_cmp(const void *a, const void *b) @@ -645,7 +649,7 @@ static bool __label_replace(struct aa_label *old, struct aa_label *new) rb_replace_node(&old->node, &new->node, &ls->root); old->flags &= ~FLAG_IN_TREE; new->flags |= FLAG_IN_TREE; - new->flags |= accum_vec_flags(new->vec, new->size); + accum_label_info(new); return true; } @@ -706,7 +710,7 @@ static struct aa_label *__label_insert(struct aa_labelset *ls, rb_link_node(&label->node, parent, new); rb_insert_color(&label->node, &ls->root); label->flags |= FLAG_IN_TREE; - label->flags |= accum_vec_flags(label->vec, label->size); + accum_label_info(label); return aa_get_label(label); } diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 25cb34e43786..2857e771e2a9 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -373,6 +373,30 @@ fail: return NULL; } +/* set of rules that are mediated by unconfined */ +static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING, 0 }; + +/* must be called after profile rulesets and start information is setup */ +void aa_compute_profile_mediates(struct aa_profile *profile) +{ + int c; + + if (profile_unconfined(profile)) { + int *pos; + + for (pos = unconfined_mediates; *pos; pos++) { + if (ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_NS) != + DFA_NOMATCH) + profile->label.mediates |= ((u64) 1) << AA_CLASS_NS; + } + return; + } + for (c = 0; c <= AA_CLASS_LAST; c++) { + if (ANY_RULE_MEDIATES(&profile->rules, c) != DFA_NOMATCH) + profile->label.mediates |= ((u64) 1) << c; + } +} + /* TODO: profile accounting - setup in remove */ /** @@ -624,10 +648,12 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, rules = list_first_entry(&profile->rules, typeof(*rules), list); rules->file = aa_get_pdb(nullpdb); rules->policy = aa_get_pdb(nullpdb); + aa_compute_profile_mediates(profile); if (parent) { profile->path_flags = parent->path_flags; - + /* override/inherit what is mediated from parent */ + profile->label.mediates = parent->label.mediates; /* released on free_profile */ rcu_assign_pointer(profile->parent, aa_get_profile(parent)); profile->ns = aa_get_ns(parent->ns); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 992b74c50d64..287e08ac4b4b 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1101,6 +1101,8 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) goto fail; } + aa_compute_profile_mediates(profile); + return profile; fail: From 2e12c5f060176ede209673e4f63ea5d0e3c5814c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 23 Jul 2023 02:30:33 -0700 Subject: [PATCH 10/60] apparmor: add additional flags to extended permission. This is a step towards merging the file and policy state machines. With the switch to extended permissions the state machine's ACCEPT2 table became unused freeing it up to store state specific flags. The first flags to be stored are FLAG_OWNER and FLAG other which paves the way towards merging the file and policydb perms into a single permission table. Currently Lookups based on the objects ownership conditional will still need separate fns, this will be address in a following patch. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 5 +++-- security/apparmor/domain.c | 9 ++++++--- security/apparmor/file.c | 23 ++++++++++++++--------- security/apparmor/include/file.h | 5 +++-- security/apparmor/include/policy.h | 7 ++++++- security/apparmor/policy_compat.c | 6 +++--- security/apparmor/policy_unpack.c | 20 +++++++++++++++++++- 7 files changed, 54 insertions(+), 21 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index e42ac7aadd31..65191c5fc5e3 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -626,7 +626,8 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, if (state) { struct path_cond cond = { }; - tmp = *(aa_lookup_fperms(rules->file, state, &cond)); + tmp = *(aa_lookup_condperms(current_fsuid(), + rules->file, state, &cond)); } } else if (rules->policy->dfa) { if (!RULE_MEDIATES(rules, *match_str)) @@ -2365,7 +2366,7 @@ static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_FILE_BOOLEAN("set_load", 1), /* number of out of band transitions supported */ AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED), - AA_SFS_FILE_U64("permstable32_version", 1), + AA_SFS_FILE_U64("permstable32_version", 3), AA_SFS_FILE_STRING("permstable32", PERMS32STR), AA_SFS_FILE_U64("state32", 1), AA_SFS_DIR("unconfined_restrictions", aa_sfs_entry_unconfined), diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index af196005d5ee..630806573793 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -154,7 +154,8 @@ next: if (!state) goto fail; } - *perms = *(aa_lookup_fperms(rules->file, state, &cond)); + *perms = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, perms); if ((perms->allow & request) != request) return -EACCES; @@ -209,7 +210,8 @@ static int label_components_match(struct aa_profile *profile, return 0; next: - tmp = *(aa_lookup_fperms(rules->file, state, &cond)); + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); label_for_each_cont(i, label, tp) { @@ -218,7 +220,8 @@ next: state = match_component(profile, tp, stack, start); if (!state) goto fail; - tmp = *(aa_lookup_fperms(rules->file, state, &cond)); + tmp = *(aa_lookup_condperms(current_fsuid(), rules->file, state, + &cond)); aa_apply_modes_to_perms(profile, &tmp); aa_perms_accum(perms, &tmp); } diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 81c54ffd63cb..6ce6547301dc 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -169,7 +169,8 @@ static int path_name(const char *op, const struct cred *subj_cred, struct aa_perms default_perms = {}; /** * aa_lookup_fperms - convert dfa compressed perms to internal perms - * @file_rules: the aa_policydb to lookup perms for (NOT NULL) + * @subj_uid: uid to use for subject owner test + * @rules: the aa_policydb to lookup perms for (NOT NULL) * @state: state in dfa * @cond: conditions to consider (NOT NULL) * @@ -177,18 +178,21 @@ struct aa_perms default_perms = {}; * * Returns: a pointer to a file permission set */ -struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, - aa_state_t state, struct path_cond *cond) +struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, struct aa_policydb *rules, + aa_state_t state, struct path_cond *cond) { - unsigned int index = ACCEPT_TABLE(file_rules->dfa)[state]; + unsigned int index = ACCEPT_TABLE(rules->dfa)[state]; - if (!(file_rules->perms)) + if (!(rules->perms)) return &default_perms; - if (uid_eq(current_fsuid(), cond->uid)) - return &(file_rules->perms[index]); + if ((ACCEPT_TABLE2(rules->dfa)[state] & ACCEPT_FLAG_OWNER)) { + if (uid_eq(subj_uid, cond->uid)) + return &(rules->perms[index]); + return &(rules->perms[index + 1]); + } - return &(file_rules->perms[index + 1]); + return &(rules->perms[index]); } /** @@ -207,7 +211,8 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, { aa_state_t state; state = aa_dfa_match(file_rules->dfa, start, name); - *perms = *(aa_lookup_fperms(file_rules, state, cond)); + *perms = *(aa_lookup_condperms(current_fsuid(), file_rules, state, + cond)); return state; } diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 6e8f2aa66cd6..06d9899098a6 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -77,8 +77,9 @@ int aa_audit_file(const struct cred *cred, const char *target, struct aa_label *tlabel, kuid_t ouid, const char *info, int error); -struct aa_perms *aa_lookup_fperms(struct aa_policydb *file_rules, - aa_state_t state, struct path_cond *cond); +struct aa_perms *aa_lookup_condperms(kuid_t subj_uid, + struct aa_policydb *file_rules, + aa_state_t state, struct path_cond *cond); aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, const char *name, struct path_cond *cond, struct aa_perms *perms); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 256fb27e5c3a..bfd8bf1a1ecd 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -59,6 +59,11 @@ extern const char *const aa_profile_mode_names[]; #define on_list_rcu(X) (!list_empty(X) && (X)->prev != LIST_POISON2) +/* flags in the dfa accept2 table */ +enum dfa_accept_flags { + ACCEPT_FLAG_OWNER = 1, +}; + /* * FIXME: currently need a clean way to replace and remove profiles as a * set. It should be done at the namespace level. @@ -124,6 +129,7 @@ static inline void aa_put_pdb(struct aa_policydb *pdb) kref_put(&pdb->count, aa_pdb_free_kref); } +/* lookup perm that doesn't have and object conditional */ static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, aa_state_t state) { @@ -135,7 +141,6 @@ static inline struct aa_perms *aa_lookup_perms(struct aa_policydb *policy, return &(policy->perms[index]); } - /* struct aa_data - generic data structure * key: name for retrieving this data * size: size of data in bytes diff --git a/security/apparmor/policy_compat.c b/security/apparmor/policy_compat.c index 423227670e68..cfc2207e5a12 100644 --- a/security/apparmor/policy_compat.c +++ b/security/apparmor/policy_compat.c @@ -286,10 +286,10 @@ static void remap_dfa_accept(struct aa_dfa *dfa, unsigned int factor) AA_BUG(!dfa); - for (state = 0; state < state_count; state++) + for (state = 0; state < state_count; state++) { ACCEPT_TABLE(dfa)[state] = state * factor; - kvfree(dfa->tables[YYTD_ID_ACCEPT2]); - dfa->tables[YYTD_ID_ACCEPT2] = NULL; + ACCEPT_TABLE2(dfa)[state] = factor > 1 ? ACCEPT_FLAG_OWNER : 0; + } } /* TODO: merge different dfa mappings into single map_policy fn */ diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 287e08ac4b4b..7813920a21e5 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -716,6 +716,7 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, void *pos = e->pos; int i, flags, error = -EPROTO; ssize_t size; + u32 version = 0; pdb = aa_alloc_pdb(GFP_KERNEL); if (!pdb) @@ -733,6 +734,9 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, if (pdb->perms) { /* perms table present accept is index */ flags = TO_ACCEPT1_FLAG(YYTD_DATA32); + if (aa_unpack_u32(e, &version, "permsv") && version > 2) + /* accept2 used for dfa flags */ + flags |= TO_ACCEPT2_FLAG(YYTD_DATA32); } else { /* packed perms in accept1 and accept2 */ flags = TO_ACCEPT1_FLAG(YYTD_DATA32) | @@ -770,6 +774,20 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, } } + if (pdb->perms && version <= 2) { + /* add dfa flags table missing in v2 */ + u32 noents = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_lolen; + u16 tdflags = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_flags; + size_t tsize = table_size(noents, tdflags); + + pdb->dfa->tables[YYTD_ID_ACCEPT2] = kvzalloc(tsize, GFP_KERNEL); + if (!pdb->dfa->tables[YYTD_ID_ACCEPT2]) { + *info = "failed to alloc dfa flags table"; + goto out; + } + pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_lolen = noents; + pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_flags = tdflags; + } /* * Unfortunately due to a bug in earlier userspaces, a * transition table may be present even when the dfa is @@ -785,7 +803,7 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, /* TODO: move compat mapping here, requires dfa merging first */ /* TODO: move verify here, it has to be done after compat mappings */ - +out: *policy = pdb; return 0; From 84c455decf27ce97a23fb70b58075592ab88d66a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 21 Aug 2023 16:54:58 -0700 Subject: [PATCH 11/60] apparmor: add support for profiles to define the kill signal Previously apparmor has only sent SIGKILL but there are cases where it can be useful to send a different signal. Allow the profile to optionally specify a different value. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 1 + security/apparmor/audit.c | 2 +- security/apparmor/include/ipc.h | 3 +++ security/apparmor/include/policy.h | 1 + security/apparmor/include/sig_names.h | 6 +----- security/apparmor/include/signal.h | 19 +++++++++++++++++++ security/apparmor/policy.c | 1 + security/apparmor/policy_unpack.c | 7 +++++++ 8 files changed, 34 insertions(+), 6 deletions(-) create mode 100644 security/apparmor/include/signal.h diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 65191c5fc5e3..3455d223879b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2342,6 +2342,7 @@ static struct aa_sfs_entry aa_sfs_entry_domain[] = { AA_SFS_FILE_BOOLEAN("computed_longest_left", 1), AA_SFS_DIR("attach_conditions", aa_sfs_entry_attach), AA_SFS_FILE_BOOLEAN("disconnected.path", 1), + AA_SFS_FILE_BOOLEAN("kill.signal", 1), AA_SFS_FILE_STRING("version", "1.2"), { } }; diff --git a/security/apparmor/audit.c b/security/apparmor/audit.c index 73087d76f649..ac89602aa2d9 100644 --- a/security/apparmor/audit.c +++ b/security/apparmor/audit.c @@ -192,7 +192,7 @@ int aa_audit(int type, struct aa_profile *profile, aa_audit_msg(type, ad, cb); if (ad->type == AUDIT_APPARMOR_KILL) - (void)send_sig_info(SIGKILL, NULL, + (void)send_sig_info(profile->signal, NULL, ad->common.type == LSM_AUDIT_DATA_TASK && ad->common.u.tsk ? ad->common.u.tsk : current); diff --git a/security/apparmor/include/ipc.h b/security/apparmor/include/ipc.h index 74d17052f76b..323dd071afe9 100644 --- a/security/apparmor/include/ipc.h +++ b/security/apparmor/include/ipc.h @@ -13,6 +13,9 @@ #include +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 + int aa_may_signal(const struct cred *subj_cred, struct aa_label *sender, const struct cred *target_cred, struct aa_label *target, int sig); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index bfd8bf1a1ecd..73cb84ef58f2 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -236,6 +236,7 @@ struct aa_profile { enum audit_mode audit; long mode; u32 path_flags; + int signal; const char *disconnected; struct aa_attachment attach; diff --git a/security/apparmor/include/sig_names.h b/security/apparmor/include/sig_names.h index cbf7a997ed84..c772668cdc62 100644 --- a/security/apparmor/include/sig_names.h +++ b/security/apparmor/include/sig_names.h @@ -1,9 +1,5 @@ #include - -#define SIGUNKNOWN 0 -#define MAXMAPPED_SIG 35 -#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1) -#define SIGRT_BASE 128 +#include "signal.h" /* provide a mapping of arch signal to internal signal # for mediation * those that are always an alias SIGCLD for SIGCLHD and SIGPOLL for SIGIO diff --git a/security/apparmor/include/signal.h b/security/apparmor/include/signal.h new file mode 100644 index 000000000000..729763fa7ce6 --- /dev/null +++ b/security/apparmor/include/signal.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor ipc mediation function definitions. + * + * Copyright 2023 Canonical Ltd. + */ + +#ifndef __AA_SIGNAL_H +#define __AA_SIGNAL_H + +#define SIGUNKNOWN 0 +#define MAXMAPPED_SIG 35 + +#define MAXMAPPED_SIGNAME (MAXMAPPED_SIG + 1) +#define SIGRT_BASE 128 + +#endif /* __AA_SIGNAL_H */ diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 2857e771e2a9..04222eddd890 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -364,6 +364,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, profile->label.flags |= FLAG_PROFILE; profile->label.vec[0] = profile; + profile->signal = SIGKILL; /* refcount released by caller */ return profile; diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 7813920a21e5..73139189df0f 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -29,6 +29,7 @@ #include "include/policy.h" #include "include/policy_unpack.h" #include "include/policy_compat.h" +#include "include/signal.h" /* audit callback for unpack fields */ static void audit_cb(struct audit_buffer *ab, void *va) @@ -916,6 +917,12 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) (void) aa_unpack_strdup(e, &disconnected, "disconnected"); profile->disconnected = disconnected; + /* optional */ + (void) aa_unpack_u32(e, &profile->signal, "kill"); + if (profile->signal < 1 && profile->signal > MAXMAPPED_SIG) { + info = "profile kill.signal invalid value"; + goto fail; + } /* per profile debug flags (complain, audit) */ if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) { info = "profile missing flags"; From a9eb185be84e998aa9a99c7760534ccc06216705 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jan 2024 21:54:30 -0800 Subject: [PATCH 12/60] apparmor: fix x_table_lookup when stacking is not the first entry x_table_lookup currently does stacking during label_parse() if the target specifies a stack but its only caller ensures that it will never be used with stacking. Refactor to slightly simplify the code in x_to_label(), this also fixes a long standing problem where x_to_labels check on stacking is only on the first element to the table option list, instead of the element that is found and used. Signed-off-by: John Johansen --- security/apparmor/domain.c | 52 +++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 630806573793..b9c299097372 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -509,6 +509,7 @@ static const char *next_name(int xtype, const char *name) * @name: returns: name tested to find label (NOT NULL) * * Returns: refcounted label, or NULL on failure (MAYBE NULL) + * @name will always be set with the last name tried */ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, const char **name) @@ -518,6 +519,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; + const char *next; AA_BUG(!name); @@ -525,25 +527,27 @@ 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 (*name = rules->file->trans.table[index]; !label && *name; - *name = next_name(xtype, *name)) { + for (next = rules->file->trans.table[index]; next; + next = next_name(xtype, next)) { + const char *lookup = (*next == '&') ? next + 1 : next; + *name = next; if (xindex & AA_X_CHILD) { - struct aa_profile *new_profile; - /* release by caller */ - new_profile = aa_find_child(profile, *name); - if (new_profile) - label = &new_profile->label; + /* TODO: switich to parse to get stack of child */ + struct aa_profile *new = aa_find_child(profile, lookup); + + if (new) + /* release by caller */ + return &new->label; continue; } - label = aa_label_parse(&profile->label, *name, GFP_KERNEL, + label = aa_label_parse(&profile->label, lookup, GFP_KERNEL, true, false); - if (IS_ERR(label)) - label = NULL; + if (!IS_ERR_OR_NULL(label)) + /* release by caller */ + return label; } - /* released by caller */ - - return label; + return NULL; } /** @@ -568,9 +572,9 @@ static struct aa_label *x_to_label(struct aa_profile *profile, struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), list); struct aa_label *new = NULL; + struct aa_label *stack = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; - const char *stack = NULL; switch (xtype) { case AA_X_NONE: @@ -579,13 +583,14 @@ static struct aa_label *x_to_label(struct aa_profile *profile, break; case AA_X_TABLE: /* TODO: fix when perm mapping done at unload */ - stack = rules->file->trans.table[xindex & AA_X_INDEX_MASK]; - if (*stack != '&') { - /* released by caller */ - new = x_table_lookup(profile, xindex, lookupname); - stack = NULL; + /* released by caller + * if null for both stack and direct want to try fallback + */ + new = x_table_lookup(profile, xindex, lookupname); + if (!new || **lookupname != '&') break; - } + stack = new; + new = NULL; fallthrough; /* to X_NAME */ case AA_X_NAME: if (xindex & AA_X_CHILD) @@ -600,6 +605,7 @@ static struct aa_label *x_to_label(struct aa_profile *profile, break; } + /* fallback transition check */ if (!new) { if (xindex & AA_X_INHERIT) { /* (p|c|n)ix - don't change profile but do @@ -618,12 +624,12 @@ static struct aa_label *x_to_label(struct aa_profile *profile, /* base the stack on post domain transition */ struct aa_label *base = new; - new = aa_label_parse(base, stack, GFP_KERNEL, true, false); - if (IS_ERR(new)) - new = NULL; + new = aa_label_merge(base, stack, GFP_KERNEL); + /* null on error */ aa_put_label(base); } + aa_put_label(stack); /* released by caller */ return new; } From ce9e3b3fa25a239f5c80989a1d05719bb2793fd4 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 4 Jan 2024 09:00:49 -0800 Subject: [PATCH 13/60] apparmor: add ability to mediate caps with policy state machine Currently the caps encoding is very limited and can't be used with conditionals. Allow capabilities to be mediated by the state machine. This will allow us to add conditionals to capabilities that aren't possible with the current encoding. This patch only adds support for using the state machine and retains the old encoding lookup as part of the runtime mediation code to support older policy abis. A follow on patch will move backwards compatibility to a mapping function done at policy load time. Signed-off-by: John Johansen --- security/apparmor/capability.c | 56 ++++++++++++++++++++++++++ security/apparmor/include/capability.h | 1 + security/apparmor/lsm.c | 11 +++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 7ca489ee1054..25b6219cdeb6 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -27,6 +27,7 @@ struct aa_sfs_entry aa_sfs_entry_caps[] = { AA_SFS_FILE_STRING("mask", AA_SFS_CAPS_MASK), + AA_SFS_FILE_BOOLEAN("extended", 1), { } }; @@ -123,8 +124,31 @@ static int profile_capable(struct aa_profile *profile, int cap, { struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), list); + aa_state_t state; int error; + state = RULE_MEDIATES(rules, ad->class); + if (state) { + struct aa_perms perms = { }; + u32 request; + + /* caps broken into 256 x 32 bit permission chunks */ + state = aa_dfa_next(rules->policy->dfa, state, cap >> 5); + request = 1 << (cap & 0x1f); + perms = *aa_lookup_perms(rules->policy, state); + aa_apply_modes_to_perms(profile, &perms); + + if (opts & CAP_OPT_NOAUDIT) { + if (perms.complain & request) + ad->info = "optional: no audit"; + else + ad = NULL; + } + return aa_check_perms(profile, &perms, request, ad, + audit_cb); + } + + /* fallback to old caps mediation that doesn't support conditionals */ if (cap_raised(rules->caps.allow, cap) && !cap_raised(rules->caps.denied, cap)) error = 0; @@ -168,3 +192,35 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label, return error; } + +kernel_cap_t aa_profile_capget(struct aa_profile *profile) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + aa_state_t state; + + state = RULE_MEDIATES(rules, AA_CLASS_CAP); + if (state) { + kernel_cap_t caps = CAP_EMPTY_SET; + int i; + + /* caps broken into up to 256, 32 bit permission chunks */ + for (i = 0; i < (CAP_LAST_CAP >> 5); i++) { + struct aa_perms perms = { }; + aa_state_t tmp; + + tmp = aa_dfa_next(rules->policy->dfa, state, i); + perms = *aa_lookup_perms(rules->policy, tmp); + aa_apply_modes_to_perms(profile, &perms); + caps.val |= ((u64)(perms.allow)) << (i * 5); + caps.val |= ((u64)(perms.complain)) << (i * 5); + } + return caps; + } + + /* fallback to old caps */ + if (COMPLAIN_MODE(profile)) + return CAP_FULL_SET; + + return rules->caps.allow; +} diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h index d6dcc604ec0c..1ddcec2d1160 100644 --- a/security/apparmor/include/capability.h +++ b/security/apparmor/include/capability.h @@ -36,6 +36,7 @@ struct aa_caps { extern struct aa_sfs_entry aa_sfs_entry_caps[]; +kernel_cap_t aa_profile_capget(struct aa_profile *profile); int aa_capable(const struct cred *subj_cred, struct aa_label *label, int cap, unsigned int opts); diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 72c3d1536f69..479bfea064af 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -177,14 +177,13 @@ static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effec label_for_each_confined(i, label, profile) { struct aa_ruleset *rules; - if (COMPLAIN_MODE(profile)) - continue; + kernel_cap_t allowed; + rules = list_first_entry(&profile->rules, typeof(*rules), list); - *effective = cap_intersect(*effective, - rules->caps.allow); - *permitted = cap_intersect(*permitted, - rules->caps.allow); + allowed = aa_profile_capget(profile); + *effective = cap_intersect(*effective, allowed); + *permitted = cap_intersect(*permitted, allowed); } } rcu_read_unlock(); From 9045aa25d17cf1d13a1c31fc45ed1f9ca725e30e Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 23 Apr 2024 08:59:33 -0700 Subject: [PATCH 14/60] apparmor: remove af_select macro The af_select macro just adds a layer of unnecessary abstraction that makes following what the code is doing harder. Signed-off-by: John Johansen --- security/apparmor/include/net.h | 10 ---------- security/apparmor/lsm.c | 35 +++++++++------------------------ 2 files changed, 9 insertions(+), 36 deletions(-) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index c42ed8a73f1c..82dc38e4c925 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -73,16 +73,6 @@ static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) (SK)->sk_protocol) -#define af_select(FAMILY, FN, DEF_FN) \ -({ \ - int __e; \ - switch ((FAMILY)) { \ - default: \ - __e = DEF_FN; \ - } \ - __e; \ -}) - struct aa_secmark { u8 audit; u8 deny; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 479bfea064af..1246115b7435 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1097,11 +1097,8 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) label = begin_current_label_crit_section(); if (!(kern || unconfined(label))) - error = af_select(family, - create_perm(label, family, type, protocol), - aa_af_perm(current_cred(), label, - OP_CREATE, AA_MAY_CREATE, - family, type, protocol)); + error = aa_af_perm(current_cred(), label, OP_CREATE, + AA_MAY_CREATE, family, type, protocol); end_current_label_crit_section(label); return error; @@ -1150,9 +1147,7 @@ static int apparmor_socket_bind(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - bind_perm(sock, address, addrlen), - aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk)); + return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); } static int apparmor_socket_connect(struct socket *sock, @@ -1163,9 +1158,7 @@ static int apparmor_socket_connect(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - connect_perm(sock, address, addrlen), - aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk)); + return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); } static int apparmor_socket_listen(struct socket *sock, int backlog) @@ -1174,9 +1167,7 @@ static int apparmor_socket_listen(struct socket *sock, int backlog) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - listen_perm(sock, backlog), - aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk)); + return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); } /* @@ -1190,9 +1181,7 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) AA_BUG(!newsock); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - accept_perm(sock, newsock), - aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk)); + return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); } static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, @@ -1203,9 +1192,7 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!msg); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - msg_perm(op, request, sock, msg, size), - aa_sk_perm(op, request, sock->sk)); + return aa_sk_perm(op, request, sock->sk); } static int apparmor_socket_sendmsg(struct socket *sock, @@ -1227,9 +1214,7 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - sock_perm(op, request, sock), - aa_sk_perm(op, request, sock->sk)); + return aa_sk_perm(op, request, sock->sk); } static int apparmor_socket_getsockname(struct socket *sock) @@ -1250,9 +1235,7 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!sock->sk); AA_BUG(in_interrupt()); - return af_select(sock->sk->sk_family, - opt_perm(op, request, sock, level, optname), - aa_sk_perm(op, request, sock->sk)); + return aa_sk_perm(op, request, sock->sk); } static int apparmor_socket_getsockopt(struct socket *sock, int level, From 6cc6a0523dde5b1f001d559d0e034494bc8b0db0 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 10 Apr 2024 14:49:43 -0700 Subject: [PATCH 15/60] apparmor: lift kernel socket check out of critical section There is no need for the kern check to be in the critical section, it only complicates the code and slows down the case where the socket is being created by the kernel. Lifting it out will also allow socket_create to share common template code, with other socket_permission checks. Signed-off-by: John Johansen --- security/apparmor/lsm.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 1246115b7435..f7b2d4bb1d97 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1095,10 +1095,14 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) AA_BUG(in_interrupt()); + if (kern) + return 0; + label = begin_current_label_crit_section(); - if (!(kern || unconfined(label))) + if (!unconfined(label)) { error = aa_af_perm(current_cred(), label, OP_CREATE, AA_MAY_CREATE, family, type, protocol); + } end_current_label_crit_section(label); return error; From b4940d913cc2c67f8f6bf17abbf3e5301f95e260 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 24 Apr 2024 15:54:26 -0700 Subject: [PATCH 16/60] apparmor: in preparation for finer networking rules rework match_prot Rework match_prot into a common fn that can be shared by all the networking rules. This will provide compatibility with current socket mediation, via the early bailout permission encoding. Signed-off-by: John Johansen --- security/apparmor/include/net.h | 8 +++- security/apparmor/net.c | 81 ++++++++++++++++++++++++++++----- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 82dc38e4c925..9361ba000398 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -82,10 +82,14 @@ struct aa_secmark { extern struct aa_sfs_entry aa_sfs_entry_network[]; +/* passing in state returned by XXX_mediates(class) */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, + u32 request, u16 family, int type, int protocol, + struct aa_perms **p, const char **info); void audit_net_cb(struct audit_buffer *ab, void *va); int aa_profile_af_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, - u32 request, u16 family, int type); + u32 request, u16 family, int type, int protocol); int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, u16 family, int type, int protocol); @@ -95,7 +99,7 @@ static inline int aa_profile_af_sk_perm(struct aa_profile *profile, struct sock *sk) { return aa_profile_af_perm(profile, ad, request, sk->sk_family, - sk->sk_type); + sk->sk_type, sk->sk_protocol); } int aa_sk_perm(const char *op, u32 request, struct sock *sk); diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 8b7a63c08ba1..c76e0f5dcc93 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -105,16 +105,78 @@ void audit_net_cb(struct audit_buffer *ab, void *va) } } +/* standard permission lookup pattern - supports early bailout */ +static int do_perms(struct aa_profile *profile, struct aa_policydb *policy, + unsigned int state, u32 request, + struct aa_perms *p, struct apparmor_audit_data *ad) +{ + struct aa_perms perms; + + AA_BUG(!profile); + AA_BUG(!policy); + + + if (state || !p) + p = aa_lookup_perms(policy, state); + perms = *p; + aa_apply_modes_to_perms(profile, &perms); + return aa_check_perms(profile, &perms, request, ad, + audit_net_cb); +} + +/* only continue match if + * insufficient current perms at current state + * indicates there are more perms in later state + * Returns: perms struct if early match + */ +static struct aa_perms *early_match(struct aa_policydb *policy, + aa_state_t state, u32 request) +{ + struct aa_perms *p; + + p = aa_lookup_perms(policy, state); + if (((p->allow & request) != request) && (p->allow & AA_CONT_MATCH)) + return NULL; + return p; +} + +/* passing in state returned by PROFILE_MEDIATES_AF */ +aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, + u32 request, u16 family, int type, int protocol, + struct aa_perms **p, const char **info) +{ + __be16 buffer; + + buffer = cpu_to_be16(family); + state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, 2); + if (!state) { + *info = "failed af match"; + return DFA_NOMATCH; + } + buffer = cpu_to_be16((u16)type); + state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, 2); + if (!state) + *info = "failed type match"; + *p = early_match(policy, state, request); + if (!*p) { + buffer = cpu_to_be16((u16)protocol); + state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, + 2); + if (!state) + *info = "failed protocol match"; + } + return state; +} + /* Generic af perm */ int aa_profile_af_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, u32 request, u16 family, - int type) + int type, int protocol) { struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), list); - struct aa_perms perms = { }; + struct aa_perms *p = NULL; aa_state_t state; - __be16 buffer[2]; AA_BUG(family >= AF_MAX); AA_BUG(type < 0 || type >= SOCK_MAX); @@ -124,14 +186,9 @@ int aa_profile_af_perm(struct aa_profile *profile, if (!state) return 0; - buffer[0] = cpu_to_be16(family); - buffer[1] = cpu_to_be16((u16) type); - state = aa_dfa_match_len(rules->policy->dfa, state, (char *) &buffer, - 4); - perms = *aa_lookup_perms(rules->policy, state); - aa_apply_modes_to_perms(profile, &perms); - - return aa_check_perms(profile, &perms, request, ad, audit_net_cb); + state = aa_match_to_prot(rules->policy, state, request, family, type, + protocol, &p, &ad->info); + return do_perms(profile, rules->policy, state, request, p, ad); } int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, @@ -142,7 +199,7 @@ int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, return fn_for_each_confined(label, profile, aa_profile_af_perm(profile, &ad, request, family, - type)); + type, protocol)); } static int aa_label_sk_perm(const struct cred *subj_cred, From c05e705812d179f4b85aeacc34a555a42bc4f9ac Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 7 Sep 2022 12:46:30 -0700 Subject: [PATCH 17/60] apparmor: add fine grained af_unix mediation Extend af_unix mediation to support fine grained controls based on the type (abstract, anonymous, fs), the address, and the labeling on the socket. This allows for using socket addresses to label and the socket and control which subjects can communicate. The unix rule format follows standard apparmor rules except that fs based unix sockets can be mediated by existing file rules. None fs unix sockets can be mediated by a unix socket rule. Where The address of an abstract unix domain socket begins with the @ character, similar to how they are reported (as paths) by netstat -x. The address then follows and may contain pattern matching and any characters including the null character. In apparmor null characters must be specified by using an escape sequence \000 or \x00. The pattern matching is the same as is used by file path matching so * will not match / even though it has no special meaning with in an abstract socket name. Eg. allow unix addr=@*, Autobound unix domain sockets have a unix sun_path assigned to them by the kernel, as such specifying a policy based address is not possible. The autobinding of sockets can be controlled by specifying the special auto keyword. Eg. allow unix addr=auto, To indicate that the rule only applies to auto binding of unix domain sockets. It is important to note this only applies to the bind permission as once the socket is bound to an address it is indistinguishable from a socket that have an addr bound with a specified name. When the auto keyword is used with other permissions or as part of a peer addr it will be replaced with a pattern that can match an autobound socket. Eg. For some kernels allow unix rw addr=auto, It is important to note, this pattern may match abstract sockets that were not autobound but have an addr that fits what is generated by the kernel when autobinding a socket. Anonymous unix domain sockets have no sun_path associated with the socket address, however it can be specified with the special none keyword to indicate the rule only applies to anonymous unix domain sockets. Eg. allow unix addr=none, If the address component of a rule is not specified then the rule applies to autobind, abstract and anonymous sockets. The label on the socket can be compared using the standard label= rule conditional. Eg. allow unix addr=@foo peer=(label=bar), see man apparmor.d for full syntax description. Signed-off-by: John Johansen --- security/apparmor/Makefile | 2 +- security/apparmor/af_unix.c | 702 +++++++++++++++++++++++++++ security/apparmor/apparmorfs.c | 7 + security/apparmor/file.c | 16 +- security/apparmor/include/af_unix.h | 57 +++ security/apparmor/include/apparmor.h | 1 + security/apparmor/include/file.h | 4 + security/apparmor/include/net.h | 17 +- security/apparmor/include/path.h | 1 + security/apparmor/include/policy.h | 9 +- security/apparmor/lsm.c | 163 ++++++- security/apparmor/net.c | 142 ++++-- 12 files changed, 1063 insertions(+), 58 deletions(-) create mode 100644 security/apparmor/af_unix.c create mode 100644 security/apparmor/include/af_unix.h diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index b9c5879dd599..be51607f52b6 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -6,7 +6,7 @@ obj-$(CONFIG_SECURITY_APPARMOR) += apparmor.o apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \ path.o domain.o policy.o policy_unpack.o procattr.o lsm.o \ resource.o secid.o file.o policy_ns.o label.o mount.o net.o \ - policy_compat.o + policy_compat.o af_unix.o apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c new file mode 100644 index 000000000000..ce7dc9d98fb1 --- /dev/null +++ b/security/apparmor/af_unix.c @@ -0,0 +1,702 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * AppArmor security module + * + * This file contains AppArmor af_unix fine grained mediation + * + * Copyright 2023 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ + +#include + +#include "include/audit.h" +#include "include/af_unix.h" +#include "include/apparmor.h" +#include "include/file.h" +#include "include/label.h" +#include "include/path.h" +#include "include/policy.h" +#include "include/cred.h" + + +static inline struct sock *aa_unix_sk(struct unix_sock *u) +{ + return &u->sk; +} + +static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, + struct aa_label *label, struct unix_sock *u) +{ + AA_BUG(!label); + AA_BUG(!u); + AA_BUG(!is_unix_fs(aa_unix_sk(u))); + + if (unconfined(label) || !label_mediates(label, AA_CLASS_FILE)) + return 0; + + mask &= NET_FS_PERMS; + /* if !u->path.dentry socket is being shutdown - implicit delegation + * until obj delegation is supported + */ + if (u->path.dentry) { + /* the sunpath may not be valid for this ns so use the path */ + struct path_cond cond = { u->path.dentry->d_inode->i_uid, + u->path.dentry->d_inode->i_mode + }; + + return aa_path_perm(op, subj_cred, label, &u->path, + PATH_SOCK_COND, mask, &cond); + } /* else implicitly delegated */ + + return 0; +} + +/* match_addr special constants */ +#define ABSTRACT_ADDR "\x00" /* abstract socket addr */ +#define ANONYMOUS_ADDR "\x01" /* anonymous endpoint, no addr */ +#define DISCONNECTED_ADDR "\x02" /* addr is another namespace */ +#define SHUTDOWN_ADDR "\x03" /* path addr is shutdown and cleared */ +#define FS_ADDR "/" /* path addr in fs */ + +static aa_state_t match_addr(struct aa_dfa *dfa, aa_state_t state, + struct sockaddr_un *addr, int addrlen) +{ + if (addr) + /* include leading \0 */ + state = aa_dfa_match_len(dfa, state, addr->sun_path, + unix_addr_len(addrlen)); + else + state = aa_dfa_match_len(dfa, state, ANONYMOUS_ADDR, 1); + /* todo: could change to out of band for cleaner separation */ + state = aa_dfa_null_transition(dfa, state); + + return state; +} + +static aa_state_t match_to_local(struct aa_policydb *policy, + aa_state_t state, u32 request, + int type, int protocol, + struct sockaddr_un *addr, int addrlen, + struct aa_perms **p, + const char **info) +{ + state = aa_match_to_prot(policy, state, request, PF_UNIX, type, + protocol, NULL, info); + if (state) { + state = match_addr(policy->dfa, state, addr, addrlen); + if (state) { + /* todo: local label matching */ + state = aa_dfa_null_transition(policy->dfa, state); + if (!state) + *info = "failed local label match"; + } else { + *info = "failed local address match"; + } + } + + return state; +} + +static aa_state_t match_to_sk(struct aa_policydb *policy, + aa_state_t state, u32 request, + struct unix_sock *u, struct aa_perms **p, + const char **info) +{ + struct sockaddr_un *addr = NULL; + int addrlen = 0; + + if (u->addr) { + addr = u->addr->name; + addrlen = u->addr->len; + } + + return match_to_local(policy, state, request, u->sk.sk_type, + u->sk.sk_protocol, addr, addrlen, p, info); +} + +#define CMD_ADDR 1 +#define CMD_LISTEN 2 +#define CMD_OPT 4 + +static aa_state_t match_to_cmd(struct aa_policydb *policy, aa_state_t state, + u32 request, struct unix_sock *u, + char cmd, struct aa_perms **p, + const char **info) +{ + AA_BUG(!p); + + state = match_to_sk(policy, state, request, u, p, info); + if (state && !*p) { + state = aa_dfa_match_len(policy->dfa, state, &cmd, 1); + if (!state) + *info = "failed cmd selection match"; + } + + return state; +} + +static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t state, + u32 request, struct unix_sock *u, + struct sockaddr_un *peer_addr, int peer_addrlen, + struct aa_perms **p, const char **info) +{ + AA_BUG(!p); + + state = match_to_cmd(policy, state, request, u, CMD_ADDR, p, info); + if (state && !*p) { + state = match_addr(policy->dfa, state, peer_addr, peer_addrlen); + if (!state) + *info = "failed peer address match"; + } + + return state; +} + +static aa_state_t match_label(struct aa_profile *profile, + struct aa_ruleset *rule, aa_state_t state, + u32 request, struct aa_profile *peer, + struct aa_perms *p, + struct apparmor_audit_data *ad) +{ + AA_BUG(!profile); + AA_BUG(!peer); + + ad->peer = &peer->label; + + if (state && !p) { + state = aa_dfa_match(rule->policy->dfa, state, + peer->base.hname); + if (!state) + ad->info = "failed peer label match"; + + } + + return aa_do_perms(profile, rule->policy, state, request, p, ad); +} + + +/* unix sock creation comes before we know if the socket will be an fs + * socket + * v6 - semantics are handled by mapping in profile load + * v7 - semantics require sock create for tasks creating an fs socket. + * v8 - same as v7 + */ +static int profile_create_perm(struct aa_profile *profile, int family, + int type, int protocol, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_NET(rules); + if (state) { + state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE, + PF_UNIX, type, protocol, NULL, + &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_CREATE, + NULL, ad); + } + + return aa_profile_af_perm(profile, ad, AA_MAY_CREATE, family, type, + protocol); +} + +static int profile_sk_perm(struct aa_profile *profile, + struct apparmor_audit_data *ad, + u32 request, struct sock *sk) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), + list); + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(is_unix_fs(sk)); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_NET(rules); + if (state) { + state = match_to_sk(rules->policy, state, request, unix_sk(sk), + &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_NET(rules); + if (state) { + /* bind for abstract socket */ + state = match_to_local(rules->policy, state, AA_MAY_BIND, + sk->sk_type, sk->sk_protocol, + unix_addr(ad->net.addr), + ad->net.addrlen, + &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_BIND, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_BIND, sk); +} + +static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, + int backlog, struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(is_unix_fs(sk)); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_NET(rules); + if (state) { + __be16 b = cpu_to_be16(backlog); + + state = match_to_cmd(rules->policy, state, AA_MAY_LISTEN, + unix_sk(sk), CMD_LISTEN, &p, &ad->info); + if (state && !p) { + state = aa_dfa_match_len(rules->policy->dfa, state, + (char *) &b, 2); + if (!state) + ad->info = "failed listen backlog match"; + } + return aa_do_perms(profile, rules->policy, state, AA_MAY_LISTEN, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_LISTEN, sk); +} + +static int profile_accept_perm(struct aa_profile *profile, + struct sock *sk, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(is_unix_fs(sk)); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_NET(rules); + if (state) { + state = match_to_sk(rules->policy, state, AA_MAY_ACCEPT, + unix_sk(sk), &p, &ad->info); + + return aa_do_perms(profile, rules->policy, state, AA_MAY_ACCEPT, + p, ad); + } + + return aa_profile_af_sk_perm(profile, ad, AA_MAY_ACCEPT, sk); +} + +static int profile_opt_perm(struct aa_profile *profile, u32 request, + struct sock *sk, int optname, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(!sk); + AA_BUG(is_unix_fs(sk)); + AA_BUG(!ad); + AA_BUG(profile_unconfined(profile)); + + state = RULE_MEDIATES_NET(rules); + if (state) { + __be16 b = cpu_to_be16(optname); + + state = match_to_cmd(rules->policy, state, request, unix_sk(sk), + CMD_OPT, &p, &ad->info); + if (state && !p) { + state = aa_dfa_match_len(rules->policy->dfa, state, + (char *) &b, 2); + if (!state) + ad->info = "failed sockopt match"; + } + return aa_do_perms(profile, rules->policy, state, request, p, + ad); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* null peer_label is allowed, in which case the peer_sk label is used */ +static int profile_peer_perm(struct aa_profile *profile, u32 request, + struct sock *sk, struct sock *peer_sk, + struct aa_label *peer_label, + struct apparmor_audit_data *ad) +{ + struct aa_ruleset *rules = list_first_entry(&profile->rules, + typeof(*rules), list); + struct aa_perms *p = NULL; + aa_state_t state; + + AA_BUG(!profile); + AA_BUG(profile_unconfined(profile)); + AA_BUG(!sk); + AA_BUG(!peer_sk); + AA_BUG(!ad); + AA_BUG(is_unix_fs(peer_sk)); /* currently always calls unix_fs_perm */ + + state = RULE_MEDIATES_NET(rules); + if (state) { + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + struct aa_profile *peerp; + struct sockaddr_un *addr = NULL; + int len = 0; + + if (unix_sk(peer_sk)->addr) { + addr = unix_sk(peer_sk)->addr->name; + len = unix_sk(peer_sk)->addr->len; + } + state = match_to_peer(rules->policy, state, request, + unix_sk(sk), + addr, len, &p, &ad->info); + if (!peer_label) + peer_label = peer_ctx->label; + + return fn_for_each_in_ns(peer_label, peerp, + match_label(profile, rules, state, request, + peerp, p, ad)); + } + + return aa_profile_af_sk_perm(profile, ad, request, sk); +} + +/* -------------------------------- */ + +int aa_unix_create_perm(struct aa_label *label, int family, int type, + int protocol) +{ + if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_NET(ad, OP_CREATE, current_cred(), NULL, family, + type, protocol); + + return fn_for_each_confined(label, profile, + profile_create_perm(profile, family, type, + protocol, &ad)); + } + + return 0; +} + +int aa_unix_label_sk_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk) +{ + if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + return fn_for_each_confined(label, profile, + profile_sk_perm(profile, &ad, request, sk)); + } + return 0; +} + +static int unix_label_sock_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, + u32 request, struct socket *sock) +{ + if (unconfined(label)) + return 0; + if (is_unix_fs(sock->sk)) + return unix_fs_perm(op, request, subj_cred, label, + unix_sk(sock->sk)); + + return aa_unix_label_sk_perm(subj_cred, label, op, request, sock->sk); +} + +/* revalidation, get/set attr, shutdown */ +int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) +{ + struct aa_label *label; + int error; + + label = begin_current_label_crit_section(); + error = unix_label_sock_perm(current_cred(), label, op, request, sock); + end_current_label_crit_section(label); + + return error; +} + +static int valid_addr(struct sockaddr *addr, int addr_len) +{ + struct sockaddr_un *sunaddr = (struct sockaddr_un *)addr; + + /* addr_len == offsetof(struct sockaddr_un, sun_path) is autobind */ + if (addr_len < offsetof(struct sockaddr_un, sun_path) || + addr_len > sizeof(*sunaddr)) + return -EINVAL; + return 0; +} + +int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, + int addrlen) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + error = valid_addr(addr, addrlen); + if (error) + return error; + + label = begin_current_label_crit_section(); + /* fs bind is handled by mknod */ + if (!(unconfined(label) || is_unix_addr_fs(addr, addrlen))) { + DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk); + + ad.net.addr = unix_addr(addr); + ad.net.addrlen = addrlen; + + error = fn_for_each_confined(label, profile, + profile_bind_perm(profile, sock->sk, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + +/* + * unix connections are covered by the + * - unix_stream_connect (stream) and unix_may_send hooks (dgram) + * - fs connect is handled by open + * This is just here to document this is not needed for af_unix + * +int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen) +{ + return 0; +} +*/ + +int aa_unix_listen_perm(struct socket *sock, int backlog) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!(unconfined(label) || is_unix_fs(sock->sk))) { + DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_listen_perm(profile, sock->sk, + backlog, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + + +/* ability of sock to connect, not peer address binding */ +int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!(unconfined(label) || is_unix_fs(sock->sk))) { + DEFINE_AUDIT_SK(ad, OP_ACCEPT, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_accept_perm(profile, sock->sk, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + + +/* + * dgram handled by unix_may_sendmsg, right to send on stream done at connect + * could do per msg unix_stream here, but connect + socket transfer is + * sufficient. This is just here to document this is not needed for af_unix + * + * sendmsg, recvmsg +int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size) +{ + return 0; +} +*/ + +int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, + int level, int optname) +{ + struct aa_profile *profile; + struct aa_label *label; + int error = 0; + + label = begin_current_label_crit_section(); + if (!(unconfined(label) || is_unix_fs(sock->sk))) { + DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk); + + error = fn_for_each_confined(label, profile, + profile_opt_perm(profile, request, + sock->sk, optname, &ad)); + } + end_current_label_crit_section(label); + + return error; +} + +/** + * + * Requires: lock held on both @sk and @peer_sk + * called by unix_stream_connect, unix_may_send + */ +int aa_unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, struct sock *peer_sk, + struct aa_label *peer_label) +{ + struct unix_sock *peeru = unix_sk(peer_sk); + struct unix_sock *u = unix_sk(sk); + + AA_BUG(!label); + AA_BUG(!sk); + AA_BUG(!peer_sk); + + if (is_unix_fs(aa_unix_sk(peeru))) { + return unix_fs_perm(op, request, subj_cred, label, peeru); + } else if (is_unix_fs(aa_unix_sk(u))) { + return unix_fs_perm(op, request, subj_cred, label, u); + } else if (!unconfined(label)) { + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + ad.net.peer_sk = peer_sk; + + return fn_for_each_confined(label, profile, + profile_peer_perm(profile, request, sk, + peer_sk, peer_label, &ad)); + } + + return 0; +} + +static void unix_state_double_lock(struct sock *sk1, struct sock *sk2) +{ + if (unlikely(sk1 == sk2) || !sk2) { + unix_state_lock(sk1); + return; + } + if (sk1 < sk2) { + unix_state_lock(sk1); + unix_state_lock(sk2); + } else { + unix_state_lock(sk2); + unix_state_lock(sk1); + } +} + +static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2) +{ + if (unlikely(sk1 == sk2) || !sk2) { + unix_state_unlock(sk1); + return; + } + unix_state_unlock(sk1); + unix_state_unlock(sk2); +} + +/* TODO: examine replacing double lock with cached addr */ + +int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file) +{ + struct socket *sock = (struct socket *) file->private_data; + struct sock *peer_sk = NULL; + u32 sk_req = request & ~NET_PEER_MASK; + bool is_sk_fs; + int error = 0; + + AA_BUG(!label); + AA_BUG(!sock); + AA_BUG(!sock->sk); + AA_BUG(sock->sk->sk_family != PF_UNIX); + + /* TODO: update sock label with new task label */ + unix_state_lock(sock->sk); + peer_sk = unix_peer(sock->sk); + if (peer_sk) + sock_hold(peer_sk); + + is_sk_fs = is_unix_fs(sock->sk); + if (is_sk_fs && peer_sk) + sk_req = request; + if (sk_req) + error = unix_label_sock_perm(subj_cred, label, op, sk_req, + sock); + unix_state_unlock(sock->sk); + if (!peer_sk) + return error; + + unix_state_double_lock(sock->sk, peer_sk); + if (!is_sk_fs && is_unix_fs(peer_sk)) { + last_error(error, + unix_fs_perm(op, request, subj_cred, label, + unix_sk(peer_sk))); + } else if (!is_sk_fs) { + struct aa_sk_ctx *pctx = aa_sock(peer_sk); + + last_error(error, + xcheck(aa_unix_peer_perm(subj_cred, label, op, + MAY_READ | MAY_WRITE, + sock->sk, peer_sk, NULL), + aa_unix_peer_perm(file->f_cred, pctx->label, op, + MAY_READ | MAY_WRITE, + peer_sk, sock->sk, label))); + } + unix_state_double_unlock(sock->sk, peer_sk); + + sock_put(peer_sk); + + return error; +} diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 3455d223879b..45afd585b52b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2387,6 +2387,11 @@ static struct aa_sfs_entry aa_sfs_entry_ns[] = { { } }; +static struct aa_sfs_entry aa_sfs_entry_dbus[] = { + AA_SFS_FILE_STRING("mask", "acquire send receive"), + { } +}; + static struct aa_sfs_entry aa_sfs_entry_query_label[] = { AA_SFS_FILE_STRING("perms", "allow deny audit quiet"), AA_SFS_FILE_BOOLEAN("data", 1), @@ -2409,6 +2414,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("domain", aa_sfs_entry_domain), AA_SFS_DIR("file", aa_sfs_entry_file), AA_SFS_DIR("network_v8", aa_sfs_entry_network), + AA_SFS_DIR("network", aa_sfs_entry_networkv9), AA_SFS_DIR("mount", aa_sfs_entry_mount), AA_SFS_DIR("namespaces", aa_sfs_entry_ns), AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), @@ -2416,6 +2422,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("caps", aa_sfs_entry_caps), AA_SFS_DIR("ptrace", aa_sfs_entry_ptrace), AA_SFS_DIR("signal", aa_sfs_entry_signal), + AA_SFS_DIR("dbus", aa_sfs_entry_dbus), AA_SFS_DIR("query", aa_sfs_entry_query), AA_SFS_DIR("io_uring", aa_sfs_entry_io_uring), { } diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 6ce6547301dc..d918b5dc6f59 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -14,6 +14,7 @@ #include #include +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" @@ -217,16 +218,17 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, return state; } -static int __aa_path_perm(const char *op, const struct cred *subj_cred, - struct aa_profile *profile, const char *name, - u32 request, struct path_cond *cond, int flags, - struct aa_perms *perms) +int __aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms) { struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), list); int e = 0; - if (profile_unconfined(profile)) + if (profile_unconfined(profile) || + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_NET(rules))) return 0; aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], name, cond, perms); @@ -549,12 +551,12 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred, return 0; /* TODO: improve to skip profiles cached in flabel */ - error = aa_sock_file_perm(subj_cred, label, op, request, sock); + error = aa_sock_file_perm(subj_cred, label, op, request, file); if (denied) { /* TODO: improve to skip profiles checked above */ /* check every profile in file label to is cached */ last_error(error, aa_sock_file_perm(subj_cred, flabel, op, - request, sock)); + request, file)); } if (!error) update_file_ctx(file_ctx(file), label, request); diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h new file mode 100644 index 000000000000..28390eec3204 --- /dev/null +++ b/security/apparmor/include/af_unix.h @@ -0,0 +1,57 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * AppArmor security module + * + * This file contains AppArmor af_unix fine grained mediation + * + * Copyright 2023 Canonical Ltd. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation, version 2 of the + * License. + */ +#ifndef __AA_AF_UNIX_H + +#include + +#include "label.h" + +#define unix_addr(A) ((struct sockaddr_un *)(A)) +#define unix_addr_len(L) ((L) - sizeof(sa_family_t)) +#define unix_peer(sk) (unix_sk(sk)->peer) +#define is_unix_addr_abstract_name(B) ((B)[0] == 0) +#define is_unix_addr_anon(A, L) ((A) && unix_addr_len(L) <= 0) +#define is_unix_addr_fs(A, L) (!is_unix_addr_anon(A, L) && \ + !is_unix_addr_abstract_name(unix_addr(A)->sun_path)) + +#define is_unix_anonymous(U) (!unix_sk(U)->addr) +#define is_unix_fs(U) (!is_unix_anonymous(U) && \ + unix_sk(U)->addr->name->sun_path[0]) +#define is_unix_connected(S) ((S)->state == SS_CONNECTED) + + +int aa_unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, struct sock *peer_sk, + struct aa_label *peer_label); +int aa_unix_label_sk_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk); +int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock); +int aa_unix_create_perm(struct aa_label *label, int family, int type, + int protocol); +int aa_unix_bind_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_unix_connect_perm(struct socket *sock, struct sockaddr *address, + int addrlen); +int aa_unix_listen_perm(struct socket *sock, int backlog); +int aa_unix_accept_perm(struct socket *sock, struct socket *newsock); +int aa_unix_msg_perm(const char *op, u32 request, struct socket *sock, + struct msghdr *msg, int size); +int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int level, + int optname); +int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct file *file); + +#endif /* __AA_AF_UNIX_H */ diff --git a/security/apparmor/include/apparmor.h b/security/apparmor/include/apparmor.h index dd12cba8139d..cc6e3df1bc62 100644 --- a/security/apparmor/include/apparmor.h +++ b/security/apparmor/include/apparmor.h @@ -28,6 +28,7 @@ #define AA_CLASS_SIGNAL 10 #define AA_CLASS_XMATCH 11 #define AA_CLASS_NET 14 +#define AA_CLASS_NETV9 15 #define AA_CLASS_LABEL 16 #define AA_CLASS_POSIX_MQUEUE 17 #define AA_CLASS_MODULE 19 diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index 06d9899098a6..eb371dffbce3 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -84,6 +84,10 @@ aa_state_t aa_str_perms(struct aa_policydb *file_rules, aa_state_t start, const char *name, struct path_cond *cond, struct aa_perms *perms); +int __aa_path_perm(const char *op, const struct cred *subj_cred, + struct aa_profile *profile, const char *name, + u32 request, struct path_cond *cond, int flags, + struct aa_perms *perms); int aa_path_perm(const char *op, const struct cred *subj_cred, struct aa_label *label, const struct path *path, int flags, u32 request, struct path_cond *cond); diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 9361ba000398..5089e937d550 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -56,7 +56,7 @@ static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) return sk->sk_security + apparmor_blob_sizes.lbs_sock; } -#define DEFINE_AUDIT_NET(NAME, OP, SK, F, T, P) \ +#define DEFINE_AUDIT_NET(NAME, OP, CRED, SK, F, T, P) \ struct lsm_network_audit NAME ## _net = { .sk = (SK), \ .family = (F)}; \ DEFINE_AUDIT_DATA(NAME, \ @@ -65,11 +65,12 @@ static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) AA_CLASS_NET, \ OP); \ NAME.common.u.net = &(NAME ## _net); \ + NAME.subj_cred = (CRED); \ NAME.net.type = (T); \ NAME.net.protocol = (P) -#define DEFINE_AUDIT_SK(NAME, OP, SK) \ - DEFINE_AUDIT_NET(NAME, OP, SK, (SK)->sk_family, (SK)->sk_type, \ +#define DEFINE_AUDIT_SK(NAME, OP, CRED, SK) \ + DEFINE_AUDIT_NET(NAME, OP, CRED, SK, (SK)->sk_family, (SK)->sk_type, \ (SK)->sk_protocol) @@ -81,10 +82,14 @@ struct aa_secmark { }; extern struct aa_sfs_entry aa_sfs_entry_network[]; +extern struct aa_sfs_entry aa_sfs_entry_networkv9[]; -/* passing in state returned by XXX_mediates(class) */ +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, + aa_state_t state, u32 request, struct aa_perms *p, + struct apparmor_audit_data *ad); +/* passing in state returned by XXX_mediates_AF() */ aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, - u32 request, u16 family, int type, int protocol, + u32 request, u16 af, int type, int protocol, struct aa_perms **p, const char **info); void audit_net_cb(struct audit_buffer *ab, void *va); int aa_profile_af_perm(struct aa_profile *profile, @@ -105,7 +110,7 @@ int aa_sk_perm(const char *op, u32 request, struct sock *sk); int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, - struct socket *sock); + struct file *file); int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, u32 secid, const struct sock *sk); diff --git a/security/apparmor/include/path.h b/security/apparmor/include/path.h index 343189903dba..8bb915d48dc7 100644 --- a/security/apparmor/include/path.h +++ b/security/apparmor/include/path.h @@ -13,6 +13,7 @@ enum path_flags { PATH_IS_DIR = 0x1, /* path is a directory */ + PATH_SOCK_COND = 0x2, PATH_CONNECT_PATH = 0x4, /* connect disconnected paths to / */ PATH_CHROOT_REL = 0x8, /* do path lookup relative to chroot */ PATH_CHROOT_NSCONNECT = 0x10, /* connect paths that are at ns root */ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 73cb84ef58f2..5128c5414f04 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -304,14 +304,9 @@ static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules, rules->policy->start[0], &class, 1); } -static inline aa_state_t RULE_MEDIATES_AF(struct aa_ruleset *rules, u16 AF) +static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) { - aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NET); - __be16 be_af = cpu_to_be16(AF); - - if (!state) - return DFA_NOMATCH; - return aa_dfa_match_len(rules->policy->dfa, state, (char *) &be_af, 2); + return RULE_MEDIATES(rules, AA_CLASS_NET); } static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index f7b2d4bb1d97..0b4f7e2e4135 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -26,6 +26,7 @@ #include #include +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/apparmorfs.h" #include "include/audit.h" @@ -1088,6 +1089,94 @@ static void apparmor_sk_clone_security(const struct sock *sk, new->peer = aa_get_label(ctx->peer); } +static int unix_connect_perm(const struct cred *cred, struct aa_label *label, + struct sock *sk, struct sock *peer_sk) +{ + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + int error; + + error = aa_unix_peer_perm(cred, label, OP_CONNECT, + (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), + sk, peer_sk, NULL); + if (!is_unix_fs(peer_sk)) { + last_error(error, + aa_unix_peer_perm(cred, + peer_ctx->label, OP_CONNECT, + (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), + peer_sk, sk, label)); + } + + return error; +} + +static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, + struct aa_sk_ctx *peer_ctx) +{ + /* Cross reference the peer labels for SO_PEERSEC */ + aa_put_label(peer_ctx->peer); + aa_put_label(sk_ctx->peer); + + peer_ctx->peer = aa_get_label(sk_ctx->label); + sk_ctx->peer = aa_get_label(peer_ctx->label); +} + +/** + * apparmor_unix_stream_connect - check perms before making unix domain conn + * + * peer is locked when this hook is called + */ +static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, + struct sock *newsk) +{ + struct aa_sk_ctx *sk_ctx = aa_sock(sk); + struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); + struct aa_sk_ctx *new_ctx = aa_sock(newsk); + struct aa_label *label; + int error; + + label = __begin_current_label_crit_section(); + error = unix_connect_perm(current_cred(), label, sk, peer_sk); + __end_current_label_crit_section(label); + + if (error) + return error; + + /* newsk doesn't go through post_create */ + AA_BUG(new_ctx->label); + new_ctx->label = aa_get_label(peer_ctx->label); + + /* Cross reference the peer labels for SO_PEERSEC */ + unix_connect_peers(sk_ctx, new_ctx); + + return 0; +} + +/** + * apparmor_unix_may_send - check perms before conn or sending unix dgrams + * + * sock and peer are locked when this hook is called + * + * called by: dgram_connect peer setup but path not copied to newsk + */ +static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) +{ + struct aa_sk_ctx *peer_ctx = aa_sock(peer->sk); + struct aa_label *label; + int error; + + label = __begin_current_label_crit_section(); + error = xcheck(aa_unix_peer_perm(current_cred(), + label, OP_SENDMSG, AA_MAY_SEND, + sock->sk, peer->sk, NULL), + aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL, + peer_ctx->label, OP_SENDMSG, + AA_MAY_RECEIVE, + peer->sk, sock->sk, label)); + __end_current_label_crit_section(label); + + return error; +} + static int apparmor_socket_create(int family, int type, int protocol, int kern) { struct aa_label *label; @@ -1100,8 +1189,13 @@ static int apparmor_socket_create(int family, int type, int protocol, int kern) label = begin_current_label_crit_section(); if (!unconfined(label)) { - error = aa_af_perm(current_cred(), label, OP_CREATE, - AA_MAY_CREATE, family, type, protocol); + if (family == PF_UNIX) + error = aa_unix_create_perm(label, family, type, + protocol); + else + error = aa_af_perm(current_cred(), label, OP_CREATE, + AA_MAY_CREATE, family, type, + protocol); } end_current_label_crit_section(label); @@ -1143,6 +1237,34 @@ static int apparmor_socket_post_create(struct socket *sock, int family, return 0; } +static int apparmor_socket_socketpair(struct socket *socka, + struct socket *sockb) +{ + struct aa_sk_ctx *a_ctx = aa_sock(socka->sk); + struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk); + struct aa_label *label; + int error = 0; + + aa_put_label(a_ctx->label); + aa_put_label(b_ctx->label); + + label = begin_current_label_crit_section(); + a_ctx->label = aa_get_label(label); + b_ctx->label = aa_get_label(label); + + if (socka->sk->sk_family == PF_UNIX) { + /* unix socket pairs by-pass unix_stream_connect */ + if (!error) + unix_connect_peers(a_ctx, b_ctx); + } + end_current_label_crit_section(label); + + return error; +} + +/** + * apparmor_socket_bind - check perms before bind addr to socket + */ static int apparmor_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) { @@ -1151,6 +1273,8 @@ static int apparmor_socket_bind(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_bind_perm(sock, address, addrlen); return aa_sk_perm(OP_BIND, AA_MAY_BIND, sock->sk); } @@ -1162,6 +1286,9 @@ static int apparmor_socket_connect(struct socket *sock, AA_BUG(!address); AA_BUG(in_interrupt()); + /* PF_UNIX goes through unix_stream_connect && unix_may_send */ + if (sock->sk->sk_family == PF_UNIX) + return 0; return aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); } @@ -1171,6 +1298,8 @@ static int apparmor_socket_listen(struct socket *sock, int backlog) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_listen_perm(sock, backlog); return aa_sk_perm(OP_LISTEN, AA_MAY_LISTEN, sock->sk); } @@ -1185,6 +1314,8 @@ static int apparmor_socket_accept(struct socket *sock, struct socket *newsock) AA_BUG(!newsock); AA_BUG(in_interrupt()); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_accept_perm(sock, newsock); return aa_sk_perm(OP_ACCEPT, AA_MAY_ACCEPT, sock->sk); } @@ -1196,6 +1327,9 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!msg); AA_BUG(in_interrupt()); + /* PF_UNIX goes through unix_may_send */ + if (sock->sk->sk_family == PF_UNIX) + return 0; return aa_sk_perm(op, request, sock->sk); } @@ -1218,6 +1352,8 @@ static int aa_sock_perm(const char *op, u32 request, struct socket *sock) AA_BUG(!sock->sk); AA_BUG(in_interrupt()); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_sock_perm(op, request, sock); return aa_sk_perm(op, request, sock->sk); } @@ -1239,6 +1375,8 @@ static int aa_sock_opt_perm(const char *op, u32 request, struct socket *sock, AA_BUG(!sock->sk); AA_BUG(in_interrupt()); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_opt_perm(op, request, sock, level, optname); return aa_sk_perm(op, request, sock->sk); } @@ -1292,14 +1430,18 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) #endif -static struct aa_label *sk_peer_label(struct sock *sk) +static struct aa_label *sk_peer_get_label(struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_label *label = ERR_PTR(-ENOPROTOOPT); if (ctx->peer) - return ctx->peer; + return aa_get_label(ctx->peer); - return ERR_PTR(-ENOPROTOOPT); + if (sk->sk_family != PF_UNIX) + return ERR_PTR(-ENOPROTOOPT); + + return label; } /** @@ -1322,7 +1464,7 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, struct aa_label *peer; label = begin_current_label_crit_section(); - peer = sk_peer_label(sock->sk); + peer = sk_peer_get_label(sock->sk); if (IS_ERR(peer)) { error = PTR_ERR(peer); goto done; @@ -1333,7 +1475,7 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, /* don't include terminating \0 in slen, it breaks some apps */ if (slen < 0) { error = -ENOMEM; - goto done; + goto done_put; } if (slen > len) { error = -ERANGE; @@ -1345,6 +1487,9 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, done_len: if (copy_to_sockptr(optlen, &slen, sizeof(slen))) error = -EFAULT; + +done_put: + aa_put_label(peer); done: end_current_label_crit_section(label); kfree(name); @@ -1456,8 +1601,12 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = { LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security), LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), + LSM_HOOK_INIT(unix_stream_connect, apparmor_unix_stream_connect), + LSM_HOOK_INIT(unix_may_send, apparmor_unix_may_send), + LSM_HOOK_INIT(socket_create, apparmor_socket_create), LSM_HOOK_INIT(socket_post_create, apparmor_socket_post_create), + LSM_HOOK_INIT(socket_socketpair, apparmor_socket_socketpair), LSM_HOOK_INIT(socket_bind, apparmor_socket_bind), LSM_HOOK_INIT(socket_connect, apparmor_socket_connect), LSM_HOOK_INIT(socket_listen, apparmor_socket_listen), diff --git a/security/apparmor/net.c b/security/apparmor/net.c index c76e0f5dcc93..a256a4664826 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -8,6 +8,7 @@ * Copyright 2009-2017 Canonical Ltd. */ +#include "include/af_unix.h" #include "include/apparmor.h" #include "include/audit.h" #include "include/cred.h" @@ -24,6 +25,12 @@ struct aa_sfs_entry aa_sfs_entry_network[] = { { } }; +struct aa_sfs_entry aa_sfs_entry_networkv9[] = { + AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK), + AA_SFS_FILE_BOOLEAN("af_unix", 1), + { } +}; + static const char * const net_mask_names[] = { "unknown", "send", @@ -66,6 +73,37 @@ static const char * const net_mask_names[] = { "unknown", }; +static void audit_unix_addr(struct audit_buffer *ab, const char *str, + struct sockaddr_un *addr, int addrlen) +{ + int len = unix_addr_len(addrlen); + + if (!addr || len <= 0) { + audit_log_format(ab, " %s=none", str); + } else if (addr->sun_path[0]) { + audit_log_format(ab, " %s=", str); + audit_log_untrustedstring(ab, addr->sun_path); + } else { + audit_log_format(ab, " %s=\"@", str); + if (audit_string_contains_control(&addr->sun_path[1], len - 1)) + audit_log_n_hex(ab, &addr->sun_path[1], len - 1); + else + audit_log_format(ab, "%.*s", len - 1, + &addr->sun_path[1]); + audit_log_format(ab, "\""); + } +} + +static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str, + const struct sock *sk) +{ + const struct unix_sock *u = unix_sk(sk); + + if (u && u->addr) + audit_unix_addr(ab, str, u->addr->name, u->addr->len); + else + audit_unix_addr(ab, str, NULL, 0); +} /* audit callback for net specific fields */ void audit_net_cb(struct audit_buffer *ab, void *va) @@ -73,12 +111,12 @@ void audit_net_cb(struct audit_buffer *ab, void *va) struct common_audit_data *sa = va; struct apparmor_audit_data *ad = aad(sa); - if (address_family_names[sa->u.net->family]) + if (address_family_names[ad->common.u.net->family]) audit_log_format(ab, " family=\"%s\"", - address_family_names[sa->u.net->family]); + address_family_names[ad->common.u.net->family]); else audit_log_format(ab, " family=\"unknown(%d)\"", - sa->u.net->family); + ad->common.u.net->family); if (sock_type_names[ad->net.type]) audit_log_format(ab, " sock_type=\"%s\"", sock_type_names[ad->net.type]); @@ -98,6 +136,23 @@ void audit_net_cb(struct audit_buffer *ab, void *va) net_mask_names, NET_PERMS_MASK); } } + if (ad->common.u.net->family == PF_UNIX) { + if ((ad->request & ~NET_PEER_MASK) && ad->net.addr) + audit_unix_addr(ab, "addr", + unix_addr(ad->net.addr), + ad->net.addrlen); + else + audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk); + if (ad->request & NET_PEER_MASK) { + if (ad->net.addr) + audit_unix_addr(ab, "peer_addr", + unix_addr(ad->net.addr), + ad->net.addrlen); + else + audit_unix_sk_addr(ab, "peer_addr", + ad->net.peer_sk); + } + } if (ad->peer) { audit_log_format(ab, " peer="); aa_label_xaudit(ab, labels_ns(ad->subj_label), ad->peer, @@ -106,9 +161,9 @@ void audit_net_cb(struct audit_buffer *ab, void *va) } /* standard permission lookup pattern - supports early bailout */ -static int do_perms(struct aa_profile *profile, struct aa_policydb *policy, - unsigned int state, u32 request, - struct aa_perms *p, struct apparmor_audit_data *ad) +int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy, + aa_state_t state, u32 request, + struct aa_perms *p, struct apparmor_audit_data *ad) { struct aa_perms perms; @@ -140,31 +195,53 @@ static struct aa_perms *early_match(struct aa_policydb *policy, return p; } -/* passing in state returned by PROFILE_MEDIATES_AF */ +static aa_state_t aa_dfa_match_be16(struct aa_dfa *dfa, aa_state_t state, + u16 data) +{ + __be16 buffer = cpu_to_be16(data); + + return aa_dfa_match_len(dfa, state, (char *) &buffer, 2); +} + +/** + * aa_match_to_prot - match the af, type, protocol triplet + * @policy: policy being matched + * @state: state to start in + * @request: permissions being requested, ignored if @p == NULL + * @af: socket address family + * @type: socket type + * @protocol: socket protocol + * @p: output - pointer to permission associated with match + * @info: output - pointer to string describing failure + * + * RETURNS: state match stopped in. + * + * If @(p) is assigned a value the returned state will be the + * corresponding state. Will not set @p on failure or if match completes + * only if an early match occurs + */ aa_state_t aa_match_to_prot(struct aa_policydb *policy, aa_state_t state, - u32 request, u16 family, int type, int protocol, + u32 request, u16 af, int type, int protocol, struct aa_perms **p, const char **info) { - __be16 buffer; - - buffer = cpu_to_be16(family); - state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, 2); + state = aa_dfa_match_be16(policy->dfa, state, (u16)af); if (!state) { *info = "failed af match"; - return DFA_NOMATCH; + return state; } - buffer = cpu_to_be16((u16)type); - state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, 2); - if (!state) + state = aa_dfa_match_be16(policy->dfa, state, (u16)type); + if (state) { + if (p) + *p = early_match(policy, state, request); + if (!p || !*p) { + state = aa_dfa_match_be16(policy->dfa, state, (u16)protocol); + if (!state) + *info = "failed protocol match"; + } + } else { *info = "failed type match"; - *p = early_match(policy, state, request); - if (!*p) { - buffer = cpu_to_be16((u16)protocol); - state = aa_dfa_match_len(policy->dfa, state, (char *) &buffer, - 2); - if (!state) - *info = "failed protocol match"; } + return state; } @@ -182,20 +259,21 @@ int aa_profile_af_perm(struct aa_profile *profile, AA_BUG(type < 0 || type >= SOCK_MAX); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES(rules, AA_CLASS_NET); + if (profile_unconfined(profile)) + return 0; + state = RULE_MEDIATES_NET(rules); if (!state) return 0; - state = aa_match_to_prot(rules->policy, state, request, family, type, protocol, &p, &ad->info); - return do_perms(profile, rules->policy, state, request, p, ad); + return aa_do_perms(profile, rules->policy, state, request, p, ad); } int aa_af_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, u16 family, int type, int protocol) { struct aa_profile *profile; - DEFINE_AUDIT_NET(ad, op, NULL, family, type, protocol); + DEFINE_AUDIT_NET(ad, op, subj_cred, NULL, family, type, protocol); return fn_for_each_confined(label, profile, aa_profile_af_perm(profile, &ad, request, family, @@ -215,7 +293,7 @@ static int aa_label_sk_perm(const struct cred *subj_cred, if (ctx->label != kernel_t && !unconfined(label)) { struct aa_profile *profile; - DEFINE_AUDIT_SK(ad, op, sk); + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); ad.subj_cred = subj_cred; error = fn_for_each_confined(label, profile, @@ -243,12 +321,16 @@ int aa_sk_perm(const char *op, u32 request, struct sock *sk) int aa_sock_file_perm(const struct cred *subj_cred, struct aa_label *label, - const char *op, u32 request, struct socket *sock) + const char *op, u32 request, struct file *file) { + struct socket *sock = (struct socket *) file->private_data; + AA_BUG(!label); AA_BUG(!sock); AA_BUG(!sock->sk); + if (sock->sk->sk_family == PF_UNIX) + return aa_unix_file_perm(subj_cred, label, op, request, file); return aa_label_sk_perm(subj_cred, label, op, request, sock->sk); } @@ -313,7 +395,7 @@ int apparmor_secmark_check(struct aa_label *label, char *op, u32 request, u32 secid, const struct sock *sk) { struct aa_profile *profile; - DEFINE_AUDIT_SK(ad, op, sk); + DEFINE_AUDIT_SK(ad, op, NULL, sk); return fn_for_each_confined(label, profile, aa_secmark_perm(profile, request, secid, From dcd7a559411e8e1cd627ad20ac70faee77329380 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 12 Oct 2024 04:43:34 -0700 Subject: [PATCH 18/60] apparmor: gate make fine grained unix mediation behind v9 abi Fine grained unix mediation in Ubuntu used ABI v7, and policy using this has propogated onto systems where fine grained unix mediation was not supported. The userspace policy compiler supports downgrading policy so the policy could be shared without changes. Unfortunately this had the side effect that policy was not updated for the none Ubuntu systems and enabling fine grained unix mediation on those systems means that a new kernel can break a system with existing policy that worked with the previous kernel. With fine grained af_unix mediation this regression can easily break the system causing boot to fail, as it affect unix socket files, non-file based unix sockets, and dbus communication. To aoid this regression move fine grained af_unix mediation behind a new abi. This means that the system's userspace and policy must be updated to support the new policy before it takes affect and dropping a new kernel on existing system will not result in a regression. The abi bump is done in such a way as existing policy can be activated on the system by changing the policy abi declaration and existing unix policy rules will apply. Policy then only needs to be incrementally updated, can even be backported to existing Ubuntu policy. Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 14 +++++++------- security/apparmor/apparmorfs.c | 2 +- security/apparmor/file.c | 2 +- security/apparmor/include/policy.h | 18 +++++++++++++++++- 4 files changed, 26 insertions(+), 10 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index ce7dc9d98fb1..ed4b34b88e38 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -197,7 +197,7 @@ static int profile_create_perm(struct aa_profile *profile, int family, AA_BUG(!profile); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { state = aa_match_to_prot(rules->policy, state, AA_MAY_CREATE, PF_UNIX, type, protocol, NULL, @@ -226,7 +226,7 @@ static int profile_sk_perm(struct aa_profile *profile, AA_BUG(is_unix_fs(sk)); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { state = match_to_sk(rules->policy, state, request, unix_sk(sk), &p, &ad->info); @@ -251,7 +251,7 @@ static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { /* bind for abstract socket */ state = match_to_local(rules->policy, state, AA_MAY_BIND, @@ -281,7 +281,7 @@ static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { __be16 b = cpu_to_be16(backlog); @@ -315,7 +315,7 @@ static int profile_accept_perm(struct aa_profile *profile, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { state = match_to_sk(rules->policy, state, AA_MAY_ACCEPT, unix_sk(sk), &p, &ad->info); @@ -342,7 +342,7 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { __be16 b = cpu_to_be16(optname); @@ -379,7 +379,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request, AA_BUG(!ad); AA_BUG(is_unix_fs(peer_sk)); /* currently always calls unix_fs_perm */ - state = RULE_MEDIATES_NET(rules); + state = RULE_MEDIATES_v9NET(rules); if (state) { struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); struct aa_profile *peerp; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 45afd585b52b..c5c756dda5cf 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2414,7 +2414,7 @@ static struct aa_sfs_entry aa_sfs_entry_features[] = { AA_SFS_DIR("domain", aa_sfs_entry_domain), AA_SFS_DIR("file", aa_sfs_entry_file), AA_SFS_DIR("network_v8", aa_sfs_entry_network), - AA_SFS_DIR("network", aa_sfs_entry_networkv9), + AA_SFS_DIR("network_v9", aa_sfs_entry_networkv9), AA_SFS_DIR("mount", aa_sfs_entry_mount), AA_SFS_DIR("namespaces", aa_sfs_entry_ns), AA_SFS_FILE_U64("capability", VFS_CAP_FLAGS_MASK), diff --git a/security/apparmor/file.c b/security/apparmor/file.c index d918b5dc6f59..85f89814af1e 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -228,7 +228,7 @@ int __aa_path_perm(const char *op, const struct cred *subj_cred, int e = 0; if (profile_unconfined(profile) || - ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_NET(rules))) + ((flags & PATH_SOCK_COND) && !RULE_MEDIATES_v9NET(rules))) return 0; aa_str_perms(rules->file, rules->file->start[AA_CLASS_FILE], name, cond, perms); diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index 5128c5414f04..a6ddf3b7478e 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -304,11 +304,27 @@ static inline aa_state_t RULE_MEDIATES(struct aa_ruleset *rules, rules->policy->start[0], &class, 1); } +static inline aa_state_t RULE_MEDIATES_v9NET(struct aa_ruleset *rules) +{ + return RULE_MEDIATES(rules, AA_CLASS_NETV9); +} + static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) { - return RULE_MEDIATES(rules, AA_CLASS_NET); + /* can not use RULE_MEDIATE_v9AF here, because AF match fail + * can not be distiguished from class match fail, and we only + * fallback to checking older class on class match failure + */ + aa_state_t state = RULE_MEDIATES(rules, AA_CLASS_NETV9); + + /* fallback and check v7/8 if v9 is NOT mediated */ + if (!state) + state = RULE_MEDIATES(rules, AA_CLASS_NET); + + return state; } + static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, unsigned char class) { From e6b087676954e36a7b1ed51249362bb499f8c1c2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 17 Jan 2025 05:02:33 -0800 Subject: [PATCH 19/60] apparmor: fix dbus permission queries to v9 ABI dbus permission queries need to be synced with fine grained unix mediation to avoid potential policy regressions. To ensure that dbus queries don't result in a case where fine grained unix mediation is not being applied but dbus mediation is check the loaded policy support ABI and abort the query if policy doesn't support the v9 ABI. Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index c5c756dda5cf..0b0e24cd4868 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -632,6 +632,14 @@ static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, } else if (rules->policy->dfa) { if (!RULE_MEDIATES(rules, *match_str)) return; /* no change to current perms */ + /* old user space does not correctly detect dbus mediation + * support so we may get dbus policy and requests when + * the abi doesn't support it. This can cause mediation + * regressions, so explicitly test for this situation. + */ + if (*match_str == AA_CLASS_DBUS && + !RULE_MEDIATES_v9NET(rules)) + return; /* no change to current perms */ state = aa_dfa_match_len(rules->policy->dfa, rules->policy->start[0], match_str, match_len); From 509f8cb2fff927eeb5eb0ebdd410ec6f40430173 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 20 Jan 2025 06:12:01 -0700 Subject: [PATCH 20/60] apparmor: Fix checking address of an array in accum_label_info() clang warns: security/apparmor/label.c:206:15: error: address of array 'new->vec' will always evaluate to 'true' [-Werror,-Wpointer-bool-conversion] 206 | AA_BUG(!new->vec); | ~~~~~~^~~ The address of this array can never be NULL because it is not at the beginning of a structure. Convert the assertion to check that the new pointer is not NULL. Fixes: de4754c801f4 ("apparmor: carry mediation check on label") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202501191802.bDp2voTJ-lkp@intel.com/ Signed-off-by: Nathan Chancellor Signed-off-by: John Johansen --- security/apparmor/label.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index afded9996f61..79be2d3d604b 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -203,7 +203,7 @@ static void accum_label_info(struct aa_label *new) long u = FLAG_UNCONFINED; int i; - AA_BUG(!new->vec); + AA_BUG(!new); /* size == 1 is a profile and flags must be set as part of creation */ if (new->size == 1) From aa904fa1182b1a4470bb082f6cddacc1dc4e8032 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Tue, 21 Jan 2025 10:44:44 +0800 Subject: [PATCH 21/60] apparmor: Modify mismatched function name No functional modification involved. security/apparmor/file.c:184: warning: expecting prototype for aa_lookup_fperms(). Prototype was for aa_lookup_condperms() instead. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=13605 Signed-off-by: Jiapeng Chong Signed-off-by: John Johansen --- security/apparmor/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 85f89814af1e..f113eedbc208 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -169,7 +169,7 @@ static int path_name(const char *op, const struct cred *subj_cred, struct aa_perms default_perms = {}; /** - * aa_lookup_fperms - convert dfa compressed perms to internal perms + * aa_lookup_condperms - convert dfa compressed perms to internal perms * @subj_uid: uid to use for subject owner test * @rules: the aa_policydb to lookup perms for (NOT NULL) * @state: state in dfa From 04fe43104e4ed103a8b55c21d1bc354fac409421 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Tue, 21 Jan 2025 10:44:43 +0800 Subject: [PATCH 22/60] apparmor: Modify mismatched function name No functional modification involved. security/apparmor/lib.c:93: warning: expecting prototype for aa_mask_to_str(). Prototype was for val_mask_to_str() instead. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=13606 Signed-off-by: Jiapeng Chong Signed-off-by: John Johansen --- security/apparmor/lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index dd5dcbe5daf7..325f26f39a63 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -82,7 +82,7 @@ int aa_parse_debug_params(const char *str) } /** - * aa_mask_to_str - convert a perm mask to its short string + * val_mask_to_str - convert a perm mask to its short string * @str: character buffer to store string in (at least 10 characters) * @str_size: size of the @str buffer * @chrs: NUL-terminated character buffer of permission characters From aabbe6f908d8264cd8aeeef8141665f71668ef36 Mon Sep 17 00:00:00 2001 From: Tanya Agarwal Date: Fri, 24 Jan 2025 00:51:00 +0530 Subject: [PATCH 23/60] apparmor: fix typos and spelling errors Fix typos and spelling errors in apparmor module comments that were identified using the codespell tool. No functional changes - documentation only. Signed-off-by: Tanya Agarwal Reviewed-by: Mimi Zohar Ryan Lee Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 6 +++--- security/apparmor/domain.c | 4 ++-- security/apparmor/label.c | 2 +- security/apparmor/lsm.c | 2 +- security/apparmor/policy.c | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 0b0e24cd4868..ecf22251c228 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -43,7 +43,7 @@ * The interface is split into two main components based on their function * a securityfs component: * used for static files that are always available, and which allows - * userspace to specificy the location of the security filesystem. + * userspace to specify the location of the security filesystem. * * fns and data are prefixed with * aa_sfs_ @@ -204,7 +204,7 @@ static struct file_system_type aafs_ops = { /** * __aafs_setup_d_inode - basic inode setup for apparmorfs * @dir: parent directory for the dentry - * @dentry: dentry we are seting the inode up for + * @dentry: dentry we are setting the inode up for * @mode: permissions the file should have * @data: data to store on inode.i_private, available in open() * @link: if symlink, symlink target string @@ -2253,7 +2253,7 @@ static void *p_next(struct seq_file *f, void *p, loff_t *pos) /** * p_stop - stop depth first traversal * @f: seq_file we are filling - * @p: the last profile writen + * @p: the last profile written * * Release all locking done by p_start/p_next on namespace tree */ diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b9c299097372..a7447d976a31 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -762,7 +762,7 @@ static int profile_onexec(const struct cred *subj_cred, /* change_profile on exec already granted */ /* * NOTE: Domain transitions from unconfined are allowed - * even when no_new_privs is set because this aways results + * even when no_new_privs is set because this always results * in a further reduction of permissions. */ return 0; @@ -933,7 +933,7 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) * * NOTE: Domain transitions from unconfined and to stacked * subsets are allowed even when no_new_privs is set because this - * aways results in a further reduction of permissions. + * always results in a further reduction of permissions. */ if ((bprm->unsafe & LSM_UNSAFE_NO_NEW_PRIVS) && !unconfined(label) && diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 79be2d3d604b..913678f199c3 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1461,7 +1461,7 @@ bool aa_update_label_name(struct aa_ns *ns, struct aa_label *label, gfp_t gfp) /* * cached label name is present and visible - * @label->hname only exists if label is namespace hierachical + * @label->hname only exists if label is namespace hierarchical */ static inline bool use_label_hname(struct aa_ns *ns, struct aa_label *label, int flags) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 0b4f7e2e4135..74e2f31ac2d8 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -2173,7 +2173,7 @@ static int __init alloc_buffers(void) * two should be enough, with more CPUs it is possible that more * buffers will be used simultaneously. The preallocated pool may grow. * This preallocation has also the side-effect that AppArmor will be - * disabled early at boot if aa_g_path_max is extremly high. + * disabled early at boot if aa_g_path_max is extremely high. */ if (num_online_cpus() > 1) num = 4 + RESERVE_COUNT; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 04222eddd890..1f532fe48a1c 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -488,7 +488,7 @@ static struct aa_policy *__lookup_parent(struct aa_ns *ns, } /** - * __create_missing_ancestors - create place holders for missing ancestores + * __create_missing_ancestors - create place holders for missing ancestors * @ns: namespace to lookup profile in (NOT NULL) * @hname: hierarchical profile name to find parent of (NOT NULL) * @gfp: type of allocation. @@ -1095,7 +1095,7 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, goto out; /* ensure that profiles are all for the same ns - * TODO: update locking to remove this constaint. All profiles in + * TODO: update locking to remove this constraint. All profiles in * the load set must succeed as a set or the load will * fail. Sort ent list and take ns locks in hierarchy order */ From 67e370aa7f968f6a4f3573ed61a77b36d1b26475 Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Mon, 27 Jan 2025 21:54:04 +0100 Subject: [PATCH 24/60] apparmor: use the condition in AA_BUG_FMT even with debug disabled MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This follows the established practice and fixes a build failure for me: security/apparmor/file.c: In function ‘__file_sock_perm’: security/apparmor/file.c:544:24: error: unused variable ‘sock’ [-Werror=unused-variable] 544 | struct socket *sock = (struct socket *) file->private_data; | ^~~~ Signed-off-by: Mateusz Guzik Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 256f4577c653..d947998262b2 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -60,7 +60,11 @@ do { \ #define AA_BUG_FMT(X, fmt, args...) \ WARN((X), "AppArmor WARN %s: (" #X "): " fmt, __func__, ##args) #else -#define AA_BUG_FMT(X, fmt, args...) no_printk(fmt, ##args) +#define AA_BUG_FMT(X, fmt, args...) \ + do { \ + BUILD_BUG_ON_INVALID(X); \ + no_printk(fmt, ##args); \ + } while (0) #endif int aa_parse_debug_params(const char *str); From 3e45553acb14692519db853e4b5be35b45e46ad0 Mon Sep 17 00:00:00 2001 From: Nathan Chancellor Date: Mon, 20 Jan 2025 06:21:14 -0700 Subject: [PATCH 25/60] apparmor: Remove unused variable 'sock' in __file_sock_perm() When CONFIG_SECURITY_APPARMOR_DEBUG_ASSERTS is disabled, there is a warning that sock is unused: security/apparmor/file.c: In function '__file_sock_perm': security/apparmor/file.c:544:24: warning: unused variable 'sock' [-Wunused-variable] 544 | struct socket *sock = (struct socket *) file->private_data; | ^~~~ sock was moved into aa_sock_file_perm(), where the same check is present, so remove sock and the assertion from __file_sock_perm() to fix the warning. Fixes: c05e705812d1 ("apparmor: add fine grained af_unix mediation") Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202501190757.myuLxLyL-lkp@intel.com/ Signed-off-by: Nathan Chancellor Signed-off-by: John Johansen --- security/apparmor/file.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index f113eedbc208..5c984792cbf0 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -541,11 +541,8 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred, struct aa_label *flabel, struct file *file, u32 request, u32 denied) { - struct socket *sock = (struct socket *) file->private_data; int error; - AA_BUG(!sock); - /* revalidation due to label out of date. No revocation at this time */ if (!denied && aa_label_is_subset(flabel, label)) return 0; From 2b270e2f43d7498ba00117c60d196435983d83d7 Mon Sep 17 00:00:00 2001 From: Zilin Guan Date: Fri, 18 Apr 2025 04:52:50 +0000 Subject: [PATCH 26/60] security/apparmor: use kfree_sensitive() in unpack_secmark() The unpack_secmark() function currently uses kfree() to release memory allocated for secmark structures and their labels. However, if a failure occurs after partially parsing secmark, sensitive data may remain in memory, posing a security risk. To mitigate this, replace kfree() with kfree_sensitive() for freeing secmark structures and their labels, aligning with the approach used in free_ruleset(). I am submitting this as an RFC to seek freedback on whether this change is appropriate and aligns with the subsystem's expectations. If confirmed to be helpful, I will send a formal patch. Signed-off-by: Zilin Guan Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 73139189df0f..459eb878c824 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -599,8 +599,8 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_ruleset *rules) fail: if (rules->secmark) { for (i = 0; i < size; i++) - kfree(rules->secmark[i].label); - kfree(rules->secmark); + kfree_sensitive(rules->secmark[i].label); + kfree_sensitive(rules->secmark); rules->secmark_count = 0; rules->secmark = NULL; } From e9ed1eb8f6217e53843d82ecf2d50f8d1a93e77c Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Mon, 28 Apr 2025 12:04:30 -0700 Subject: [PATCH 27/60] apparmor: use SHA-256 library API instead of crypto_shash API This user of SHA-256 does not support any other algorithm, so the crypto_shash abstraction provides no value. Just use the SHA-256 library API instead, which is much simpler and easier to use. Signed-off-by: Eric Biggers Signed-off-by: John Johansen --- security/apparmor/Kconfig | 3 +- security/apparmor/crypto.c | 85 ++++++-------------------------------- 2 files changed, 13 insertions(+), 75 deletions(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 64cc3044a42c..1e3bd44643da 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -59,8 +59,7 @@ config SECURITY_APPARMOR_INTROSPECT_POLICY config SECURITY_APPARMOR_HASH bool "Enable introspection of sha256 hashes for loaded profiles" depends on SECURITY_APPARMOR_INTROSPECT_POLICY - select CRYPTO - select CRYPTO_SHA256 + select CRYPTO_LIB_SHA256 default y help This option selects whether introspection of loaded policy diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c index aad486b2fca6..40e17e153f1e 100644 --- a/security/apparmor/crypto.c +++ b/security/apparmor/crypto.c @@ -11,113 +11,52 @@ * it should be. */ -#include +#include #include "include/apparmor.h" #include "include/crypto.h" -static unsigned int apparmor_hash_size; - -static struct crypto_shash *apparmor_tfm; - unsigned int aa_hash_size(void) { - return apparmor_hash_size; + return SHA256_DIGEST_SIZE; } char *aa_calc_hash(void *data, size_t len) { - SHASH_DESC_ON_STACK(desc, apparmor_tfm); char *hash; - int error; - if (!apparmor_tfm) - return NULL; - - hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); if (!hash) return ERR_PTR(-ENOMEM); - desc->tfm = apparmor_tfm; - - error = crypto_shash_init(desc); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) data, len); - if (error) - goto fail; - error = crypto_shash_final(desc, hash); - if (error) - goto fail; - + sha256(data, len, hash); return hash; - -fail: - kfree(hash); - - return ERR_PTR(error); } int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len) { - SHASH_DESC_ON_STACK(desc, apparmor_tfm); - int error; + struct sha256_state state; __le32 le32_version = cpu_to_le32(version); if (!aa_g_hash_policy) return 0; - if (!apparmor_tfm) - return 0; - - profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL); + profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); if (!profile->hash) return -ENOMEM; - desc->tfm = apparmor_tfm; - - error = crypto_shash_init(desc); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) &le32_version, 4); - if (error) - goto fail; - error = crypto_shash_update(desc, (u8 *) start, len); - if (error) - goto fail; - error = crypto_shash_final(desc, profile->hash); - if (error) - goto fail; - + sha256_init(&state); + sha256_update(&state, (u8 *)&le32_version, 4); + sha256_update(&state, (u8 *)start, len); + sha256_final(&state, profile->hash); return 0; - -fail: - kfree(profile->hash); - profile->hash = NULL; - - return error; } static int __init init_profile_hash(void) { - struct crypto_shash *tfm; - - if (!apparmor_initialized) - return 0; - - tfm = crypto_alloc_shash("sha256", 0, 0); - if (IS_ERR(tfm)) { - int error = PTR_ERR(tfm); - AA_ERROR("failed to setup profile sha256 hashing: %d\n", error); - return error; - } - apparmor_tfm = tfm; - apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm); - - aa_info_message("AppArmor sha256 policy hashing enabled"); - + if (apparmor_initialized) + aa_info_message("AppArmor sha256 policy hashing enabled"); return 0; } - late_initcall(init_profile_hash); From 44fbeeb3087ee2ddce39d261d0a26688c2e22742 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Sat, 17 May 2025 01:49:20 -0700 Subject: [PATCH 28/60] apparmor: Fix incorrect profile->signal range check The check on profile->signal is always false, the value can never be less than 1 *and* greater than MAXMAPPED_SIG. Fix this by replacing the logical operator && with ||. Fixes: 84c455decf27 ("apparmor: add support for profiles to define the kill signal") Signed-off-by: Colin Ian King Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 459eb878c824..588dd1d5d364 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -919,7 +919,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) /* optional */ (void) aa_unpack_u32(e, &profile->signal, "kill"); - if (profile->signal < 1 && profile->signal > MAXMAPPED_SIG) { + if (profile->signal < 1 || profile->signal > MAXMAPPED_SIG) { info = "profile kill.signal invalid value"; goto fail; } From a949b46e7d82ef0fed09aa0590442156d44d39b1 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 2 May 2025 21:49:19 -0700 Subject: [PATCH 29/60] apparmor: fix some kernel-doc issues in header files Fix kernel-doc warnings in apparmor header files as reported by scripts/kernel-doc: cred.h:128: warning: expecting prototype for end_label_crit_section(). Prototype was for end_current_label_crit_section() instead file.h:108: warning: expecting prototype for aa_map_file_perms(). Prototype was for aa_map_file_to_perms() instead lib.h:159: warning: Function parameter or struct member 'hname' not described in 'basename' lib.h:159: warning: Excess function parameter 'name' description in 'basename' match.h:21: warning: This comment starts with '/**', but isn't a kernel-doc comment. Refer Documentation/doc-guide/kernel-doc.rst * The format used for transition tables is based on the GNU flex table perms.h:109: warning: Function parameter or struct member 'accum' not described in 'aa_perms_accum_raw' perms.h:109: warning: Function parameter or struct member 'addend' not described in 'aa_perms_accum_raw' perms.h:136: warning: Function parameter or struct member 'accum' not described in 'aa_perms_accum' perms.h:136: warning: Function parameter or struct member 'addend' not described in 'aa_perms_accum' Signed-off-by: Randy Dunlap Reviewed-by: Ryan Lee Cc: John Johansen Cc: John Johansen Cc: apparmor@lists.ubuntu.com Cc: linux-security-module@vger.kernel.org Cc: Paul Moore Cc: James Morris Cc: "Serge E. Hallyn" Signed-off-by: John Johansen --- security/apparmor/include/cred.h | 2 +- security/apparmor/include/file.h | 2 +- security/apparmor/include/lib.h | 2 +- security/apparmor/include/match.h | 2 +- security/apparmor/include/perms.h | 8 ++++---- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 7265d2f81dd5..674af3175905 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -117,7 +117,7 @@ static inline struct aa_label *aa_get_current_label(void) #define __end_current_label_crit_section(X) end_current_label_crit_section(X) /** - * end_label_crit_section - put a reference found with begin_current_label.. + * end_current_label_crit_section - put a reference found with begin_current_label.. * @label: label reference to put * * Should only be used with a reference obtained with diff --git a/security/apparmor/include/file.h b/security/apparmor/include/file.h index eb371dffbce3..ef60f99bc5ae 100644 --- a/security/apparmor/include/file.h +++ b/security/apparmor/include/file.h @@ -104,7 +104,7 @@ void aa_inherit_files(const struct cred *cred, struct files_struct *files); /** - * aa_map_file_perms - map file flags to AppArmor permissions + * aa_map_file_to_perms - map file flags to AppArmor permissions * @file: open file to map flags to AppArmor permissions * * Returns: apparmor permission set for the file diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index d947998262b2..e60bfa410e55 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -170,7 +170,7 @@ struct aa_policy { /** * basename - find the last component of an hname - * @name: hname to find the base profile name component of (NOT NULL) + * @hname: hname to find the base profile name component of (NOT NULL) * * Returns: the tail (base profile name) name component of an hname */ diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 536ce3abd598..01a703fef8e1 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -17,7 +17,7 @@ #define DFA_START 1 -/** +/* * The format used for transition tables is based on the GNU flex table * file format (--tables-file option; see Table File Format in the flex * info pages and the flex sources for documentation). The magic number diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h index bbaa7d39a39a..37a3781b99a0 100644 --- a/security/apparmor/include/perms.h +++ b/security/apparmor/include/perms.h @@ -101,8 +101,8 @@ extern struct aa_perms allperms; /** * aa_perms_accum_raw - accumulate perms with out masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum + * @accum: perms struct to accumulate into + * @addend: perms struct to add to @accum */ static inline void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend) @@ -128,8 +128,8 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum, /** * aa_perms_accum - accumulate perms, masking off overlapping perms - * @accum - perms struct to accumulate into - * @addend - perms struct to add to @accum + * @accum: perms struct to accumulate into + * @addend: perms struct to add to @accum */ static inline void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend) From 6c055e62560b958354625604293652753d82bcae Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 1 May 2025 12:54:38 -0700 Subject: [PATCH 30/60] apparmor: ensure WB_HISTORY_SIZE value is a power of 2 WB_HISTORY_SIZE was defined to be a value not a power of 2, despite a comment in the declaration of struct match_workbuf stating it is and a modular arithmetic usage in the inc_wb_pos macro assuming that it is. Bump WB_HISTORY_SIZE's value up to 32 and add a BUILD_BUG_ON_NOT_POWER_OF_2 line to ensure that any future changes to the value of WB_HISTORY_SIZE respect this requirement. Fixes: 136db994852a ("apparmor: increase left match history buffer size") Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/include/match.h | 3 ++- security/apparmor/match.c | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 01a703fef8e1..21e049b40824 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -137,7 +137,8 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, void aa_dfa_free_kref(struct kref *kref); -#define WB_HISTORY_SIZE 24 +/* This needs to be a power of 2 */ +#define WB_HISTORY_SIZE 32 struct match_workbuf { unsigned int count; unsigned int pos; diff --git a/security/apparmor/match.c b/security/apparmor/match.c index f2d9c57f8794..1ceabde550f2 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -681,6 +681,7 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, #define inc_wb_pos(wb) \ do { \ + BUILD_BUG_ON_NOT_POWER_OF_2(WB_HISTORY_SIZE); \ wb->pos = (wb->pos + 1) & (WB_HISTORY_SIZE - 1); \ wb->len = (wb->len + 1) & (WB_HISTORY_SIZE - 1); \ } while (0) From a88db916b8c77552f49f7d9f8744095ea01a268f Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 1 May 2025 12:54:39 -0700 Subject: [PATCH 31/60] apparmor: fix loop detection used in conflicting attachment resolution Conflicting attachment resolution is based on the number of states traversed to reach an accepting state in the attachment DFA, accounting for DFA loops traversed during the matching process. However, the loop counting logic had multiple bugs: - The inc_wb_pos macro increments both position and length, but length is supposed to saturate upon hitting buffer capacity, instead of wrapping around. - If no revisited state is found when traversing the history, is_loop would still return true, as if there was a loop found the length of the history buffer, instead of returning false and signalling that no loop was found. As a result, the adjustment step of aa_dfa_leftmatch would sometimes produce negative counts with loop- free DFAs that traversed enough states. - The iteration in the is_loop for loop is supposed to stop before i = wb->len, so the conditional should be < instead of <=. This patch fixes the above bugs as well as the following nits: - The count and size fields in struct match_workbuf were not used, so they can be removed. - The history buffer in match_workbuf semantically stores aa_state_t and not unsigned ints, even if aa_state_t is currently unsigned int. - The local variables in is_loop are counters, and thus should be unsigned ints instead of aa_state_t's. Fixes: 21f606610502 ("apparmor: improve overlapping domain attachment resolution") Signed-off-by: Ryan Lee Co-developed-by: John Johansen Signed-off-by: John Johansen --- security/apparmor/include/match.h | 5 +---- security/apparmor/match.c | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/security/apparmor/include/match.h b/security/apparmor/include/match.h index 21e049b40824..1fbe82f5021b 100644 --- a/security/apparmor/include/match.h +++ b/security/apparmor/include/match.h @@ -140,15 +140,12 @@ void aa_dfa_free_kref(struct kref *kref); /* This needs to be a power of 2 */ #define WB_HISTORY_SIZE 32 struct match_workbuf { - unsigned int count; unsigned int pos; unsigned int len; - unsigned int size; /* power of 2, same as history size */ - unsigned int history[WB_HISTORY_SIZE]; + aa_state_t history[WB_HISTORY_SIZE]; }; #define DEFINE_MATCH_WB(N) \ struct match_workbuf N = { \ - .count = 0, \ .pos = 0, \ .len = 0, \ } diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 1ceabde550f2..c5a91600842a 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -679,35 +679,35 @@ aa_state_t aa_dfa_matchn_until(struct aa_dfa *dfa, aa_state_t start, return state; } -#define inc_wb_pos(wb) \ -do { \ +#define inc_wb_pos(wb) \ +do { \ BUILD_BUG_ON_NOT_POWER_OF_2(WB_HISTORY_SIZE); \ wb->pos = (wb->pos + 1) & (WB_HISTORY_SIZE - 1); \ - wb->len = (wb->len + 1) & (WB_HISTORY_SIZE - 1); \ + wb->len = (wb->len + 1) > WB_HISTORY_SIZE ? WB_HISTORY_SIZE : \ + wb->len + 1; \ } while (0) /* For DFAs that don't support extended tagging of states */ +/* adjust is only set if is_loop returns true */ static bool is_loop(struct match_workbuf *wb, aa_state_t state, unsigned int *adjust) { - aa_state_t pos = wb->pos; - aa_state_t i; + int pos = wb->pos; + int i; if (wb->history[pos] < state) return false; - for (i = 0; i <= wb->len; i++) { + for (i = 0; i < wb->len; i++) { if (wb->history[pos] == state) { *adjust = i; return true; } - if (pos == 0) - pos = WB_HISTORY_SIZE; - pos--; + /* -1 wraps to WB_HISTORY_SIZE - 1 */ + pos = (pos - 1) & (WB_HISTORY_SIZE - 1); } - *adjust = i; - return true; + return false; } static aa_state_t leftmatch_fb(struct aa_dfa *dfa, aa_state_t start, From 95ff11895846eec76a19351a109fbabbdd86b417 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Tue, 8 Apr 2025 18:02:00 -0700 Subject: [PATCH 32/60] apparmor: make all generated string array headers const char *const address_family_names and sock_type_names were created as const char *a[], which declares them as (non-const) pointers to const chars. Since the pointers themselves would not be changed, they should be generated as const char *const a[]. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/Makefile b/security/apparmor/Makefile index be51607f52b6..12fb419714c0 100644 --- a/security/apparmor/Makefile +++ b/security/apparmor/Makefile @@ -28,7 +28,7 @@ clean-files := capability_names.h rlim_names.h net_names.h # to # #define AA_SFS_AF_MASK "local inet" quiet_cmd_make-af = GEN $@ -cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ +cmd_make-af = echo "static const char *const address_family_names[] = {" > $@ ;\ sed $< >>$@ -r -n -e "/AF_MAX/d" -e "/AF_LOCAL/d" -e "/AF_ROUTE/d" -e \ 's/^\#define[ \t]+AF_([A-Z0-9_]+)[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ echo "};" >> $@ ;\ @@ -43,7 +43,7 @@ cmd_make-af = echo "static const char *address_family_names[] = {" > $@ ;\ # to # [1] = "stream", quiet_cmd_make-sock = GEN $@ -cmd_make-sock = echo "static const char *sock_type_names[] = {" >> $@ ;\ +cmd_make-sock = echo "static const char *const sock_type_names[] = {" >> $@ ;\ sed $^ >>$@ -r -n \ -e 's/^\tSOCK_([A-Z0-9_]+)[\t]+=[ \t]+([0-9]+)(.*)/[\2] = "\L\1",/p';\ echo "};" >> $@ From 89a3561e69e5187fcce302eef429acd38aec1277 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 1 May 2025 17:55:43 -0700 Subject: [PATCH 33/60] apparmor: force audit on unconfined exec if info is set by find_attach find_attach may set info if something unusual happens during that process (currently only used to signal conflicting attachments, but this could be expanded in the future). This is information that should be propagated to userspace via an audit message. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index a7447d976a31..4263bb1ee4a8 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -670,6 +670,22 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, if (profile_unconfined(profile)) { new = find_attach(bprm, profile->ns, &profile->ns->base.profiles, name, &info); + /* info set -> something unusual that we should report + * Currently this is only conflicting attachments, but other + * infos added in the future should also be logged by default + * and only excluded on a case-by-case basis + */ + if (info) { + /* Because perms is never used again after this audit + * we don't need to care about clobbering it + */ + perms.audit |= MAY_EXEC; + perms.allow |= MAY_EXEC; + /* Don't cause error if auditing fails */ + (void) aa_audit_file(subj_cred, profile, &perms, + OP_EXEC, MAY_EXEC, name, target, new, cond->uid, + info, error); + } if (new) { AA_DEBUG(DEBUG_DOMAIN, "unconfined attached to new label"); return new; From e76d733b1b1ff0bec6a305341fda3fe937fbf51f Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 1 May 2025 17:55:44 -0700 Subject: [PATCH 34/60] apparmor: move the "conflicting profile attachments" infostr to a const declaration Instead of having a literal, making this a constant will allow for (hacky) detection of conflicting profile attachments from inspection of the info pointer. This is used in the next patch to augment the information provided through domain.c:x_to_label for ix/ux fallback. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 4263bb1ee4a8..ca8cd7ea088b 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -28,6 +28,8 @@ #include "include/policy.h" #include "include/policy_ns.h" +static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments"; + /** * may_change_ptraced_domain - check if can change profile on ptraced task * @to_cred: cred of task changing domain @@ -485,7 +487,7 @@ restart: if (!candidate || conflict) { if (conflict) - *info = "conflicting profile attachments"; + *info = CONFLICTING_ATTACH_STR; rcu_read_unlock(); return NULL; } From b824b5f82bbc8ace0982391a1718b04a1f93346e Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 1 May 2025 17:55:45 -0700 Subject: [PATCH 35/60] apparmor: include conflicting attachment info for confined ix/ux fallback Instead of silently overwriting the conflicting profile attachment string, include that information in the ix/ux fallback string that gets set as info instead. Also add a warning print if some other info is set that would be overwritten by the ix/ux fallback string or by the profile not found error. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index ca8cd7ea088b..b5e1defbd4ac 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -29,6 +29,10 @@ #include "include/policy_ns.h" static const char * const CONFLICTING_ATTACH_STR = "conflicting profile attachments"; +static const char * const CONFLICTING_ATTACH_STR_IX = + "conflicting profile attachments - ix fallback"; +static const char * const CONFLICTING_ATTACH_STR_UX = + "conflicting profile attachments - ux fallback"; /** * may_change_ptraced_domain - check if can change profile on ptraced task @@ -577,6 +581,8 @@ static struct aa_label *x_to_label(struct aa_profile *profile, struct aa_label *stack = NULL; struct aa_ns *ns = profile->ns; u32 xtype = xindex & AA_X_TYPE_MASK; + /* Used for info checks during fallback handling */ + const char *old_info = NULL; switch (xtype) { case AA_X_NONE: @@ -613,12 +619,32 @@ static struct aa_label *x_to_label(struct aa_profile *profile, /* (p|c|n)ix - don't change profile but do * use the newest version */ - *info = "ix fallback"; + if (*info == CONFLICTING_ATTACH_STR) { + *info = CONFLICTING_ATTACH_STR_IX; + } else { + old_info = *info; + *info = "ix fallback"; + } /* no profile && no error */ new = aa_get_newest_label(&profile->label); } else if (xindex & AA_X_UNCONFINED) { new = aa_get_newest_label(ns_unconfined(profile->ns)); - *info = "ux fallback"; + if (*info == CONFLICTING_ATTACH_STR) { + *info = CONFLICTING_ATTACH_STR_UX; + } else { + old_info = *info; + *info = "ux fallback"; + } + } + /* We set old_info on the code paths above where overwriting + * could have happened, so now check if info was set by + * find_attach as well (i.e. whether we actually overwrote) + * and warn accordingly. + */ + if (old_info && old_info != CONFLICTING_ATTACH_STR) { + pr_warn_ratelimited( + "AppArmor: find_attach (from profile %s) audit info \"%s\" dropped", + profile->base.hname, old_info); } } @@ -706,6 +732,11 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, /* hack ix fallback - improve how this is detected */ goto audit; } else if (!new) { + if (info) { + pr_warn_ratelimited( + "AppArmor: %s (from profile %s) audit info \"%s\" dropped on missing transition", + __func__, profile->base.hname, info); + } info = "profile transition not found"; /* remove MAY_EXEC to audit as failure or complaint */ perms.allow &= ~MAY_EXEC; From 16916b17b4f80f99aad2ad29ad112313539ad219 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 1 May 2025 17:55:46 -0700 Subject: [PATCH 36/60] apparmor: force auditing of conflicting attachment execs from confined Conflicting attachment paths are an error state that result in the binary in question executing under an unexpected ix/ux fallback. As such, it should be audited to record the occurrence of conflicting attachments. Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index b5e1defbd4ac..f9370a63a83c 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -729,6 +729,15 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, new = x_to_label(profile, bprm, name, perms.xindex, &target, &info); if (new && new->proxy == profile->label.proxy && info) { + /* Force audit on conflicting attachment fallback + * Because perms is never used again after this audit + * we don't need to care about clobbering it + */ + if (info == CONFLICTING_ATTACH_STR_IX + || info == CONFLICTING_ATTACH_STR_UX) { + perms.audit |= MAY_EXEC; + perms.allow |= MAY_EXEC; + } /* hack ix fallback - improve how this is detected */ goto audit; } else if (!new) { From 4c0dc425fd613c5de0ca445f29d63150b52efc35 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 17 Feb 2025 01:50:36 -0800 Subject: [PATCH 37/60] apparmor: make debug_values_table static The debug_values_table is only referenced from lib.c so it should be static. Signed-off-by: John Johansen --- security/apparmor/lib.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 325f26f39a63..7cdf430762a8 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -30,7 +30,7 @@ struct val_table_ent { int value; }; -struct val_table_ent debug_values_table[] = { +static struct val_table_ent debug_values_table[] = { { "N", DEBUG_NONE }, { "none", DEBUG_NONE }, { "n", DEBUG_NONE }, From b1f87be7280ff48794f0fe55c9ca6df9d87d62c5 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 16 Feb 2025 03:40:52 -0800 Subject: [PATCH 38/60] apparmor: Document that label must be last member in struct aa_profile The label struct is variable length. While its use in struct aa_profile is fixed length at 2 entries the variable length member needs to be the last member in the structure. The code already does this but the comment has it in the wrong location. Also add a comment to ensure it stays at the end of the structure. While we are at it, update the documentation for other profile members as well. Signed-off-by: John Johansen --- security/apparmor/include/policy.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index a6ddf3b7478e..a4c0f76fd03d 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -198,7 +198,6 @@ struct aa_attachment { /* struct aa_profile - basic confinement data * @base - base components of the profile (name, refcount, lists, lock ...) - * @label - label this profile is an extension of * @parent: parent of profile * @ns: namespace the profile is in * @rename: optional profile name that this profile renamed @@ -206,13 +205,19 @@ struct aa_attachment { * @audit: the auditing mode of the profile * @mode: the enforcement mode of the profile * @path_flags: flags controlling path generation behavior + * @signal: the signal that should be used when kill is used * @disconnected: what to prepend if attach_disconnected is specified * @attach: attachment rules for the profile * @rules: rules to be enforced * + * learning_cache: the accesses learned in complain mode + * raw_data: rawdata of the loaded profile policy + * hash: cryptographic hash of the profile * @dents: dentries for the profiles file entries in apparmorfs * @dirname: name of the profile dir in apparmorfs + * @dents: set of dentries associated with the profile * @data: hashtable for free-form policy aa_data + * @label - label this profile is an extension of * * The AppArmor profile contains the basic confinement data. Each profile * has a name, and exists in a namespace. The @name and @exec_match are @@ -247,6 +252,8 @@ struct aa_profile { char *dirname; struct dentry *dents[AAFS_PROF_SIZEOF]; struct rhashtable *data; + + /* special - variable length must be last entry in profile */ struct aa_label label; }; From aff426f35966e6e77ecfe065984344a7d834eaa9 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 23 May 2025 21:04:51 -0700 Subject: [PATCH 39/60] apparmor: mitigate parser generating large xtables Some versions of the parser are generating an xtable transition per state in the state machine, even when the state machine isn't using the transition table. The parser bug is triggered by commit 2e12c5f06017 ("apparmor: add additional flags to extended permission.") In addition to fixing this in userspace, mitigate this in the kernel as part of the policy verification checks by detecting this situation and adjusting to what is actually used, or if not used at all freeing it, so we are not wasting unneeded memory on policy. Fixes: 2e12c5f06017 ("apparmor: add additional flags to extended permission.") Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 1 + security/apparmor/lib.c | 23 +++++++++++++++++++++++ security/apparmor/policy_unpack.c | 27 +++++++++++++++++++++------ 3 files changed, 45 insertions(+), 6 deletions(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index e60bfa410e55..200cf36c5e0a 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -125,6 +125,7 @@ struct aa_str_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); struct counted_str { struct kref count; diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7cdf430762a8..f51e79cc36d4 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -116,6 +116,29 @@ int aa_print_debug_params(char *buffer) aa_g_debug); } +bool aa_resize_str_table(struct aa_str_table *t, int newsize, gfp_t gfp) +{ + char **n; + int i; + + if (t->size == newsize) + return true; + n = kcalloc(newsize, sizeof(*n), gfp); + if (!n) + return false; + for (i = 0; i < min(t->size, newsize); i++) + n[i] = t->table[i]; + for (; i < t->size; i++) + kfree_sensitive(t->table[i]); + if (newsize > t->size) + memset(&n[t->size], 0, (newsize-t->size)*sizeof(*n)); + kfree_sensitive(t->table); + t->table = n; + t->size = newsize; + + return true; +} + /** * aa_free_str_table - free entries str table * @t: the string table to free (MAYBE NULL) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 588dd1d5d364..58c106b63727 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -802,8 +802,12 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, if (!pdb->dfa && pdb->trans.table) aa_free_str_table(&pdb->trans); - /* TODO: move compat mapping here, requires dfa merging first */ - /* TODO: move verify here, it has to be done after compat mappings */ + /* TODO: + * - move compat mapping here, requires dfa merging first + * - move verify here, it has to be done after compat mappings + * - move free of unneeded trans table here, has to be done + * after perm mapping. + */ out: *policy = pdb; return 0; @@ -1242,21 +1246,32 @@ static bool verify_perm(struct aa_perms *perm) static bool verify_perms(struct aa_policydb *pdb) { int i; + int xidx, xmax = -1; for (i = 0; i < pdb->size; i++) { if (!verify_perm(&pdb->perms[i])) return false; /* verify indexes into str table */ - if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE && - (pdb->perms[i].xindex & AA_X_INDEX_MASK) >= pdb->trans.size) - return false; + if ((pdb->perms[i].xindex & AA_X_TYPE_MASK) == AA_X_TABLE) { + xidx = pdb->perms[i].xindex & AA_X_INDEX_MASK; + if (xidx >= pdb->trans.size) + return false; + if (xmax < xidx) + xmax = xidx; + } if (pdb->perms[i].tag && pdb->perms[i].tag >= pdb->trans.size) return false; if (pdb->perms[i].label && pdb->perms[i].label >= pdb->trans.size) return false; } - + /* deal with incorrectly constructed string tables */ + if (xmax == -1) { + aa_free_str_table(&pdb->trans); + } else if (pdb->trans.size > xmax + 1) { + if (!aa_resize_str_table(&pdb->trans, xmax + 1, GFP_KERNEL)) + return false; + } return true; } From 37a3741d27b64012ab6a5d9c92b514b977349dbb Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 30 Jun 2025 00:06:22 -0700 Subject: [PATCH 40/60] Revert "apparmor: use SHA-256 library API instead of crypto_shash API" This reverts commit e9ed1eb8f6217e53843d82ecf2d50f8d1a93e77c. Eric has requested that this patch be taken through the libcrypto-next tree, instead. Signed-off-by: John Johansen --- security/apparmor/Kconfig | 3 +- security/apparmor/crypto.c | 85 ++++++++++++++++++++++++++++++++------ 2 files changed, 75 insertions(+), 13 deletions(-) diff --git a/security/apparmor/Kconfig b/security/apparmor/Kconfig index 1e3bd44643da..64cc3044a42c 100644 --- a/security/apparmor/Kconfig +++ b/security/apparmor/Kconfig @@ -59,7 +59,8 @@ config SECURITY_APPARMOR_INTROSPECT_POLICY config SECURITY_APPARMOR_HASH bool "Enable introspection of sha256 hashes for loaded profiles" depends on SECURITY_APPARMOR_INTROSPECT_POLICY - select CRYPTO_LIB_SHA256 + select CRYPTO + select CRYPTO_SHA256 default y help This option selects whether introspection of loaded policy diff --git a/security/apparmor/crypto.c b/security/apparmor/crypto.c index 40e17e153f1e..aad486b2fca6 100644 --- a/security/apparmor/crypto.c +++ b/security/apparmor/crypto.c @@ -11,52 +11,113 @@ * it should be. */ -#include +#include #include "include/apparmor.h" #include "include/crypto.h" +static unsigned int apparmor_hash_size; + +static struct crypto_shash *apparmor_tfm; + unsigned int aa_hash_size(void) { - return SHA256_DIGEST_SIZE; + return apparmor_hash_size; } char *aa_calc_hash(void *data, size_t len) { + SHASH_DESC_ON_STACK(desc, apparmor_tfm); char *hash; + int error; - hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); + if (!apparmor_tfm) + return NULL; + + hash = kzalloc(apparmor_hash_size, GFP_KERNEL); if (!hash) return ERR_PTR(-ENOMEM); - sha256(data, len, hash); + desc->tfm = apparmor_tfm; + + error = crypto_shash_init(desc); + if (error) + goto fail; + error = crypto_shash_update(desc, (u8 *) data, len); + if (error) + goto fail; + error = crypto_shash_final(desc, hash); + if (error) + goto fail; + return hash; + +fail: + kfree(hash); + + return ERR_PTR(error); } int aa_calc_profile_hash(struct aa_profile *profile, u32 version, void *start, size_t len) { - struct sha256_state state; + SHASH_DESC_ON_STACK(desc, apparmor_tfm); + int error; __le32 le32_version = cpu_to_le32(version); if (!aa_g_hash_policy) return 0; - profile->hash = kzalloc(SHA256_DIGEST_SIZE, GFP_KERNEL); + if (!apparmor_tfm) + return 0; + + profile->hash = kzalloc(apparmor_hash_size, GFP_KERNEL); if (!profile->hash) return -ENOMEM; - sha256_init(&state); - sha256_update(&state, (u8 *)&le32_version, 4); - sha256_update(&state, (u8 *)start, len); - sha256_final(&state, profile->hash); + desc->tfm = apparmor_tfm; + + error = crypto_shash_init(desc); + if (error) + goto fail; + error = crypto_shash_update(desc, (u8 *) &le32_version, 4); + if (error) + goto fail; + error = crypto_shash_update(desc, (u8 *) start, len); + if (error) + goto fail; + error = crypto_shash_final(desc, profile->hash); + if (error) + goto fail; + return 0; + +fail: + kfree(profile->hash); + profile->hash = NULL; + + return error; } static int __init init_profile_hash(void) { - if (apparmor_initialized) - aa_info_message("AppArmor sha256 policy hashing enabled"); + struct crypto_shash *tfm; + + if (!apparmor_initialized) + return 0; + + tfm = crypto_alloc_shash("sha256", 0, 0); + if (IS_ERR(tfm)) { + int error = PTR_ERR(tfm); + AA_ERROR("failed to setup profile sha256 hashing: %d\n", error); + return error; + } + apparmor_tfm = tfm; + apparmor_hash_size = crypto_shash_digestsize(apparmor_tfm); + + aa_info_message("AppArmor sha256 policy hashing enabled"); + return 0; } + late_initcall(init_profile_hash); From 87cc7b00114f6f751d25f6a5f05128dc27ef64db Mon Sep 17 00:00:00 2001 From: Mateusz Guzik Date: Tue, 18 Mar 2025 23:06:41 +0100 Subject: [PATCH 41/60] apparmor: make __begin_current_label_crit_section() indicate whether put is needed Same as aa_get_newest_cred_label_condref(). This avoids a bunch of work overall and allows the compiler to note when no clean up is necessary, allowing for tail calls. This in particular happens in apparmor_file_permission(), which manages to tail call aa_file_perm() 105 bytes in (vs a regular call 112 bytes in followed by branches to figure out if clean up is needed). Signed-off-by: Mateusz Guzik Signed-off-by: John Johansen --- security/apparmor/include/cred.h | 21 ++++++--- security/apparmor/lsm.c | 75 ++++++++++++++++++++------------ security/apparmor/policy.c | 12 ++--- 3 files changed, 67 insertions(+), 41 deletions(-) diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index 674af3175905..de6ec4969598 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -114,7 +114,12 @@ static inline struct aa_label *aa_get_current_label(void) return aa_get_label(l); } -#define __end_current_label_crit_section(X) end_current_label_crit_section(X) +static inline void __end_current_label_crit_section(struct aa_label *label, + bool needput) +{ + if (unlikely(needput)) + aa_put_label(label); +} /** * end_current_label_crit_section - put a reference found with begin_current_label.. @@ -142,13 +147,16 @@ static inline void end_current_label_crit_section(struct aa_label *label) * critical section between __begin_current_label_crit_section() .. * __end_current_label_crit_section() */ -static inline struct aa_label *__begin_current_label_crit_section(void) +static inline struct aa_label *__begin_current_label_crit_section(bool *needput) { struct aa_label *label = aa_current_raw_label(); - if (label_is_stale(label)) - label = aa_get_newest_label(label); + if (label_is_stale(label)) { + *needput = true; + return aa_get_newest_label(label); + } + *needput = false; return label; } @@ -184,10 +192,11 @@ static inline struct aa_ns *aa_get_current_ns(void) { struct aa_label *label; struct aa_ns *ns; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); ns = aa_get_ns(labels_ns(label)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return ns; } diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 74e2f31ac2d8..990211381319 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -127,14 +127,15 @@ static int apparmor_ptrace_access_check(struct task_struct *child, struct aa_label *tracer, *tracee; const struct cred *cred; int error; + bool needput; cred = get_task_cred(child); tracee = cred_label(cred); /* ref count on cred */ - tracer = __begin_current_label_crit_section(); + tracer = __begin_current_label_crit_section(&needput); error = aa_may_ptrace(current_cred(), tracer, cred, tracee, (mode & PTRACE_MODE_READ) ? AA_PTRACE_READ : AA_PTRACE_TRACE); - __end_current_label_crit_section(tracer); + __end_current_label_crit_section(tracer, needput); put_cred(cred); return error; @@ -145,14 +146,15 @@ static int apparmor_ptrace_traceme(struct task_struct *parent) struct aa_label *tracer, *tracee; const struct cred *cred; int error; + bool needput; - tracee = __begin_current_label_crit_section(); + tracee = __begin_current_label_crit_section(&needput); cred = get_task_cred(parent); tracer = cred_label(cred); /* ref count on cred */ error = aa_may_ptrace(cred, tracer, current_cred(), tracee, AA_PTRACE_TRACE); put_cred(cred); - __end_current_label_crit_section(tracee); + __end_current_label_crit_section(tracee, needput); return error; } @@ -221,12 +223,13 @@ static int common_perm(const char *op, const struct path *path, u32 mask, { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_path_perm(op, current_cred(), label, path, 0, mask, cond); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -524,14 +527,15 @@ static int common_file_perm(const char *op, struct file *file, u32 mask, { struct aa_label *label; int error = 0; + bool needput; /* don't reaudit files closed during inheritance */ - if (file->f_path.dentry == aa_null.dentry) + if (unlikely(file->f_path.dentry == aa_null.dentry)) return -EACCES; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = aa_file_perm(op, current_cred(), label, file, mask, in_atomic); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -664,15 +668,16 @@ static int apparmor_uring_override_creds(const struct cred *new) struct aa_profile *profile; struct aa_label *label; int error; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, OP_URING_OVERRIDE); ad.uring.target = cred_label(new); - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = fn_for_each(label, profile, profile_uring(profile, AA_MAY_OVERRIDE_CRED, cred_label(new), CAP_SYS_ADMIN, &ad)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -688,14 +693,15 @@ static int apparmor_uring_sqpoll(void) struct aa_profile *profile; struct aa_label *label; int error; + bool needput; DEFINE_AUDIT_DATA(ad, LSM_AUDIT_DATA_NONE, AA_CLASS_IO_URING, OP_URING_SQPOLL); - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = fn_for_each(label, profile, profile_uring(profile, AA_MAY_CREATE_SQPOLL, NULL, CAP_SYS_ADMIN, &ad)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -706,6 +712,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path, { struct aa_label *label; int error = 0; + bool needput; /* Discard magic */ if ((flags & MS_MGC_MSK) == MS_MGC_VAL) @@ -713,7 +720,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path, flags &= ~AA_MS_IGNORE_MASK; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) { if (flags & MS_REMOUNT) error = aa_remount(current_cred(), label, path, flags, @@ -732,7 +739,7 @@ static int apparmor_sb_mount(const char *dev_name, const struct path *path, error = aa_new_mount(current_cred(), label, dev_name, path, type, flags, data); } - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -742,12 +749,13 @@ static int apparmor_move_mount(const struct path *from_path, { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_move_mount(current_cred(), label, from_path, to_path); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -756,11 +764,12 @@ static int apparmor_sb_umount(struct vfsmount *mnt, int flags) { struct aa_label *label; int error = 0; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_umount(current_cred(), label, mnt, flags); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -984,10 +993,12 @@ static void apparmor_bprm_committed_creds(const struct linux_binprm *bprm) static void apparmor_current_getlsmprop_subj(struct lsm_prop *prop) { - struct aa_label *label = __begin_current_label_crit_section(); + struct aa_label *label; + bool needput; + label = __begin_current_label_crit_section(&needput); prop->apparmor.label = label; - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); } static void apparmor_task_getlsmprop_obj(struct task_struct *p, @@ -1002,13 +1013,16 @@ static void apparmor_task_getlsmprop_obj(struct task_struct *p, static int apparmor_task_setrlimit(struct task_struct *task, unsigned int resource, struct rlimit *new_rlim) { - struct aa_label *label = __begin_current_label_crit_section(); + struct aa_label *label; int error = 0; + bool needput; + + label = __begin_current_label_crit_section(&needput); if (!unconfined(label)) error = aa_task_setrlimit(current_cred(), label, task, resource, new_rlim); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } @@ -1019,6 +1033,7 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo const struct cred *tc; struct aa_label *cl, *tl; int error; + bool needput; tc = get_task_cred(target); tl = aa_get_newest_cred_label(tc); @@ -1030,9 +1045,9 @@ static int apparmor_task_kill(struct task_struct *target, struct kernel_siginfo error = aa_may_signal(cred, cl, tc, tl, sig); aa_put_label(cl); } else { - cl = __begin_current_label_crit_section(); + cl = __begin_current_label_crit_section(&needput); error = aa_may_signal(current_cred(), cl, tc, tl, sig); - __end_current_label_crit_section(cl); + __end_current_label_crit_section(cl, needput); } aa_put_label(tl); put_cred(tc); @@ -1133,10 +1148,11 @@ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, struct aa_sk_ctx *new_ctx = aa_sock(newsk); struct aa_label *label; int error; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = unix_connect_perm(current_cred(), label, sk, peer_sk); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); if (error) return error; @@ -1163,8 +1179,9 @@ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) struct aa_sk_ctx *peer_ctx = aa_sock(peer->sk); struct aa_label *label; int error; + bool needput; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); error = xcheck(aa_unix_peer_perm(current_cred(), label, OP_SENDMSG, AA_MAY_SEND, sock->sk, peer->sk, NULL), @@ -1172,7 +1189,7 @@ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) peer_ctx->label, OP_SENDMSG, AA_MAY_RECEIVE, peer->sk, sock->sk, label)); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return error; } diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 1f532fe48a1c..a60bb7d9b583 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -870,11 +870,11 @@ bool aa_policy_admin_capable(const struct cred *subj_cred, bool aa_current_policy_view_capable(struct aa_ns *ns) { struct aa_label *label; - bool res; + bool needput, res; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); res = aa_policy_view_capable(current_cred(), label, ns); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return res; } @@ -882,11 +882,11 @@ bool aa_current_policy_view_capable(struct aa_ns *ns) bool aa_current_policy_admin_capable(struct aa_ns *ns) { struct aa_label *label; - bool res; + bool needput, res; - label = __begin_current_label_crit_section(); + label = __begin_current_label_crit_section(&needput); res = aa_policy_admin_capable(current_cred(), label, ns); - __end_current_label_crit_section(label); + __end_current_label_crit_section(label, needput); return res; } From 6afb0a7bc95a61e40c38c58e2bcf6c88fff68d67 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 22 Jun 2025 04:09:06 -0700 Subject: [PATCH 42/60] apparmor: update kernel doc comments for xxx_label_crit_section Add a kernel doc header for __end_current_label_crit_section(), and update the header for __begin_current_label_crit_section(). Fixes: b42ecc5f58ef ("apparmor: make __begin_current_label_crit_section() indicate whether put is needed") Signed-off-by: John Johansen --- security/apparmor/include/cred.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/security/apparmor/include/cred.h b/security/apparmor/include/cred.h index de6ec4969598..b028e4c13b6f 100644 --- a/security/apparmor/include/cred.h +++ b/security/apparmor/include/cred.h @@ -114,6 +114,13 @@ static inline struct aa_label *aa_get_current_label(void) return aa_get_label(l); } +/** + * __end_current_label_crit_section - end crit section begun with __begin_... + * @label: label obtained from __begin_current_label_crit_section + * @needput: output: bool set by __begin_current_label_crit_section + * + * Returns: label to use for this crit section + */ static inline void __end_current_label_crit_section(struct aa_label *label, bool needput) { @@ -137,6 +144,7 @@ static inline void end_current_label_crit_section(struct aa_label *label) /** * __begin_current_label_crit_section - current's confining label + * @needput: store whether the label needs to be put when ending crit section * * Returns: up to date confining label or the ns unconfined label (NOT NULL) * From bc6e5f6933b8e7b74858ac830d5b9b4ca10a099a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 1 Apr 2025 15:28:13 -0700 Subject: [PATCH 43/60] apparmor: Remove use of the double lock The use of the double lock is not necessary and problematic. Instead pull the bits that need locks into their own sections and grab the needed references. Fixes: c05e705812d1 ("apparmor: add fine grained af_unix mediation") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 197 ++++++++++++++-------------- security/apparmor/include/af_unix.h | 1 + security/apparmor/include/audit.h | 1 - security/apparmor/lsm.c | 4 +- security/apparmor/net.c | 3 - 5 files changed, 104 insertions(+), 102 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index ed4b34b88e38..53ccf9becdf7 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -30,11 +30,10 @@ static inline struct sock *aa_unix_sk(struct unix_sock *u) } static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, - struct aa_label *label, struct unix_sock *u) + struct aa_label *label, struct path *path) { AA_BUG(!label); - AA_BUG(!u); - AA_BUG(!is_unix_fs(aa_unix_sk(u))); + AA_BUG(!path); if (unconfined(label) || !label_mediates(label, AA_CLASS_FILE)) return 0; @@ -43,13 +42,13 @@ static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, /* if !u->path.dentry socket is being shutdown - implicit delegation * until obj delegation is supported */ - if (u->path.dentry) { + if (path->dentry) { /* the sunpath may not be valid for this ns so use the path */ - struct path_cond cond = { u->path.dentry->d_inode->i_uid, - u->path.dentry->d_inode->i_mode + struct path_cond cond = { path->dentry->d_inode->i_uid, + path->dentry->d_inode->i_mode }; - return aa_path_perm(op, subj_cred, label, &u->path, + return aa_path_perm(op, subj_cred, label, path, PATH_SOCK_COND, mask, &cond); } /* else implicitly delegated */ @@ -102,18 +101,27 @@ static aa_state_t match_to_local(struct aa_policydb *policy, return state; } +struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen) +{ + struct unix_address *addr; + + /* memory barrier is sufficient see note in net/unix/af_unix.c */ + addr = smp_load_acquire(&u->addr); + if (addr) { + *addrlen = addr->len; + return addr->name; + } + *addrlen = 0; + return NULL; +} + static aa_state_t match_to_sk(struct aa_policydb *policy, aa_state_t state, u32 request, struct unix_sock *u, struct aa_perms **p, const char **info) { - struct sockaddr_un *addr = NULL; - int addrlen = 0; - - if (u->addr) { - addr = u->addr->name; - addrlen = u->addr->len; - } + int addrlen; + struct sockaddr_un *addr = aa_sunaddr(u, &addrlen); return match_to_local(policy, state, request, u->sk.sk_type, u->sk.sk_protocol, addr, addrlen, p, info); @@ -363,7 +371,8 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, /* null peer_label is allowed, in which case the peer_sk label is used */ static int profile_peer_perm(struct aa_profile *profile, u32 request, - struct sock *sk, struct sock *peer_sk, + struct sock *sk, struct sockaddr_un *peer_addr, + int peer_addrlen, struct aa_label *peer_label, struct apparmor_audit_data *ad) { @@ -375,26 +384,16 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request, AA_BUG(!profile); AA_BUG(profile_unconfined(profile)); AA_BUG(!sk); - AA_BUG(!peer_sk); + AA_BUG(!peer_label); AA_BUG(!ad); - AA_BUG(is_unix_fs(peer_sk)); /* currently always calls unix_fs_perm */ state = RULE_MEDIATES_v9NET(rules); if (state) { - struct aa_sk_ctx *peer_ctx = aa_sock(peer_sk); struct aa_profile *peerp; - struct sockaddr_un *addr = NULL; - int len = 0; - if (unix_sk(peer_sk)->addr) { - addr = unix_sk(peer_sk)->addr->name; - len = unix_sk(peer_sk)->addr->len; - } state = match_to_peer(rules->policy, state, request, unix_sk(sk), - addr, len, &p, &ad->info); - if (!peer_label) - peer_label = peer_ctx->label; + peer_addr, peer_addrlen, &p, &ad->info); return fn_for_each_in_ns(peer_label, peerp, match_label(profile, rules, state, request, @@ -422,9 +421,8 @@ int aa_unix_create_perm(struct aa_label *label, int family, int type, return 0; } -int aa_unix_label_sk_perm(const struct cred *subj_cred, - struct aa_label *label, const char *op, u32 request, - struct sock *sk) +int aa_unix_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, + const char *op, u32 request, struct sock *sk) { if (!unconfined(label)) { struct aa_profile *profile; @@ -436,19 +434,6 @@ int aa_unix_label_sk_perm(const struct cred *subj_cred, return 0; } -static int unix_label_sock_perm(const struct cred *subj_cred, - struct aa_label *label, const char *op, - u32 request, struct socket *sock) -{ - if (unconfined(label)) - return 0; - if (is_unix_fs(sock->sk)) - return unix_fs_perm(op, request, subj_cred, label, - unix_sk(sock->sk)); - - return aa_unix_label_sk_perm(subj_cred, label, op, request, sock->sk); -} - /* revalidation, get/set attr, shutdown */ int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) { @@ -456,7 +441,12 @@ int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) int error; label = begin_current_label_crit_section(); - error = unix_label_sock_perm(current_cred(), label, op, request, sock); + if (is_unix_fs(sock->sk)) + error = unix_fs_perm(op, request, current_cred(), label, + &unix_sk(sock->sk)->path); + else + error = aa_unix_label_sk_perm(current_cred(), label, op, + request, sock->sk); end_current_label_crit_section(label); return error; @@ -464,7 +454,7 @@ int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) static int valid_addr(struct sockaddr *addr, int addr_len) { - struct sockaddr_un *sunaddr = (struct sockaddr_un *)addr; + struct sockaddr_un *sunaddr = unix_addr(addr); /* addr_len == offsetof(struct sockaddr_un, sun_path) is autobind */ if (addr_len < offsetof(struct sockaddr_un, sun_path) || @@ -586,6 +576,22 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, return error; } +static int unix_peer_perm(const struct cred *subj_cred, + struct aa_label *label, const char *op, u32 request, + struct sock *sk, struct sockaddr_un *peer_addr, + int peer_addrlen, struct aa_label *peer_label) +{ + struct aa_profile *profile; + DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + + ad.net.addr = peer_addr; + ad.net.addrlen = peer_addrlen; + + return fn_for_each_confined(label, profile, + profile_peer_perm(profile, request, sk, + peer_addr, peer_addrlen, peer_label, &ad)); +} + /** * * Requires: lock held on both @sk and @peer_sk @@ -602,58 +608,37 @@ int aa_unix_peer_perm(const struct cred *subj_cred, AA_BUG(!label); AA_BUG(!sk); AA_BUG(!peer_sk); + AA_BUG(!peer_label); if (is_unix_fs(aa_unix_sk(peeru))) { - return unix_fs_perm(op, request, subj_cred, label, peeru); + return unix_fs_perm(op, request, subj_cred, label, + &peeru->path); } else if (is_unix_fs(aa_unix_sk(u))) { - return unix_fs_perm(op, request, subj_cred, label, u); + return unix_fs_perm(op, request, subj_cred, label, &u->path); } else if (!unconfined(label)) { - struct aa_profile *profile; - DEFINE_AUDIT_SK(ad, op, subj_cred, sk); + int plen; + struct sockaddr_un *paddr = aa_sunaddr(unix_sk(peer_sk), + &plen); - ad.net.peer_sk = peer_sk; - - return fn_for_each_confined(label, profile, - profile_peer_perm(profile, request, sk, - peer_sk, peer_label, &ad)); + return unix_peer_perm(subj_cred, label, op, request, + sk, paddr, plen, peer_label); } return 0; } -static void unix_state_double_lock(struct sock *sk1, struct sock *sk2) -{ - if (unlikely(sk1 == sk2) || !sk2) { - unix_state_lock(sk1); - return; - } - if (sk1 < sk2) { - unix_state_lock(sk1); - unix_state_lock(sk2); - } else { - unix_state_lock(sk2); - unix_state_lock(sk1); - } -} - -static void unix_state_double_unlock(struct sock *sk1, struct sock *sk2) -{ - if (unlikely(sk1 == sk2) || !sk2) { - unix_state_unlock(sk1); - return; - } - unix_state_unlock(sk1); - unix_state_unlock(sk2); -} - -/* TODO: examine replacing double lock with cached addr */ - +/* This fn is only checked if something has changed in the security + * boundaries. Otherwise cached info off file is sufficient + */ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, struct file *file) { struct socket *sock = (struct socket *) file->private_data; + struct sockaddr_un *addr, *peer_addr; + int addrlen, peer_addrlen; struct sock *peer_sk = NULL; u32 sk_req = request & ~NET_PEER_MASK; + struct path path; bool is_sk_fs; int error = 0; @@ -663,40 +648,60 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, AA_BUG(sock->sk->sk_family != PF_UNIX); /* TODO: update sock label with new task label */ + /* investigate only using lock via unix_peer_get() + * addr only needs the memory barrier, but need to investigate + * path + */ unix_state_lock(sock->sk); peer_sk = unix_peer(sock->sk); if (peer_sk) sock_hold(peer_sk); is_sk_fs = is_unix_fs(sock->sk); + addr = aa_sunaddr(unix_sk(sock->sk), &addrlen); + path = unix_sk(sock->sk)->path; + unix_state_unlock(sock->sk); + if (is_sk_fs && peer_sk) sk_req = request; - if (sk_req) - error = unix_label_sock_perm(subj_cred, label, op, sk_req, - sock); - unix_state_unlock(sock->sk); + if (sk_req) { + if (is_sk_fs) + error = unix_fs_perm(op, sk_req, subj_cred, label, + &path); + else + error = aa_unix_label_sk_perm(subj_cred, label, op, + sk_req, sock->sk); + } if (!peer_sk) - return error; + goto out; - unix_state_double_lock(sock->sk, peer_sk); + peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen); + + struct path peer_path; + + peer_path = unix_sk(peer_sk)->path; if (!is_sk_fs && is_unix_fs(peer_sk)) { last_error(error, unix_fs_perm(op, request, subj_cred, label, - unix_sk(peer_sk))); + &peer_path)); } else if (!is_sk_fs) { struct aa_sk_ctx *pctx = aa_sock(peer_sk); + /* no fs check of aa_unix_peer_perm because conditions above + * ensure they will never be done + */ last_error(error, - xcheck(aa_unix_peer_perm(subj_cred, label, op, - MAY_READ | MAY_WRITE, - sock->sk, peer_sk, NULL), - aa_unix_peer_perm(file->f_cred, pctx->label, op, - MAY_READ | MAY_WRITE, - peer_sk, sock->sk, label))); + xcheck(unix_peer_perm(subj_cred, label, op, + MAY_READ | MAY_WRITE, sock->sk, + peer_addr, peer_addrlen, + pctx->label), + unix_peer_perm(file->f_cred, pctx->label, op, + MAY_READ | MAY_WRITE, peer_sk, + addr, addrlen, label))); } - unix_state_double_unlock(sock->sk, peer_sk); - sock_put(peer_sk); +out: + return error; } diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h index 28390eec3204..760d98132392 100644 --- a/security/apparmor/include/af_unix.h +++ b/security/apparmor/include/af_unix.h @@ -31,6 +31,7 @@ #define is_unix_connected(S) ((S)->state == SS_CONNECTED) +struct sockaddr_un *aa_sunaddr(const struct unix_sock *u, int *addrlen); int aa_unix_peer_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, struct sock *sk, struct sock *peer_sk, diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index e27229349abb..365bc67dd150 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -138,7 +138,6 @@ struct apparmor_audit_data { }; struct { int type, protocol; - struct sock *peer_sk; void *addr; int addrlen; } net; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 990211381319..0b53ac1c2d70 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1112,7 +1112,7 @@ static int unix_connect_perm(const struct cred *cred, struct aa_label *label, error = aa_unix_peer_perm(cred, label, OP_CONNECT, (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), - sk, peer_sk, NULL); + sk, peer_sk, peer_ctx->label); if (!is_unix_fs(peer_sk)) { last_error(error, aa_unix_peer_perm(cred, @@ -1184,7 +1184,7 @@ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) label = __begin_current_label_crit_section(&needput); error = xcheck(aa_unix_peer_perm(current_cred(), label, OP_SENDMSG, AA_MAY_SEND, - sock->sk, peer->sk, NULL), + sock->sk, peer->sk, peer_ctx->label), aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL, peer_ctx->label, OP_SENDMSG, AA_MAY_RECEIVE, diff --git a/security/apparmor/net.c b/security/apparmor/net.c index a256a4664826..e6f9e11eaa6a 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -148,9 +148,6 @@ void audit_net_cb(struct audit_buffer *ab, void *va) audit_unix_addr(ab, "peer_addr", unix_addr(ad->net.addr), ad->net.addrlen); - else - audit_unix_sk_addr(ab, "peer_addr", - ad->net.peer_sk); } } if (ad->peer) { From a30a9fdb66319466a7c76b455524d27c75d2b05b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 14 Jun 2025 13:49:02 -0700 Subject: [PATCH 44/60] apparmor: fix af_unix auditing to include all address information The auditing of addresses currently doesn't include the source address and mixes source and foreign/peer under the same audit name. Fix this so source is always addr, and the foreign/peer is peer_addr. Fixes: c05e705812d1 ("apparmor: add fine grained af_unix mediation") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 4 ++-- security/apparmor/include/audit.h | 4 ++++ security/apparmor/net.c | 20 ++++++++++++-------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 53ccf9becdf7..03d44fa19d12 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -584,8 +584,8 @@ static int unix_peer_perm(const struct cred *subj_cred, struct aa_profile *profile; DEFINE_AUDIT_SK(ad, op, subj_cred, sk); - ad.net.addr = peer_addr; - ad.net.addrlen = peer_addrlen; + ad.net.peer.addr = peer_addr; + ad.net.peer.addrlen = peer_addrlen; return fn_for_each_confined(label, profile, profile_peer_perm(profile, request, sk, diff --git a/security/apparmor/include/audit.h b/security/apparmor/include/audit.h index 365bc67dd150..1a71a94ea19c 100644 --- a/security/apparmor/include/audit.h +++ b/security/apparmor/include/audit.h @@ -140,6 +140,10 @@ struct apparmor_audit_data { int type, protocol; void *addr; int addrlen; + struct { + void *addr; + int addrlen; + } peer; } net; }; }; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index e6f9e11eaa6a..2da554cc3a35 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -99,10 +99,15 @@ static void audit_unix_sk_addr(struct audit_buffer *ab, const char *str, { const struct unix_sock *u = unix_sk(sk); - if (u && u->addr) - audit_unix_addr(ab, str, u->addr->name, u->addr->len); - else + if (u && u->addr) { + int addrlen; + struct sockaddr_un *addr = aa_sunaddr(u, &addrlen); + + audit_unix_addr(ab, str, addr, addrlen); + } else { audit_unix_addr(ab, str, NULL, 0); + + } } /* audit callback for net specific fields */ @@ -137,17 +142,16 @@ void audit_net_cb(struct audit_buffer *ab, void *va) } } if (ad->common.u.net->family == PF_UNIX) { - if ((ad->request & ~NET_PEER_MASK) && ad->net.addr) + if (ad->net.addr || !ad->common.u.net->sk) audit_unix_addr(ab, "addr", unix_addr(ad->net.addr), ad->net.addrlen); else audit_unix_sk_addr(ab, "addr", ad->common.u.net->sk); if (ad->request & NET_PEER_MASK) { - if (ad->net.addr) - audit_unix_addr(ab, "peer_addr", - unix_addr(ad->net.addr), - ad->net.addrlen); + audit_unix_addr(ab, "peer_addr", + unix_addr(ad->net.peer.addr), + ad->net.peer.addrlen); } } if (ad->peer) { From 50d56a1a366a3a5e7e41d9efff1a5e4ee7bf98a7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sat, 14 Jun 2025 13:49:34 -0700 Subject: [PATCH 45/60] apparmor: fix AA_DEBUG_LABEL() AA_DEBUG_LABEL() was not specifying it vargs, which is needed so it can output debug parameters. Fixes: 71e6cff3e0dd ("apparmor: Improve debug print infrastructure") Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 200cf36c5e0a..444197075fd6 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -42,7 +42,7 @@ extern struct aa_dfa *stacksplitdfa; if (aa_g_debug & opt) \ pr_warn_ratelimited("%s: " fmt, __func__, ##args); \ } while (0) -#define AA_DEBUG_LABEL(LAB, X, fmt, args) \ +#define AA_DEBUG_LABEL(LAB, X, fmt, args...) \ do { \ if ((LAB)->flags & FLAG_DEBUG1) \ AA_DEBUG(X, fmt, args); \ From 6456ccbd2ff72814b3c1b2e2a3a2145a2ced858d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 4 Jun 2025 01:45:05 -0700 Subject: [PATCH 46/60] apparmor: fix regression in fs based unix sockets when using old abi Policy loaded using abi 7 socket mediation was not being applied correctly in all cases. In some cases with fs based unix sockets a subset of permissions where allowed when they should have been denied. This was happening because the check for if the socket was an fs based unix socket came before the abi check. But the abi check is where the correct path is selected, so having the fs unix socket check occur early would cause the wrong code path to be used. Fix this by pushing the fs unix to be done after the abi check. Fixes: dcd7a559411e ("apparmor: gate make fine grained unix mediation behind v9 abi") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 119 +++++++++++++++++----------- security/apparmor/include/af_unix.h | 3 - 2 files changed, 71 insertions(+), 51 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 03d44fa19d12..dc25f1afe819 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -221,7 +221,7 @@ static int profile_create_perm(struct aa_profile *profile, int family, static int profile_sk_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, - u32 request, struct sock *sk) + u32 request, struct sock *sk, struct path *path) { struct aa_ruleset *rules = list_first_entry(&profile->rules, typeof(*rules), @@ -231,11 +231,15 @@ static int profile_sk_perm(struct aa_profile *profile, AA_BUG(!profile); AA_BUG(!sk); - AA_BUG(is_unix_fs(sk)); AA_BUG(profile_unconfined(profile)); state = RULE_MEDIATES_v9NET(rules); if (state) { + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, + &unix_sk(sk)->path); + state = match_to_sk(rules->policy, state, request, unix_sk(sk), &p, &ad->info); @@ -261,6 +265,9 @@ static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, state = RULE_MEDIATES_v9NET(rules); if (state) { + if (is_unix_addr_fs(ad->net.addr, ad->net.addrlen)) + /* under v7-9 fs hook handles bind */ + return 0; /* bind for abstract socket */ state = match_to_local(rules->policy, state, AA_MAY_BIND, sk->sk_type, sk->sk_protocol, @@ -285,7 +292,6 @@ static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, AA_BUG(!profile); AA_BUG(!sk); - AA_BUG(is_unix_fs(sk)); AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); @@ -293,6 +299,11 @@ static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, if (state) { __be16 b = cpu_to_be16(backlog); + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, AA_MAY_LISTEN, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + state = match_to_cmd(rules->policy, state, AA_MAY_LISTEN, unix_sk(sk), CMD_LISTEN, &p, &ad->info); if (state && !p) { @@ -319,12 +330,16 @@ static int profile_accept_perm(struct aa_profile *profile, AA_BUG(!profile); AA_BUG(!sk); - AA_BUG(is_unix_fs(sk)); AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); state = RULE_MEDIATES_v9NET(rules); if (state) { + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, AA_MAY_ACCEPT, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); + state = match_to_sk(rules->policy, state, AA_MAY_ACCEPT, unix_sk(sk), &p, &ad->info); @@ -346,13 +361,16 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, AA_BUG(!profile); AA_BUG(!sk); - AA_BUG(is_unix_fs(sk)); AA_BUG(!ad); AA_BUG(profile_unconfined(profile)); state = RULE_MEDIATES_v9NET(rules); if (state) { __be16 b = cpu_to_be16(optname); + if (is_unix_fs(sk)) + return unix_fs_perm(ad->op, request, + ad->subj_cred, &profile->label, + &unix_sk(sk)->path); state = match_to_cmd(rules->policy, state, request, unix_sk(sk), CMD_OPT, &p, &ad->info); @@ -371,8 +389,9 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, /* null peer_label is allowed, in which case the peer_sk label is used */ static int profile_peer_perm(struct aa_profile *profile, u32 request, - struct sock *sk, struct sockaddr_un *peer_addr, - int peer_addrlen, + struct sock *sk, struct path *path, + struct sockaddr_un *peer_addr, + int peer_addrlen, struct path *peer_path, struct aa_label *peer_label, struct apparmor_audit_data *ad) { @@ -391,6 +410,12 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request, if (state) { struct aa_profile *peerp; + if (peer_path) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, peer_path); + else if (path) + return unix_fs_perm(ad->op, request, ad->subj_cred, + &profile->label, path); state = match_to_peer(rules->policy, state, request, unix_sk(sk), peer_addr, peer_addrlen, &p, &ad->info); @@ -421,15 +446,18 @@ int aa_unix_create_perm(struct aa_label *label, int family, int type, return 0; } -int aa_unix_label_sk_perm(const struct cred *subj_cred, struct aa_label *label, - const char *op, u32 request, struct sock *sk) +static int aa_unix_label_sk_perm(const struct cred *subj_cred, + struct aa_label *label, + const char *op, u32 request, struct sock *sk, + struct path *path) { if (!unconfined(label)) { struct aa_profile *profile; DEFINE_AUDIT_SK(ad, op, subj_cred, sk); return fn_for_each_confined(label, profile, - profile_sk_perm(profile, &ad, request, sk)); + profile_sk_perm(profile, &ad, request, sk, + path)); } return 0; } @@ -441,12 +469,9 @@ int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock) int error; label = begin_current_label_crit_section(); - if (is_unix_fs(sock->sk)) - error = unix_fs_perm(op, request, current_cred(), label, - &unix_sk(sock->sk)->path); - else - error = aa_unix_label_sk_perm(current_cred(), label, op, - request, sock->sk); + error = aa_unix_label_sk_perm(current_cred(), label, op, + request, sock->sk, + is_unix_fs(sock->sk) ? &unix_sk(sock->sk)->path : NULL); end_current_label_crit_section(label); return error; @@ -476,7 +501,7 @@ int aa_unix_bind_perm(struct socket *sock, struct sockaddr *addr, label = begin_current_label_crit_section(); /* fs bind is handled by mknod */ - if (!(unconfined(label) || is_unix_addr_fs(addr, addrlen))) { + if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_BIND, current_cred(), sock->sk); ad.net.addr = unix_addr(addr); @@ -510,7 +535,7 @@ int aa_unix_listen_perm(struct socket *sock, int backlog) int error = 0; label = begin_current_label_crit_section(); - if (!(unconfined(label) || is_unix_fs(sock->sk))) { + if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_LISTEN, current_cred(), sock->sk); error = fn_for_each_confined(label, profile, @@ -531,7 +556,7 @@ int aa_unix_accept_perm(struct socket *sock, struct socket *newsock) int error = 0; label = begin_current_label_crit_section(); - if (!(unconfined(label) || is_unix_fs(sock->sk))) { + if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, OP_ACCEPT, current_cred(), sock->sk); error = fn_for_each_confined(label, profile, @@ -564,12 +589,12 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, int error = 0; label = begin_current_label_crit_section(); - if (!(unconfined(label) || is_unix_fs(sock->sk))) { + if (!unconfined(label)) { DEFINE_AUDIT_SK(ad, op, current_cred(), sock->sk); error = fn_for_each_confined(label, profile, - profile_opt_perm(profile, request, - sock->sk, optname, &ad)); + profile_opt_perm(profile, request, sock->sk, + optname, &ad)); } end_current_label_crit_section(label); @@ -578,8 +603,9 @@ int aa_unix_opt_perm(const char *op, u32 request, struct socket *sock, static int unix_peer_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, - struct sock *sk, struct sockaddr_un *peer_addr, - int peer_addrlen, struct aa_label *peer_label) + struct sock *sk, struct path *path, + struct sockaddr_un *peer_addr, int peer_addrlen, + struct path *peer_path, struct aa_label *peer_label) { struct aa_profile *profile; DEFINE_AUDIT_SK(ad, op, subj_cred, sk); @@ -588,8 +614,9 @@ static int unix_peer_perm(const struct cred *subj_cred, ad.net.peer.addrlen = peer_addrlen; return fn_for_each_confined(label, profile, - profile_peer_perm(profile, request, sk, - peer_addr, peer_addrlen, peer_label, &ad)); + profile_peer_perm(profile, request, sk, path, + peer_addr, peer_addrlen, peer_path, + peer_label, &ad)); } /** @@ -604,27 +631,19 @@ int aa_unix_peer_perm(const struct cred *subj_cred, { struct unix_sock *peeru = unix_sk(peer_sk); struct unix_sock *u = unix_sk(sk); + int plen; + struct sockaddr_un *paddr = aa_sunaddr(unix_sk(peer_sk), &plen); AA_BUG(!label); AA_BUG(!sk); AA_BUG(!peer_sk); AA_BUG(!peer_label); - if (is_unix_fs(aa_unix_sk(peeru))) { - return unix_fs_perm(op, request, subj_cred, label, - &peeru->path); - } else if (is_unix_fs(aa_unix_sk(u))) { - return unix_fs_perm(op, request, subj_cred, label, &u->path); - } else if (!unconfined(label)) { - int plen; - struct sockaddr_un *paddr = aa_sunaddr(unix_sk(peer_sk), - &plen); - - return unix_peer_perm(subj_cred, label, op, request, - sk, paddr, plen, peer_label); - } - - return 0; + return unix_peer_perm(subj_cred, label, op, request, sk, + is_unix_fs(sk) ? &u->path : NULL, + paddr, plen, + is_unix_fs(peer_sk) ? &peeru->path : NULL, + peer_label); } /* This fn is only checked if something has changed in the security @@ -665,12 +684,9 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, if (is_sk_fs && peer_sk) sk_req = request; if (sk_req) { - if (is_sk_fs) - error = unix_fs_perm(op, sk_req, subj_cred, label, - &path); - else error = aa_unix_label_sk_perm(subj_cred, label, op, - sk_req, sock->sk); + sk_req, sock->sk, + is_sk_fs ? &path : NULL); } if (!peer_sk) goto out; @@ -683,7 +699,7 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, if (!is_sk_fs && is_unix_fs(peer_sk)) { last_error(error, unix_fs_perm(op, request, subj_cred, label, - &peer_path)); + is_unix_fs(peer_sk) ? &peer_path : NULL)); } else if (!is_sk_fs) { struct aa_sk_ctx *pctx = aa_sock(peer_sk); @@ -693,11 +709,18 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, last_error(error, xcheck(unix_peer_perm(subj_cred, label, op, MAY_READ | MAY_WRITE, sock->sk, + is_sk_fs ? &path : NULL, peer_addr, peer_addrlen, + is_unix_fs(peer_sk) ? + &peer_path : NULL, pctx->label), unix_peer_perm(file->f_cred, pctx->label, op, MAY_READ | MAY_WRITE, peer_sk, - addr, addrlen, label))); + is_unix_fs(peer_sk) ? + &peer_path : NULL, + addr, addrlen, + is_sk_fs ? &path : NULL, + label))); } sock_put(peer_sk); diff --git a/security/apparmor/include/af_unix.h b/security/apparmor/include/af_unix.h index 760d98132392..4a62e600d82b 100644 --- a/security/apparmor/include/af_unix.h +++ b/security/apparmor/include/af_unix.h @@ -36,9 +36,6 @@ int aa_unix_peer_perm(const struct cred *subj_cred, struct aa_label *label, const char *op, u32 request, struct sock *sk, struct sock *peer_sk, struct aa_label *peer_label); -int aa_unix_label_sk_perm(const struct cred *subj_cred, - struct aa_label *label, const char *op, u32 request, - struct sock *sk); int aa_unix_sock_perm(const char *op, u32 request, struct socket *sock); int aa_unix_create_perm(struct aa_label *label, int family, int type, int protocol); From 88fec3526e84123997ecebd6bb6778eb4ce779b7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 19 Jun 2025 22:11:52 -0700 Subject: [PATCH 47/60] apparmor: make sure unix socket labeling is correctly updated. When a unix socket is passed into a different confinement domain make sure its cached mediation labeling is updated to correctly reflect which domains are using the socket. Fixes: c05e705812d1 ("apparmor: add fine grained af_unix mediation") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 79 +++++++++++++- security/apparmor/file.c | 35 +++++-- security/apparmor/include/label.h | 7 ++ security/apparmor/include/net.h | 5 +- security/apparmor/lsm.c | 165 +++++++++++++++++++++--------- security/apparmor/net.c | 2 +- 6 files changed, 231 insertions(+), 62 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index dc25f1afe819..257648a13bf8 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -646,6 +646,67 @@ int aa_unix_peer_perm(const struct cred *subj_cred, peer_label); } +/* sk_plabel for comparison only */ +static void update_sk_ctx(struct sock *sk, struct aa_label *label, + struct aa_label *plabel) +{ + struct aa_label *l, *old; + struct aa_sk_ctx *ctx = aa_sock(sk); + bool update_sk; + + rcu_read_lock(); + update_sk = (plabel && + (plabel != rcu_access_pointer(ctx->peer_lastupdate) || + !aa_label_is_subset(plabel, rcu_dereference(ctx->peer)))) || + !__aa_subj_label_is_cached(label, rcu_dereference(ctx->label)); + rcu_read_unlock(); + if (!update_sk) + return; + + spin_lock(&unix_sk(sk)->lock); + old = rcu_dereference_protected(ctx->label, + lockdep_is_held(&unix_sk(sk)->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(ctx->label, l); + aa_put_label(old); + } else + aa_put_label(l); + } + if (plabel && rcu_access_pointer(ctx->peer_lastupdate) != plabel) { + old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock)); + + if (old == plabel) { + rcu_assign_pointer(ctx->peer_lastupdate, plabel); + } else if (aa_label_is_subset(plabel, old)) { + rcu_assign_pointer(ctx->peer_lastupdate, plabel); + rcu_assign_pointer(ctx->peer, aa_get_label(plabel)); + aa_put_label(old); + } /* else race or a subset - don't update */ + } + spin_unlock(&unix_sk(sk)->lock); +} + +static void update_peer_ctx(struct sock *sk, struct aa_sk_ctx *ctx, + struct aa_label *label) +{ + struct aa_label *l, *old; + + spin_lock(&unix_sk(sk)->lock); + old = rcu_dereference_protected(ctx->peer, + lockdep_is_held(&unix_sk(sk)->lock)); + l = aa_label_merge(old, label, GFP_ATOMIC); + if (l) { + if (l != old) { + rcu_assign_pointer(ctx->peer, l); + aa_put_label(old); + } else + aa_put_label(l); + } + spin_unlock(&unix_sk(sk)->lock); +} + /* This fn is only checked if something has changed in the security * boundaries. Otherwise cached info off file is sufficient */ @@ -655,6 +716,7 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, struct socket *sock = (struct socket *) file->private_data; struct sockaddr_un *addr, *peer_addr; int addrlen, peer_addrlen; + struct aa_label *plabel = NULL; struct sock *peer_sk = NULL; u32 sk_req = request & ~NET_PEER_MASK; struct path path; @@ -666,7 +728,6 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, AA_BUG(!sock->sk); AA_BUG(sock->sk->sk_family != PF_UNIX); - /* TODO: update sock label with new task label */ /* investigate only using lock via unix_peer_get() * addr only needs the memory barrier, but need to investigate * path @@ -701,8 +762,12 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, unix_fs_perm(op, request, subj_cred, label, is_unix_fs(peer_sk) ? &peer_path : NULL)); } else if (!is_sk_fs) { + struct aa_label *plabel; struct aa_sk_ctx *pctx = aa_sock(peer_sk); + rcu_read_lock(); + plabel = aa_get_label_rcu(&pctx->label); + rcu_read_unlock(); /* no fs check of aa_unix_peer_perm because conditions above * ensure they will never be done */ @@ -713,18 +778,26 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, peer_addr, peer_addrlen, is_unix_fs(peer_sk) ? &peer_path : NULL, - pctx->label), - unix_peer_perm(file->f_cred, pctx->label, op, + plabel), + unix_peer_perm(file->f_cred, plabel, op, MAY_READ | MAY_WRITE, peer_sk, is_unix_fs(peer_sk) ? &peer_path : NULL, addr, addrlen, is_sk_fs ? &path : NULL, label))); + if (!error && !__aa_subj_label_is_cached(plabel, label)) + update_peer_ctx(peer_sk, pctx, label); } sock_put(peer_sk); out: + /* update peer cache to latest successful perm check */ + if (error == 0) + update_sk_ctx(sock->sk, label, plabel); + aa_put_label(plabel); + return error; } + diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 5c984792cbf0..65e1d29af792 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -561,19 +561,35 @@ static int __file_sock_perm(const char *op, const struct cred *subj_cred, return error; } -/* wrapper fn to indicate semantics of the check */ -static bool __subj_label_is_cached(struct aa_label *subj_label, - struct aa_label *obj_label) -{ - return aa_label_is_subset(obj_label, subj_label); -} - /* for now separate fn to indicate semantics of the check */ static bool __file_is_delegated(struct aa_label *obj_label) { return unconfined(obj_label); } +static bool __unix_needs_revalidation(struct file *file, struct aa_label *label, + u32 request) +{ + struct socket *sock = (struct socket *) file->private_data; + + lockdep_assert_in_rcu_read_lock(); + + if (!S_ISSOCK(file_inode(file)->i_mode)) + return false; + if (request & NET_PEER_MASK) + return false; + if (sock->sk->sk_family == PF_UNIX) { + struct aa_sk_ctx *ctx = aa_sock(sock->sk); + + if (rcu_access_pointer(ctx->peer) != + rcu_access_pointer(ctx->peer_lastupdate)) + return true; + return !__aa_subj_label_is_cached(rcu_dereference(ctx->label), + label); + } + return false; +} + /** * aa_file_perm - do permission revalidation check & audit for @file * @op: operation being checked @@ -612,14 +628,15 @@ int aa_file_perm(const char *op, const struct cred *subj_cred, */ denied = request & ~fctx->allow; if (unconfined(label) || __file_is_delegated(flabel) || - (!denied && __subj_label_is_cached(label, flabel))) { + __unix_needs_revalidation(file, label, request) || + (!denied && __aa_subj_label_is_cached(label, flabel))) { rcu_read_unlock(); goto done; } + /* slow path - revalidate access */ flabel = aa_get_newest_label(flabel); rcu_read_unlock(); - /* TODO: label cross check */ if (file->f_path.mnt && path_mediated_fs(file->f_path.dentry)) error = __file_path_perm(op, subj_cred, label, flabel, file, diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 5e7d199c15e2..9aa2e364cca9 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -415,6 +415,13 @@ static inline void aa_put_label(struct aa_label *l) kref_put(&l->count, aa_label_kref); } +/* wrapper fn to indicate semantics of the check */ +static inline bool __aa_subj_label_is_cached(struct aa_label *subj_label, + struct aa_label *obj_label) +{ + return aa_label_is_subset(obj_label, subj_label); +} + struct aa_proxy *aa_alloc_proxy(struct aa_label *l, gfp_t gfp); void aa_proxy_kref(struct kref *kref); diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h index 5089e937d550..0d0b0ce42723 100644 --- a/security/apparmor/include/net.h +++ b/security/apparmor/include/net.h @@ -47,8 +47,9 @@ #define NET_PEER_MASK (AA_MAY_SEND | AA_MAY_RECEIVE | AA_MAY_CONNECT | \ AA_MAY_ACCEPT) struct aa_sk_ctx { - struct aa_label *label; - struct aa_label *peer; + struct aa_label __rcu *label; + struct aa_label __rcu *peer; + struct aa_label __rcu *peer_lastupdate; /* ptr cmp only, no deref */ }; static inline struct aa_sk_ctx *aa_sock(const struct sock *sk) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 0b53ac1c2d70..0640a379a518 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -508,7 +508,6 @@ static int apparmor_file_alloc_security(struct file *file) struct aa_file_ctx *ctx = file_ctx(file); struct aa_label *label = begin_current_label_crit_section(); - spin_lock_init(&ctx->lock); rcu_assign_pointer(ctx->label, aa_get_label(label)); end_current_label_crit_section(label); return 0; @@ -1076,12 +1075,29 @@ static int apparmor_userns_create(const struct cred *cred) return error; } +static int apparmor_sk_alloc_security(struct sock *sk, int family, gfp_t gfp) +{ + struct aa_sk_ctx *ctx = aa_sock(sk); + struct aa_label *label; + bool needput; + + label = __begin_current_label_crit_section(&needput); + //spin_lock_init(&ctx->lock); + rcu_assign_pointer(ctx->label, aa_get_label(label)); + rcu_assign_pointer(ctx->peer, NULL); + rcu_assign_pointer(ctx->peer_lastupdate, NULL); + __end_current_label_crit_section(label, needput); + return 0; +} + static void apparmor_sk_free_security(struct sock *sk) { struct aa_sk_ctx *ctx = aa_sock(sk); - aa_put_label(ctx->label); - aa_put_label(ctx->peer); + /* dead these won't be updated any more */ + aa_put_label(rcu_dereference_protected(ctx->label, true)); + aa_put_label(rcu_dereference_protected(ctx->peer, true)); + aa_put_label(rcu_dereference_protected(ctx->peer_lastupdate, true)); } /** @@ -1095,13 +1111,22 @@ static void apparmor_sk_clone_security(const struct sock *sk, struct aa_sk_ctx *ctx = aa_sock(sk); struct aa_sk_ctx *new = aa_sock(newsk); - if (new->label) - aa_put_label(new->label); - new->label = aa_get_label(ctx->label); + /* not actually in use yet */ + if (rcu_access_pointer(ctx->label) != rcu_access_pointer(new->label)) { + aa_put_label(rcu_dereference_protected(new->label, true)); + rcu_assign_pointer(new->label, aa_get_label_rcu(&ctx->label)); + } - if (new->peer) - aa_put_label(new->peer); - new->peer = aa_get_label(ctx->peer); + if (rcu_access_pointer(ctx->peer) != rcu_access_pointer(new->peer)) { + aa_put_label(rcu_dereference_protected(new->peer, true)); + rcu_assign_pointer(new->peer, aa_get_label_rcu(&ctx->peer)); + } + + if (rcu_access_pointer(ctx->peer_lastupdate) != rcu_access_pointer(new->peer_lastupdate)) { + aa_put_label(rcu_dereference_protected(new->peer_lastupdate, true)); + rcu_assign_pointer(new->peer_lastupdate, + aa_get_label_rcu(&ctx->peer_lastupdate)); + } } static int unix_connect_perm(const struct cred *cred, struct aa_label *label, @@ -1112,27 +1137,47 @@ static int unix_connect_perm(const struct cred *cred, struct aa_label *label, error = aa_unix_peer_perm(cred, label, OP_CONNECT, (AA_MAY_CONNECT | AA_MAY_SEND | AA_MAY_RECEIVE), - sk, peer_sk, peer_ctx->label); + sk, peer_sk, + rcu_dereference_protected(peer_ctx->label, + lockdep_is_held(&unix_sk(peer_sk)->lock))); if (!is_unix_fs(peer_sk)) { last_error(error, aa_unix_peer_perm(cred, - peer_ctx->label, OP_CONNECT, + rcu_dereference_protected(peer_ctx->label, + lockdep_is_held(&unix_sk(peer_sk)->lock)), + OP_CONNECT, (AA_MAY_ACCEPT | AA_MAY_SEND | AA_MAY_RECEIVE), - peer_sk, sk, label)); + peer_sk, sk, label)); } return error; } +/* lockdep check in unix_connect_perm - push sks here to check */ static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, struct aa_sk_ctx *peer_ctx) { /* Cross reference the peer labels for SO_PEERSEC */ - aa_put_label(peer_ctx->peer); - aa_put_label(sk_ctx->peer); + struct aa_label *label = rcu_dereference_protected(sk_ctx->label, true); - peer_ctx->peer = aa_get_label(sk_ctx->label); - sk_ctx->peer = aa_get_label(peer_ctx->label); + aa_get_label(label); + aa_put_label(rcu_dereference_protected(peer_ctx->peer, + true)); + rcu_assign_pointer(peer_ctx->peer, label); /* transfer cnt */ + + label = aa_get_label(rcu_dereference_protected(peer_ctx->label, + true)); + //spin_unlock(&peer_ctx->lock); + + //spin_lock(&sk_ctx->lock); + aa_put_label(rcu_dereference_protected(sk_ctx->peer, + true)); + aa_put_label(rcu_dereference_protected(sk_ctx->peer_lastupdate, + true)); + + rcu_assign_pointer(sk_ctx->peer, aa_get_label(label)); + rcu_assign_pointer(sk_ctx->peer_lastupdate, label); /* transfer cnt */ + //spin_unlock(&sk_ctx->lock); } /** @@ -1158,8 +1203,10 @@ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, return error; /* newsk doesn't go through post_create */ - AA_BUG(new_ctx->label); - new_ctx->label = aa_get_label(peer_ctx->label); + AA_BUG(rcu_access_pointer(new_ctx->label)); + rcu_assign_pointer(new_ctx->label, + aa_get_label(rcu_dereference_protected(peer_ctx->label, + true))); /* Cross reference the peer labels for SO_PEERSEC */ unix_connect_peers(sk_ctx, new_ctx); @@ -1183,12 +1230,15 @@ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) label = __begin_current_label_crit_section(&needput); error = xcheck(aa_unix_peer_perm(current_cred(), - label, OP_SENDMSG, AA_MAY_SEND, - sock->sk, peer->sk, peer_ctx->label), + label, OP_SENDMSG, AA_MAY_SEND, + sock->sk, peer->sk, + rcu_dereference_protected(peer_ctx->label, + true)), aa_unix_peer_perm(peer->file ? peer->file->f_cred : NULL, - peer_ctx->label, OP_SENDMSG, - AA_MAY_RECEIVE, - peer->sk, sock->sk, label)); + rcu_dereference_protected(peer_ctx->label, + true), + OP_SENDMSG, AA_MAY_RECEIVE, peer->sk, + sock->sk, label)); __end_current_label_crit_section(label, needput); return error; @@ -1246,8 +1296,9 @@ static int apparmor_socket_post_create(struct socket *sock, int family, if (sock->sk) { struct aa_sk_ctx *ctx = aa_sock(sock->sk); - aa_put_label(ctx->label); - ctx->label = aa_get_label(label); + /* still not live */ + aa_put_label(rcu_dereference_protected(ctx->label, true)); + rcu_assign_pointer(ctx->label, aa_get_label(label)); } aa_put_label(label); @@ -1260,23 +1311,27 @@ static int apparmor_socket_socketpair(struct socket *socka, struct aa_sk_ctx *a_ctx = aa_sock(socka->sk); struct aa_sk_ctx *b_ctx = aa_sock(sockb->sk); struct aa_label *label; - int error = 0; - - aa_put_label(a_ctx->label); - aa_put_label(b_ctx->label); + /* socks not live yet - initial values set in sk_alloc */ label = begin_current_label_crit_section(); - a_ctx->label = aa_get_label(label); - b_ctx->label = aa_get_label(label); + if (rcu_access_pointer(a_ctx->label) != label) { + AA_BUG("a_ctx != label"); + aa_put_label(rcu_dereference_protected(a_ctx->label, true)); + rcu_assign_pointer(a_ctx->label, aa_get_label(label)); + } + if (rcu_access_pointer(b_ctx->label) != label) { + AA_BUG("b_ctx != label"); + aa_put_label(rcu_dereference_protected(b_ctx->label, true)); + rcu_assign_pointer(b_ctx->label, aa_get_label(label)); + } if (socka->sk->sk_family == PF_UNIX) { /* unix socket pairs by-pass unix_stream_connect */ - if (!error) - unix_connect_peers(a_ctx, b_ctx); + unix_connect_peers(a_ctx, b_ctx); } end_current_label_crit_section(label); - return error; + return 0; } /** @@ -1430,6 +1485,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) { struct aa_sk_ctx *ctx = aa_sock(sk); + int error; if (!skb->secmark) return 0; @@ -1438,11 +1494,15 @@ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) * If reach here before socket_post_create hook is called, in which * case label is null, drop the packet. */ - if (!ctx->label) + if (!rcu_access_pointer(ctx->label)) return -EACCES; - return apparmor_secmark_check(ctx->label, OP_RECVMSG, AA_MAY_RECEIVE, - skb->secmark, sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_RECVMSG, + AA_MAY_RECEIVE, skb->secmark, sk); + rcu_read_unlock(); + + return error; } #endif @@ -1452,8 +1512,8 @@ static struct aa_label *sk_peer_get_label(struct sock *sk) struct aa_sk_ctx *ctx = aa_sock(sk); struct aa_label *label = ERR_PTR(-ENOPROTOOPT); - if (ctx->peer) - return aa_get_label(ctx->peer); + if (rcu_access_pointer(ctx->peer)) + return aa_get_label_rcu(&ctx->peer); if (sk->sk_family != PF_UNIX) return ERR_PTR(-ENOPROTOOPT); @@ -1480,12 +1540,12 @@ static int apparmor_socket_getpeersec_stream(struct socket *sock, struct aa_label *label; struct aa_label *peer; - label = begin_current_label_crit_section(); peer = sk_peer_get_label(sock->sk); if (IS_ERR(peer)) { error = PTR_ERR(peer); goto done; } + label = begin_current_label_crit_section(); slen = aa_label_asxprint(&name, labels_ns(label), peer, FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED, GFP_KERNEL); @@ -1506,9 +1566,9 @@ done_len: error = -EFAULT; done_put: + end_current_label_crit_section(label); aa_put_label(peer); done: - end_current_label_crit_section(label); kfree(name); return error; } @@ -1544,8 +1604,9 @@ static void apparmor_sock_graft(struct sock *sk, struct socket *parent) { struct aa_sk_ctx *ctx = aa_sock(sk); - if (!ctx->label) - ctx->label = aa_get_current_label(); + /* setup - not live */ + if (!rcu_access_pointer(ctx->label)) + rcu_assign_pointer(ctx->label, aa_get_current_label()); } #ifdef CONFIG_NETWORK_SECMARK @@ -1553,12 +1614,17 @@ static int apparmor_inet_conn_request(const struct sock *sk, struct sk_buff *skb struct request_sock *req) { struct aa_sk_ctx *ctx = aa_sock(sk); + int error; if (!skb->secmark) return 0; - return apparmor_secmark_check(ctx->label, OP_CONNECT, AA_MAY_CONNECT, - skb->secmark, sk); + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_CONNECT, + AA_MAY_CONNECT, skb->secmark, sk); + rcu_read_unlock(); + + return error; } #endif @@ -1615,6 +1681,7 @@ static struct security_hook_list apparmor_hooks[] __ro_after_init = { LSM_HOOK_INIT(getprocattr, apparmor_getprocattr), LSM_HOOK_INIT(setprocattr, apparmor_setprocattr), + LSM_HOOK_INIT(sk_alloc_security, apparmor_sk_alloc_security), LSM_HOOK_INIT(sk_free_security, apparmor_sk_free_security), LSM_HOOK_INIT(sk_clone_security, apparmor_sk_clone_security), @@ -2266,6 +2333,7 @@ static unsigned int apparmor_ip_postroute(void *priv, { struct aa_sk_ctx *ctx; struct sock *sk; + int error; if (!skb->secmark) return NF_ACCEPT; @@ -2275,8 +2343,11 @@ static unsigned int apparmor_ip_postroute(void *priv, return NF_ACCEPT; ctx = aa_sock(sk); - if (!apparmor_secmark_check(ctx->label, OP_SENDMSG, AA_MAY_SEND, - skb->secmark, sk)) + rcu_read_lock(); + error = apparmor_secmark_check(rcu_dereference(ctx->label), OP_SENDMSG, + AA_MAY_SEND, skb->secmark, sk); + rcu_read_unlock(); + if (!error) return NF_ACCEPT; return NF_DROP_ERR(-ECONNREFUSED); diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 2da554cc3a35..7382069efd7d 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -292,7 +292,7 @@ static int aa_label_sk_perm(const struct cred *subj_cred, AA_BUG(!label); AA_BUG(!sk); - if (ctx->label != kernel_t && !unconfined(label)) { + if (rcu_access_pointer(ctx->label) != kernel_t && !unconfined(label)) { struct aa_profile *profile; DEFINE_AUDIT_SK(ad, op, subj_cred, sk); From c5bf96d20fd787e4909b755de4705d52f3458836 Mon Sep 17 00:00:00 2001 From: Gabriel Totev Date: Wed, 16 Apr 2025 18:42:08 -0400 Subject: [PATCH 48/60] apparmor: shift ouid when mediating hard links in userns When using AppArmor profiles inside an unprivileged container, the link operation observes an unshifted ouid. (tested with LXD and Incus) For example, root inside container and uid 1000000 outside, with `owner /root/link l,` profile entry for ln: /root$ touch chain && ln chain link ==> dmesg apparmor="DENIED" operation="link" class="file" namespace="root//lxd-feet_" profile="linkit" name="/root/link" pid=1655 comm="ln" requested_mask="l" denied_mask="l" fsuid=1000000 ouid=0 [<== should be 1000000] target="/root/chain" Fix by mapping inode uid of old_dentry in aa_path_link() rather than using it directly, similarly to how it's mapped in __file_path_perm() later in the file. Signed-off-by: Gabriel Totev Signed-off-by: John Johansen --- security/apparmor/file.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 65e1d29af792..5504059d6101 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -430,9 +430,11 @@ int aa_path_link(const struct cred *subj_cred, { struct path link = { .mnt = new_dir->mnt, .dentry = new_dentry }; struct path target = { .mnt = new_dir->mnt, .dentry = old_dentry }; + struct inode *inode = d_backing_inode(old_dentry); + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(target.mnt), inode); struct path_cond cond = { - d_backing_inode(old_dentry)->i_uid, - d_backing_inode(old_dentry)->i_mode + .uid = vfsuid_into_kuid(vfsuid), + .mode = inode->i_mode, }; char *buffer = NULL, *buffer2 = NULL; struct aa_profile *profile; From 3fa0af4cc8a31d4139ee85a7b0e3d9b4f37b3093 Mon Sep 17 00:00:00 2001 From: Gabriel Totev Date: Wed, 16 Apr 2025 18:42:09 -0400 Subject: [PATCH 49/60] apparmor: shift uid when mediating af_unix in userns Avoid unshifted ouids for socket file operations as observed when using AppArmor profiles in unprivileged containers with LXD or Incus. For example, root inside container and uid 1000000 outside, with `owner /root/sock rw,` profile entry for nc: /root$ nc -lkU sock & nc -U sock ==> dmesg apparmor="DENIED" operation="connect" class="file" namespace="root//lxd-podia_" profile="sockit" name="/root/sock" pid=3924 comm="nc" requested_mask="wr" denied_mask="wr" fsuid=1000000 ouid=0 [<== should be 1000000] Fix by performing uid mapping as per common_perm_cond() in lsm.c Signed-off-by: Gabriel Totev Fixes: c05e705812d1 ("apparmor: add fine grained af_unix mediation") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 257648a13bf8..c4e722605fcd 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -12,6 +12,7 @@ * License. */ +#include #include #include "include/audit.h" @@ -44,8 +45,11 @@ static int unix_fs_perm(const char *op, u32 mask, const struct cred *subj_cred, */ if (path->dentry) { /* the sunpath may not be valid for this ns so use the path */ - struct path_cond cond = { path->dentry->d_inode->i_uid, - path->dentry->d_inode->i_mode + struct inode *inode = path->dentry->d_inode; + vfsuid_t vfsuid = i_uid_into_vfsuid(mnt_idmap(path->mnt), inode); + struct path_cond cond = { + .uid = vfsuid_into_kuid(vfsuid), + .mode = inode->i_mode, }; return aa_path_perm(op, subj_cred, label, path, From c567de2c4f5fe6e079672e074e1bc6122bf7e444 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 31 May 2025 17:08:21 +0200 Subject: [PATCH 50/60] apparmor: Fix 8-byte alignment for initial dfa blob streams The dfa blob stream for the aa_dfa_unpack() function is expected to be aligned on a 8 byte boundary. The static nulldfa_src[] and stacksplitdfa_src[] arrays store the initial apparmor dfa blob streams, but since they are declared as an array-of-chars the compiler and linker will only ensure a "char" (1-byte) alignment. Add an __aligned(8) annotation to the arrays to tell the linker to always align them on a 8-byte boundary. This avoids runtime warnings at startup on alignment-sensitive platforms like parisc such as: Kernel: unaligned access to 0x7f2a584a in aa_dfa_unpack+0x124/0x788 (iir 0xca0109f) Kernel: unaligned access to 0x7f2a584e in aa_dfa_unpack+0x210/0x788 (iir 0xca8109c) Kernel: unaligned access to 0x7f2a586a in aa_dfa_unpack+0x278/0x788 (iir 0xcb01090) Signed-off-by: Helge Deller Cc: stable@vger.kernel.org Fixes: 98b824ff8984 ("apparmor: refcount the pdb") Signed-off-by: John Johansen --- security/apparmor/lsm.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 0640a379a518..d3da9db244b0 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -2404,12 +2404,12 @@ static int __init apparmor_nf_ip_init(void) __initcall(apparmor_nf_ip_init); #endif -static char nulldfa_src[] = { +static char nulldfa_src[] __aligned(8) = { #include "nulldfa.in" }; static struct aa_dfa *nulldfa; -static char stacksplitdfa_src[] = { +static char stacksplitdfa_src[] __aligned(8) = { #include "stacksplitdfa.in" }; struct aa_dfa *stacksplitdfa; From c68804199dd9d63868497a27b5da3c3cd15356db Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Sat, 31 May 2025 17:08:22 +0200 Subject: [PATCH 51/60] apparmor: Fix unaligned memory accesses in KUnit test The testcase triggers some unnecessary unaligned memory accesses on the parisc architecture: Kernel: unaligned access to 0x12f28e27 in policy_unpack_test_init+0x180/0x374 (iir 0x0cdc1280) Kernel: unaligned access to 0x12f28e67 in policy_unpack_test_init+0x270/0x374 (iir 0x64dc00ce) Use the existing helper functions put_unaligned_le32() and put_unaligned_le16() to avoid such warnings on architectures which prefer aligned memory accesses. Signed-off-by: Helge Deller Fixes: 98c0cc48e27e ("apparmor: fix policy_unpack_test on big endian systems") Signed-off-by: John Johansen --- security/apparmor/policy_unpack_test.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/apparmor/policy_unpack_test.c b/security/apparmor/policy_unpack_test.c index f070902da8fc..a7ac0ccc6cfe 100644 --- a/security/apparmor/policy_unpack_test.c +++ b/security/apparmor/policy_unpack_test.c @@ -9,6 +9,8 @@ #include "include/policy.h" #include "include/policy_unpack.h" +#include + #define TEST_STRING_NAME "TEST_STRING" #define TEST_STRING_DATA "testing" #define TEST_STRING_BUF_OFFSET \ @@ -80,7 +82,7 @@ static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf, *(buf + 1) = strlen(TEST_U32_NAME) + 1; strscpy(buf + 3, TEST_U32_NAME, e->end - (void *)(buf + 3)); *(buf + 3 + strlen(TEST_U32_NAME) + 1) = AA_U32; - *((__le32 *)(buf + 3 + strlen(TEST_U32_NAME) + 2)) = cpu_to_le32(TEST_U32_DATA); + put_unaligned_le32(TEST_U32_DATA, buf + 3 + strlen(TEST_U32_NAME) + 2); buf = e->start + TEST_NAMED_U64_BUF_OFFSET; *buf = AA_NAME; @@ -103,7 +105,7 @@ static struct aa_ext *build_aa_ext_struct(struct policy_unpack_fixture *puf, *(buf + 1) = strlen(TEST_ARRAY_NAME) + 1; strscpy(buf + 3, TEST_ARRAY_NAME, e->end - (void *)(buf + 3)); *(buf + 3 + strlen(TEST_ARRAY_NAME) + 1) = AA_ARRAY; - *((__le16 *)(buf + 3 + strlen(TEST_ARRAY_NAME) + 2)) = cpu_to_le16(TEST_ARRAY_SIZE); + put_unaligned_le16(TEST_ARRAY_SIZE, buf + 3 + strlen(TEST_ARRAY_NAME) + 2); return e; } From da0edababafa444e638a0be6dd2feef0a9e529e2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 20 Jun 2025 15:05:01 -0700 Subject: [PATCH 52/60] apparmor: fix kernel doc warnings for kernel test robot Fix kernel doc warnings for the functions - apparmor_socket_bind - apparmor_unix_may_send - apparmor_unix_stream_connect - val_mask_to_str Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202506070127.B1bc3da4-lkp@intel.com/ Signed-off-by: John Johansen --- security/apparmor/lib.c | 4 ++-- security/apparmor/lsm.c | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index f51e79cc36d4..7d43f6a62404 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -84,8 +84,8 @@ int aa_parse_debug_params(const char *str) /** * val_mask_to_str - convert a perm mask to its short string * @str: character buffer to store string in (at least 10 characters) - * @str_size: size of the @str buffer - * @chrs: NUL-terminated character buffer of permission characters + * @size: size of the @str buffer + * @table: NUL-terminated character buffer of permission characters * @mask: permission mask to convert */ static int val_mask_to_str(char *str, size_t size, diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index d3da9db244b0..09fe237e5324 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1182,7 +1182,9 @@ static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, /** * apparmor_unix_stream_connect - check perms before making unix domain conn - * + * @sk: sk attempting to connect + * @peer_sk: sk that is accepting the connection + * @newsk: new sk created for this connection * peer is locked when this hook is called */ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, @@ -1216,9 +1218,10 @@ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, /** * apparmor_unix_may_send - check perms before conn or sending unix dgrams + * @sock: socket sending the message + * @peer: socket message is being send to * * sock and peer are locked when this hook is called - * * called by: dgram_connect peer setup but path not copied to newsk */ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) @@ -1336,6 +1339,9 @@ static int apparmor_socket_socketpair(struct socket *socka, /** * apparmor_socket_bind - check perms before bind addr to socket + * @sock: socket to bind the address to + * @address: address that is being bound + * @addrlen: length of @address */ static int apparmor_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) From 4ce7d3cf5ad846a8843f8afc78de2a8309f74f12 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Mon, 23 Jun 2025 14:58:00 -0700 Subject: [PATCH 53/60] apparmor: remove redundant perms.allow MAY_EXEC bitflag set This section of profile_transition that occurs after x_to_label only happens if perms.allow already has the MAY_EXEC bit set, so we don't need to set it again. Fixes: 16916b17b4f8 ("apparmor: force auditing of conflicting attachment execs from confined") Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index f9370a63a83c..d689597f253b 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -734,10 +734,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, * we don't need to care about clobbering it */ if (info == CONFLICTING_ATTACH_STR_IX - || info == CONFLICTING_ATTACH_STR_UX) { + || info == CONFLICTING_ATTACH_STR_UX) perms.audit |= MAY_EXEC; - perms.allow |= MAY_EXEC; - } /* hack ix fallback - improve how this is detected */ goto audit; } else if (!new) { From f9c9dce01e9640d94a37304bddc97b738ee4ac35 Mon Sep 17 00:00:00 2001 From: Peng Jiang Date: Mon, 23 Jun 2025 14:41:11 +0800 Subject: [PATCH 54/60] apparmor: fix documentation mismatches in val_mask_to_str and socket functions This patch fixes kernel-doc warnings: 1. val_mask_to_str: - Added missing descriptions for `size` and `table` parameters. - Removed outdated str_size and chrs references. 2. Socket Functions: - Makes non-null requirements clear for socket/address args. - Standardizes return values per kernel conventions. - Adds Unix domain socket protocol details. These changes silence doc validation warnings and improve accuracy for AppArmor LSM docs. Signed-off-by: Peng Jiang Signed-off-by: John Johansen --- security/apparmor/lib.c | 2 +- security/apparmor/lsm.c | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c index 7d43f6a62404..82dbb97ad406 100644 --- a/security/apparmor/lib.c +++ b/security/apparmor/lib.c @@ -85,7 +85,7 @@ int aa_parse_debug_params(const char *str) * val_mask_to_str - convert a perm mask to its short string * @str: character buffer to store string in (at least 10 characters) * @size: size of the @str buffer - * @table: NUL-terminated character buffer of permission characters + * @table: NUL-terminated character buffer of permission characters (NOT NULL) * @mask: permission mask to convert */ static int val_mask_to_str(char *str, size_t size, diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 09fe237e5324..97f0f25a3cfa 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1186,6 +1186,10 @@ static void unix_connect_peers(struct aa_sk_ctx *sk_ctx, * @peer_sk: sk that is accepting the connection * @newsk: new sk created for this connection * peer is locked when this hook is called + * + * Return: + * 0 if connection is permitted + * error code on denial or failure */ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, struct sock *newsk) @@ -1221,8 +1225,16 @@ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, * @sock: socket sending the message * @peer: socket message is being send to * + * Performs bidirectional permission checks for Unix domain socket communication: + * 1. Verifies sender has AA_MAY_SEND to target socket + * 2. Verifies receiver has AA_MAY_RECEIVE from source socket + * * sock and peer are locked when this hook is called * called by: dgram_connect peer setup but path not copied to newsk + * + * Return: + * 0 if transmission is permitted + * error code on denial or failure */ static int apparmor_unix_may_send(struct socket *sock, struct socket *peer) { @@ -1339,9 +1351,17 @@ static int apparmor_socket_socketpair(struct socket *socka, /** * apparmor_socket_bind - check perms before bind addr to socket - * @sock: socket to bind the address to - * @address: address that is being bound + * @sock: socket to bind the address to (must be non-NULL) + * @address: address that is being bound (must be non-NULL) * @addrlen: length of @address + * + * Performs security checks before allowing a socket to bind to an address. + * Handles Unix domain sockets specially through aa_unix_bind_perm(). + * For other socket families, uses generic permission check via aa_sk_perm(). + * + * Return: + * 0 if binding is permitted + * error code on denial or invalid parameters */ static int apparmor_socket_bind(struct socket *sock, struct sockaddr *address, int addrlen) From 9afdc6abb007d5a86f54e9f10870ac1468155ca5 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 17 Feb 2025 01:46:37 -0800 Subject: [PATCH 55/60] apparmor: transition from a list of rules to a vector of rules The set of rules on a profile is not dynamically extended, instead if a new ruleset is needed a new version of the profile is created. This allows us to use a vector of rules instead of a list, slightly reducing memory usage and simplifying the code. Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 22 +++++---------- security/apparmor/apparmorfs.c | 3 +- security/apparmor/capability.c | 9 ++---- security/apparmor/domain.c | 23 +++++---------- security/apparmor/file.c | 6 ++-- security/apparmor/include/label.h | 20 +++++++++++-- security/apparmor/include/policy.h | 16 ++--------- security/apparmor/ipc.c | 3 +- security/apparmor/lsm.c | 5 ++-- security/apparmor/mount.c | 12 +++----- security/apparmor/net.c | 6 ++-- security/apparmor/policy.c | 45 +++++++++++++++++------------- security/apparmor/policy_unpack.c | 6 ++-- security/apparmor/resource.c | 11 ++------ security/apparmor/task.c | 11 +++----- 15 files changed, 85 insertions(+), 113 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index c4e722605fcd..9129766d1e9c 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -202,8 +202,7 @@ static int profile_create_perm(struct aa_profile *profile, int family, int type, int protocol, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; AA_BUG(!profile); @@ -227,9 +226,7 @@ static int profile_sk_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, u32 request, struct sock *sk, struct path *path) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; @@ -257,8 +254,7 @@ static int profile_sk_perm(struct aa_profile *profile, static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; @@ -289,8 +285,7 @@ static int profile_bind_perm(struct aa_profile *profile, struct sock *sk, static int profile_listen_perm(struct aa_profile *profile, struct sock *sk, int backlog, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; @@ -327,8 +322,7 @@ static int profile_accept_perm(struct aa_profile *profile, struct sock *sk, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; @@ -358,8 +352,7 @@ static int profile_opt_perm(struct aa_profile *profile, u32 request, struct sock *sk, int optname, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; @@ -399,8 +392,7 @@ static int profile_peer_perm(struct aa_profile *profile, u32 request, struct aa_label *peer_label, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index ecf22251c228..5ae0089554a7 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -612,8 +612,7 @@ static const struct file_operations aa_fs_ns_revision_fops = { static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms, const char *match_str, size_t match_len) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms tmp = { }; aa_state_t state = DFA_NOMATCH; diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c index 25b6219cdeb6..b9ea6bc45c1a 100644 --- a/security/apparmor/capability.c +++ b/security/apparmor/capability.c @@ -69,8 +69,7 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile { const u64 AUDIT_CACHE_TIMEOUT_NS = 1000*1000*1000; /* 1 second */ - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct audit_cache *ent; int type = AUDIT_APPARMOR_AUTO; @@ -122,8 +121,7 @@ static int audit_caps(struct apparmor_audit_data *ad, struct aa_profile *profile static int profile_capable(struct aa_profile *profile, int cap, unsigned int opts, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; int error; @@ -195,8 +193,7 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label, kernel_cap_t aa_profile_capget(struct aa_profile *profile) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; state = RULE_MEDIATES(rules, AA_CLASS_CAP); diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index d689597f253b..267da82afb14 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -93,8 +93,7 @@ static inline aa_state_t match_component(struct aa_profile *profile, struct aa_profile *tp, bool stack, aa_state_t state) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *ns_name; if (stack) @@ -131,8 +130,7 @@ static int label_compound_match(struct aa_profile *profile, aa_state_t state, bool subns, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct path_cond cond = { }; @@ -194,8 +192,7 @@ static int label_components_match(struct aa_profile *profile, aa_state_t start, bool subns, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_profile *tp; struct label_it i; struct aa_perms tmp; @@ -520,8 +517,7 @@ static const char *next_name(int xtype, const char *name) struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex, const char **name) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_label *label = NULL; u32 xtype = xindex & AA_X_TYPE_MASK; int index = xindex & AA_X_INDEX_MASK; @@ -575,8 +571,6 @@ static struct aa_label *x_to_label(struct aa_profile *profile, const char **lookupname, const char **info) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); struct aa_label *new = NULL; struct aa_label *stack = NULL; struct aa_ns *ns = profile->ns; @@ -668,8 +662,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred, char *buffer, struct path_cond *cond, bool *secure_exec) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_label *new = NULL; struct aa_profile *new_profile = NULL; const char *info = NULL, *name = NULL, *target = NULL; @@ -802,8 +795,7 @@ static int profile_onexec(const struct cred *subj_cred, char *buffer, struct path_cond *cond, bool *secure_exec) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state = rules->file->start[AA_CLASS_FILE]; struct aa_perms perms = {}; const char *xname = NULL, *info = "change_profile onexec"; @@ -1361,8 +1353,7 @@ static int change_profile_perms_wrapper(const char *op, const char *name, struct aa_label *target, bool stack, u32 request, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *info = NULL; int error = 0; diff --git a/security/apparmor/file.c b/security/apparmor/file.c index 5504059d6101..deffd278d6fd 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -223,8 +223,7 @@ int __aa_path_perm(const char *op, const struct cred *subj_cred, u32 request, struct path_cond *cond, int flags, struct aa_perms *perms) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int e = 0; if (profile_unconfined(profile) || @@ -323,8 +322,7 @@ static int profile_path_link(const struct cred *subj_cred, const struct path *target, char *buffer2, struct path_cond *cond) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *lname, *tname = NULL; struct aa_perms lperms = {}, perms; const char *info = NULL; diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 9aa2e364cca9..c0812dbc1b5b 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -19,6 +19,7 @@ #include "lib.h" struct aa_ns; +struct aa_ruleset; #define LOCAL_VEC_ENTRIES 8 #define DEFINE_VEC(T, V) \ @@ -109,7 +110,7 @@ struct label_it { int i, j; }; -/* struct aa_label - lazy labeling struct +/* struct aa_label_base - base info of label * @count: ref count of active users * @node: rbtree position * @rcu: rcu callback struct @@ -118,7 +119,10 @@ struct label_it { * @flags: stale and other flags - values may change under label set lock * @secid: secid that references this label * @size: number of entries in @ent[] - * @ent: set of profiles for label, actual size determined by @size + * @mediates: bitmask for label_mediates + * profile: label vec when embedded in a profile FLAG_PROFILE is set + * rules: variable length rules in a profile FLAG_PROFILE is set + * vec: vector of profiles comprising the compound label */ struct aa_label { struct kref count; @@ -130,7 +134,17 @@ struct aa_label { u32 secid; int size; u64 mediates; - struct aa_profile *vec[]; + union { + struct { + /* only used is the label is a profile, size of + * rules[] is determined by the profile + * profile[1] is poison or null as guard + */ + struct aa_profile *profile[2]; + DECLARE_FLEX_ARRAY(struct aa_ruleset *, rules); + }; + DECLARE_FLEX_ARRAY(struct aa_profile *, vec); + }; }; #define last_error(E, FN) \ diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h index a4c0f76fd03d..4c50875c9d13 100644 --- a/security/apparmor/include/policy.h +++ b/security/apparmor/include/policy.h @@ -165,8 +165,6 @@ struct aa_data { * @secmark: secmark label match info */ struct aa_ruleset { - struct list_head list; - int size; /* TODO: merge policy and file */ @@ -180,6 +178,7 @@ struct aa_ruleset { struct aa_secmark *secmark; }; + /* struct aa_attachment - data and rules for a profiles attachment * @list: * @xmatch_str: human readable attachment string @@ -218,6 +217,7 @@ struct aa_attachment { * @dents: set of dentries associated with the profile * @data: hashtable for free-form policy aa_data * @label - label this profile is an extension of + * @rules - label with the rule vec on its end * * The AppArmor profile contains the basic confinement data. Each profile * has a name, and exists in a namespace. The @name and @exec_match are @@ -245,7 +245,6 @@ struct aa_profile { const char *disconnected; struct aa_attachment attach; - struct list_head rules; struct aa_loaddata *rawdata; unsigned char *hash; @@ -253,6 +252,7 @@ struct aa_profile { struct dentry *dents[AAFS_PROF_SIZEOF]; struct rhashtable *data; + int n_rules; /* special - variable length must be last entry in profile */ struct aa_label label; }; @@ -332,16 +332,6 @@ static inline aa_state_t RULE_MEDIATES_NET(struct aa_ruleset *rules) } -static inline aa_state_t ANY_RULE_MEDIATES(struct list_head *head, - unsigned char class) -{ - struct aa_ruleset *rule; - - /* TODO: change to list walk */ - rule = list_first_entry(head, typeof(*rule), list); - return RULE_MEDIATES(rule, class); -} - void aa_compute_profile_mediates(struct aa_profile *profile); static inline bool profile_mediates(struct aa_profile *profile, unsigned char class) diff --git a/security/apparmor/ipc.c b/security/apparmor/ipc.c index 3566d875645e..df5712cea685 100644 --- a/security/apparmor/ipc.c +++ b/security/apparmor/ipc.c @@ -80,8 +80,7 @@ static int profile_signal_perm(const struct cred *cred, struct aa_label *peer, u32 request, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms; aa_state_t state; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 97f0f25a3cfa..cecbb985928f 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -182,8 +182,7 @@ static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effec struct aa_ruleset *rules; kernel_cap_t allowed; - rules = list_first_entry(&profile->rules, - typeof(*rules), list); + rules = profile->label.rules[0]; allowed = aa_profile_capget(profile); *effective = cap_intersect(*effective, allowed); *permitted = cap_intersect(*permitted, allowed); @@ -636,7 +635,7 @@ static int profile_uring(struct aa_profile *profile, u32 request, AA_BUG(!profile); - rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules = profile->label.rules[0]; state = RULE_MEDIATES(rules, AA_CLASS_IO_URING); if (state) { struct aa_perms perms = { }; diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index bf8863253e07..523570aa1a5a 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -311,8 +311,7 @@ static int match_mnt_path_str(const struct cred *subj_cred, { struct aa_perms perms = { }; const char *mntpnt = NULL, *info = NULL; - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int pos, error; AA_BUG(!profile); @@ -371,8 +370,7 @@ static int match_mnt(const struct cred *subj_cred, bool binary) { const char *devname = NULL, *info = NULL; - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int error = -EACCES; AA_BUG(!profile); @@ -604,8 +602,7 @@ static int profile_umount(const struct cred *subj_cred, struct aa_profile *profile, const struct path *path, char *buffer) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms = { }; const char *name = NULL, *info = NULL; aa_state_t state; @@ -668,8 +665,7 @@ static struct aa_label *build_pivotroot(const struct cred *subj_cred, const struct path *old_path, char *old_buffer) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; const char *old_name, *new_name = NULL, *info = NULL; const char *trans_name = NULL; struct aa_perms perms = { }; diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 7382069efd7d..45cf25605c34 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -251,8 +251,7 @@ int aa_profile_af_perm(struct aa_profile *profile, struct apparmor_audit_data *ad, u32 request, u16 family, int type, int protocol) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms *p = NULL; aa_state_t state; @@ -362,8 +361,7 @@ static int aa_secmark_perm(struct aa_profile *profile, u32 request, u32 secid, { int i, ret; struct aa_perms perms = { }; - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; if (rules->secmark_count == 0) return 0; diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index a60bb7d9b583..261a9d3a0afe 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -259,8 +259,6 @@ struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) struct aa_ruleset *rules; rules = kzalloc(sizeof(*rules), gfp); - if (rules) - INIT_LIST_HEAD(&rules->list); return rules; } @@ -277,7 +275,6 @@ struct aa_ruleset *aa_alloc_ruleset(gfp_t gfp) */ void aa_free_profile(struct aa_profile *profile) { - struct aa_ruleset *rule, *tmp; struct rhashtable *rht; AA_DEBUG(DEBUG_POLICY, "%s(%p)\n", __func__, profile); @@ -299,10 +296,9 @@ void aa_free_profile(struct aa_profile *profile) * at this point there are no tasks that can have a reference * to rules */ - list_for_each_entry_safe(rule, tmp, &profile->rules, list) { - list_del_init(&rule->list); - free_ruleset(rule); - } + for (int i = 0; i < profile->n_rules; i++) + free_ruleset(profile->label.rules[i]); + kfree_sensitive(profile->dirname); if (profile->data) { @@ -331,25 +327,25 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, gfp_t gfp) { struct aa_profile *profile; - struct aa_ruleset *rules; - /* freed by free_profile - usually through aa_put_profile */ - profile = kzalloc(struct_size(profile, label.vec, 2), gfp); + /* freed by free_profile - usually through aa_put_profile + * this adds space for a single ruleset in the rules section of the + * label + */ + profile = kzalloc(struct_size(profile, label.rules, 1), gfp); if (!profile) return NULL; + profile->n_rules = 1; if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; if (!aa_label_init(&profile->label, 1, gfp)) goto fail; - INIT_LIST_HEAD(&profile->rules); - /* allocate the first ruleset, but leave it empty */ - rules = aa_alloc_ruleset(gfp); - if (!rules) + profile->label.rules[0] = aa_alloc_ruleset(gfp); + if (!profile->label.rules[0]) goto fail; - list_add(&rules->list, &profile->rules); /* update being set needed by fs interface */ if (!proxy) { @@ -374,6 +370,18 @@ fail: return NULL; } +static inline bool ANY_RULE_MEDIATES(struct aa_profile *profile, + unsigned char class) +{ + int i; + + for (i = 0; i < profile->n_rules; i++) { + if (RULE_MEDIATES(profile->label.rules[i], class)) + return true; + } + return false; +} + /* set of rules that are mediated by unconfined */ static int unconfined_mediates[] = { AA_CLASS_NS, AA_CLASS_IO_URING, 0 }; @@ -386,14 +394,13 @@ void aa_compute_profile_mediates(struct aa_profile *profile) int *pos; for (pos = unconfined_mediates; *pos; pos++) { - if (ANY_RULE_MEDIATES(&profile->rules, AA_CLASS_NS) != - DFA_NOMATCH) + if (ANY_RULE_MEDIATES(profile, *pos)) profile->label.mediates |= ((u64) 1) << AA_CLASS_NS; } return; } for (c = 0; c <= AA_CLASS_LAST; c++) { - if (ANY_RULE_MEDIATES(&profile->rules, c) != DFA_NOMATCH) + if (ANY_RULE_MEDIATES(profile, c)) profile->label.mediates |= ((u64) 1) << c; } } @@ -646,7 +653,7 @@ struct aa_profile *aa_alloc_null(struct aa_profile *parent, const char *name, /* TODO: ideally we should inherit abi from parent */ profile->label.flags |= FLAG_NULL; profile->attach.xmatch = aa_get_pdb(nullpdb); - rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules = profile->label.rules[0]; rules->file = aa_get_pdb(nullpdb); rules->policy = aa_get_pdb(nullpdb); aa_compute_profile_mediates(profile); diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 58c106b63727..553e52df3aa9 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -885,7 +885,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name) error = -ENOMEM; goto fail; } - rules = list_first_entry(&profile->rules, typeof(*rules), list); + rules = profile->label.rules[0]; /* profile renaming is optional */ (void) aa_unpack_str(e, &profile->rename, "rename"); @@ -1285,8 +1285,8 @@ static bool verify_perms(struct aa_policydb *pdb) */ static int verify_profile(struct aa_profile *profile) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; + if (!rules) return 0; diff --git a/security/apparmor/resource.c b/security/apparmor/resource.c index dcc94c3153d5..8e80db3ae21c 100644 --- a/security/apparmor/resource.c +++ b/security/apparmor/resource.c @@ -89,8 +89,7 @@ static int profile_setrlimit(const struct cred *subj_cred, struct aa_profile *profile, unsigned int resource, struct rlimit *new_rlim) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; int e = 0; if (rules->rlimits.mask & (1 << resource) && new_rlim->rlim_max > @@ -165,9 +164,7 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) * to the lesser of the tasks hard limit and the init tasks soft limit */ label_for_each_confined(i, old_l, old) { - struct aa_ruleset *rules = list_first_entry(&old->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = old->label.rules[0]; if (rules->rlimits.mask) { int j; @@ -185,9 +182,7 @@ void __aa_transition_rlimits(struct aa_label *old_l, struct aa_label *new_l) /* set any new hard limits as dictated by the new profile */ label_for_each_confined(i, new_l, new) { - struct aa_ruleset *rules = list_first_entry(&new->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = new->label.rules[0]; int j; if (!rules->rlimits.mask) diff --git a/security/apparmor/task.c b/security/apparmor/task.c index c87fb9f4ac18..c9bc9cc69475 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -228,8 +228,7 @@ static int profile_ptrace_perm(const struct cred *cred, struct aa_label *peer, u32 request, struct apparmor_audit_data *ad) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), list); + struct aa_ruleset *rules = profile->label.rules[0]; struct aa_perms perms = { }; ad->subj_cred = cred; @@ -246,7 +245,7 @@ static int profile_tracee_perm(const struct cred *cred, struct apparmor_audit_data *ad) { if (profile_unconfined(tracee) || unconfined(tracer) || - !ANY_RULE_MEDIATES(&tracee->rules, AA_CLASS_PTRACE)) + !label_mediates(&tracee->label, AA_CLASS_PTRACE)) return 0; return profile_ptrace_perm(cred, tracee, tracer, request, ad); @@ -260,7 +259,7 @@ static int profile_tracer_perm(const struct cred *cred, if (profile_unconfined(tracer)) return 0; - if (ANY_RULE_MEDIATES(&tracer->rules, AA_CLASS_PTRACE)) + if (label_mediates(&tracer->label, AA_CLASS_PTRACE)) return profile_ptrace_perm(cred, tracer, tracee, request, ad); /* profile uses the old style capability check for ptrace */ @@ -324,9 +323,7 @@ int aa_profile_ns_perm(struct aa_profile *profile, ad->request = request; if (!profile_unconfined(profile)) { - struct aa_ruleset *rules = list_first_entry(&profile->rules, - typeof(*rules), - list); + struct aa_ruleset *rules = profile->label.rules[0]; aa_state_t state; state = RULE_MEDIATES(rules, ad->class); From 4d9d1a08b796efd54f1e29b42bd95879109fe448 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 22 May 2025 13:54:05 -0700 Subject: [PATCH 56/60] apparmor: fix: accept2 being specifie even when permission table is presnt The transition to the perms32 permission table dropped the need for the accept2 table as permissions. However accept2 can be used for flags and may be present even when the perms32 table is present. So instead of checking on version, check whether the table is present. Fixes: 2e12c5f06017 ("apparmor: add additional flags to extended permission.") Signed-off-by: John Johansen --- security/apparmor/policy_unpack.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c index 553e52df3aa9..7523971e37d9 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -775,7 +775,8 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, } } - if (pdb->perms && version <= 2) { + /* accept2 is in some cases being allocated, even with perms */ + if (pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) { /* add dfa flags table missing in v2 */ u32 noents = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_lolen; u16 tdflags = pdb->dfa->tables[YYTD_ID_ACCEPT]->td_flags; From 8936125e232803e64cb29e107326a942981188d6 Mon Sep 17 00:00:00 2001 From: Jiapeng Chong Date: Fri, 25 Jul 2025 17:52:52 +0800 Subject: [PATCH 57/60] apparmor: Remove the unused variable rules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Variable rules is not effectively used, so delete it. security/apparmor/lsm.c:182:23: warning: variable ‘rules’ set but not used. Reported-by: Abaci Robot Closes: https://bugzilla.openanolis.cn/show_bug.cgi?id=22942 Signed-off-by: Jiapeng Chong Signed-off-by: John Johansen --- security/apparmor/lsm.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index cecbb985928f..9a64b2db0267 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -179,10 +179,8 @@ static int apparmor_capget(const struct task_struct *target, kernel_cap_t *effec struct label_it i; label_for_each_confined(i, label, profile) { - struct aa_ruleset *rules; kernel_cap_t allowed; - rules = profile->label.rules[0]; allowed = aa_profile_capget(profile); *effective = cap_intersect(*effective, allowed); *permitted = cap_intersect(*permitted, allowed); From f3c0675bb9e0a3a472dd519ec7ccde23bdcf180b Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 30 Jul 2025 03:08:29 -0700 Subject: [PATCH 58/60] apparmor: fix test error: WARNING in apparmor_unix_stream_connect commit 88fec3526e84 ("apparmor: make sure unix socket labeling is correctly updated.") added the use of security_sk_alloc() which ensures the sk label is initialized. This means that the AA_BUG in apparmor_unix_stream_connect() is no longer correct, because while the sk is still not being initialized by going through post_create, it is now initialize in sk_alloc(). Remove the now invalid check. Reported-by: syzbot+cd38ee04bcb3866b0c6d@syzkaller.appspotmail.com Fixes: 88fec3526e84 ("apparmor: make sure unix socket labeling is correctly updated.") Signed-off-by: John Johansen --- security/apparmor/lsm.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 9a64b2db0267..e4b2944431e4 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1205,8 +1205,9 @@ static int apparmor_unix_stream_connect(struct sock *sk, struct sock *peer_sk, if (error) return error; - /* newsk doesn't go through post_create */ - AA_BUG(rcu_access_pointer(new_ctx->label)); + /* newsk doesn't go through post_create, but does go through + * security_sk_alloc() + */ rcu_assign_pointer(new_ctx->label, aa_get_label(rcu_dereference_protected(peer_ctx->label, true))); From 43584e993293326cfc508e664fe81f56a65f6240 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 30 Jul 2025 03:47:07 -0700 Subject: [PATCH 59/60] apparmor: fix Regression on linux-next (next-20250721) sk lock initialization was incorrectly removed, from apparmor_file_alloc_security() while testing changes to changes to apparmor_sk_alloc_security() resulting in the following regression. [ 48.056654] INFO: trying to register non-static key. [ 48.057480] The code is fine but needs lockdep annotation, or maybe [ 48.058416] you didn't initialize this object before use? [ 48.059209] turning off the locking correctness validator. [ 48.060040] CPU: 0 UID: 0 PID: 648 Comm: chronyd Not tainted 6.16.0-rc7-test-next-20250721-11410-g1ee809985e11-dirty #577 NONE [ 48.060049] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 48.060055] Call Trace: [ 48.060059] [ 48.060063] dump_stack_lvl (lib/dump_stack.c:122) [ 48.060075] register_lock_class (kernel/locking/lockdep.c:988 kernel/locking/lockdep.c:1302) [ 48.060084] ? path_name (security/apparmor/file.c:159) [ 48.060093] __lock_acquire (kernel/locking/lockdep.c:5116) [ 48.060103] lock_acquire (kernel/locking/lockdep.c:473 (discriminator 4) kernel/locking/lockdep.c:5873 (discriminator 4) kernel/locking/lockdep.c:5828 (discriminator 4)) [ 48.060109] ? update_file_ctx (security/apparmor/file.c:464) [ 48.060115] ? __pfx_profile_path_perm (security/apparmor/file.c:247) [ 48.060121] _raw_spin_lock (include/linux/spinlock_api_smp.h:134 kernel/locking/spinlock.c:154) [ 48.060130] ? update_file_ctx (security/apparmor/file.c:464) [ 48.060134] update_file_ctx (security/apparmor/file.c:464) [ 48.060140] aa_file_perm (security/apparmor/file.c:532 (discriminator 1) security/apparmor/file.c:642 (discriminator 1)) [ 48.060147] ? __pfx_aa_file_perm (security/apparmor/file.c:607) [ 48.060152] ? do_mmap (mm/mmap.c:558) [ 48.060160] ? __pfx_userfaultfd_unmap_complete (fs/userfaultfd.c:841) [ 48.060170] ? __lock_acquire (kernel/locking/lockdep.c:4677 (discriminator 1) kernel/locking/lockdep.c:5194 (discriminator 1)) [ 48.060176] ? common_file_perm (security/apparmor/lsm.c:535 (discriminator 1)) [ 48.060185] security_mmap_file (security/security.c:3012 (discriminator 2)) [ 48.060192] vm_mmap_pgoff (mm/util.c:574 (discriminator 1)) [ 48.060200] ? find_held_lock (kernel/locking/lockdep.c:5353 (discriminator 1)) [ 48.060206] ? __pfx_vm_mmap_pgoff (mm/util.c:568) [ 48.060212] ? lock_release (kernel/locking/lockdep.c:5539 kernel/locking/lockdep.c:5892 kernel/locking/lockdep.c:5878) [ 48.060219] ? __fget_files (arch/x86/include/asm/preempt.h:85 (discriminator 13) include/linux/rcupdate.h:100 (discriminator 13) include/linux/rcupdate.h:873 (discriminator 13) fs/file.c:1072 (discriminator 13)) [ 48.060229] ksys_mmap_pgoff (mm/mmap.c:604) [ 48.060239] do_syscall_64 (arch/x86/entry/syscall_64.c:63 (discriminator 1) arch/x86/entry/syscall_64.c:94 (discriminator 1)) [ 48.060248] entry_SYSCALL_64_after_hwframe (arch/x86/entry/entry_64.S:130) [ 48.060254] RIP: 0033:0x7fb6920e30a2 [ 48.060265] Code: 08 00 04 00 00 eb e2 90 41 f7 c1 ff 0f 00 00 75 27 55 89 cd 53 48 89 fb 48 85 ff 74 33 41 89 ea 48 89 df b8 09 00 00 00 0f 05 <48> 3d 00 f0 ff ff 77 5e 5b 5d c3 0f 1f 00 c7 05 e6 41 01 00 16 00 All code ======== 0: 08 00 or %al,(%rax) 2: 04 00 add $0x0,%al 4: 00 eb add %ch,%bl 6: e2 90 loop 0xffffffffffffff98 8: 41 f7 c1 ff 0f 00 00 test $0xfff,%r9d f: 75 27 jne 0x38 11: 55 push %rbp 12: 89 cd mov %ecx,%ebp 14: 53 push %rbx 15: 48 89 fb mov %rdi,%rbx 18: 48 85 ff test %rdi,%rdi 1b: 74 33 je 0x50 1d: 41 89 ea mov %ebp,%r10d 20: 48 89 df mov %rbx,%rdi 23: b8 09 00 00 00 mov $0x9,%eax 28: 0f 05 syscall 2a:* 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax <-- trapping instruction 30: 77 5e ja 0x90 32: 5b pop %rbx 33: 5d pop %rbp 34: c3 ret 35: 0f 1f 00 nopl (%rax) 38: c7 .byte 0xc7 39: 05 e6 41 01 00 add $0x141e6,%eax 3e: 16 (bad) ... Code starting with the faulting instruction =========================================== 0: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax 6: 77 5e ja 0x66 8: 5b pop %rbx 9: 5d pop %rbp a: c3 ret b: 0f 1f 00 nopl (%rax) e: c7 .byte 0xc7 f: 05 e6 41 01 00 add $0x141e6,%eax 14: 16 (bad) ... [ 48.060270] RSP: 002b:00007ffd2c0d3528 EFLAGS: 00000206 ORIG_RAX: 0000000000000009 [ 48.060279] RAX: ffffffffffffffda RBX: 00007fb691fc8000 RCX: 00007fb6920e30a2 [ 48.060283] RDX: 0000000000000005 RSI: 000000000007d000 RDI: 00007fb691fc8000 [ 48.060287] RBP: 0000000000000812 R08: 0000000000000003 R09: 0000000000011000 [ 48.060290] R10: 0000000000000812 R11: 0000000000000206 R12: 00007ffd2c0d3578 [ 48.060293] R13: 00007fb6920b6160 R14: 00007ffd2c0d39f0 R15: 00000fffa581a6a8 Fixes: 88fec3526e84 ("apparmor: make sure unix socket labeling is correctly updated.") Signed-off-by: John Johansen --- security/apparmor/lsm.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index e4b2944431e4..f385913e7d0e 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -505,6 +505,7 @@ static int apparmor_file_alloc_security(struct file *file) struct aa_file_ctx *ctx = file_ctx(file); struct aa_label *label = begin_current_label_crit_section(); + spin_lock_init(&ctx->lock); rcu_assign_pointer(ctx->label, aa_get_label(label)); end_current_label_crit_section(label); return 0; From 5f49c2d1f422c660c726ac5e0499c66c901633c2 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 1 Aug 2025 20:36:06 -0700 Subject: [PATCH 60/60] apparmor: fix: oops when trying to free null ruleset profile allocation is wrongly setting the number of entries on the rules vector before any ruleset is assigned. If profile allocation fails between ruleset allocation and assigning the first ruleset, free_ruleset() will be called with a null pointer resulting in an oops. [ 107.350226] kernel BUG at mm/slub.c:545! [ 107.350912] Oops: invalid opcode: 0000 [#1] PREEMPT SMP NOPTI [ 107.351447] CPU: 1 UID: 0 PID: 27 Comm: ksoftirqd/1 Not tainted 6.14.6-hwe-rlee287-dev+ #5 [ 107.353279] Hardware name:[ 107.350218] -QE-----------[ cutMU here ]--------- Ub--- [ 107.3502untu26] kernel BUG a 24t mm/slub.c:545.!04 P [ 107.350912]C ( Oops: invalid oi4pcode: 0000 [#1]40 PREEMPT SMP NOPFXTI + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 107.356054] RIP: 0010:__slab_free+0x152/0x340 [ 107.356444] Code: 00 4c 89 ff e8 0f ac df 00 48 8b 14 24 48 8b 4c 24 20 48 89 44 24 08 48 8b 03 48 c1 e8 09 83 e0 01 88 44 24 13 e9 71 ff ff ff <0f> 0b 41 f7 44 24 08 87 04 00 00 75 b2 eb a8 41 f7 44 24 08 87 04 [ 107.357856] RSP: 0018:ffffad4a800fbbb0 EFLAGS: 00010246 [ 107.358937] RAX: ffff97ebc2a88e70 RBX: ffffd759400aa200 RCX: 0000000000800074 [ 107.359976] RDX: ffff97ebc2a88e60 RSI: ffffd759400aa200 RDI: ffffad4a800fbc20 [ 107.360600] RBP: ffffad4a800fbc50 R08: 0000000000000001 R09: ffffffff86f02cf2 [ 107.361254] R10: 0000000000000000 R11: 0000000000000000 R12: ffff97ecc0049400 [ 107.361934] R13: ffff97ebc2a88e60 R14: ffff97ecc0049400 R15: 0000000000000000 [ 107.362597] FS: 0000000000000000(0000) GS:ffff97ecfb200000(0000) knlGS:0000000000000000 [ 107.363332] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [ 107.363784] CR2: 000061c9545ac000 CR3: 0000000047aa6000 CR4: 0000000000750ef0 [ 107.364331] PKRU: 55555554 [ 107.364545] Call Trace: [ 107.364761] [ 107.364931] ? local_clock+0x15/0x30 [ 107.365219] ? srso_alias_return_thunk+0x5/0xfbef5 [ 107.365593] ? kfree_sensitive+0x32/0x70 [ 107.365900] kfree+0x29d/0x3a0 [ 107.366144] ? srso_alias_return_thunk+0x5/0xfbef5 [ 107.366510] ? local_clock_noinstr+0xe/0xd0 [ 107.366841] ? srso_alias_return_thunk+0x5/0xfbef5 [ 107.367209] kfree_sensitive+0x32/0x70 [ 107.367502] aa_free_profile.part.0+0xa2/0x400 [ 107.367850] ? rcu_do_batch+0x1e6/0x5e0 [ 107.368148] aa_free_profile+0x23/0x60 [ 107.368438] label_free_switch+0x4c/0x80 [ 107.368751] label_free_rcu+0x1c/0x50 [ 107.369038] rcu_do_batch+0x1e8/0x5e0 [ 107.369324] ? rcu_do_batch+0x157/0x5e0 [ 107.369626] rcu_core+0x1b0/0x2f0 [ 107.369888] rcu_core_si+0xe/0x20 [ 107.370156] handle_softirqs+0x9b/0x3d0 [ 107.370460] ? smpboot_thread_fn+0x26/0x210 [ 107.370790] run_ksoftirqd+0x3a/0x70 [ 107.371070] smpboot_thread_fn+0xf9/0x210 [ 107.371383] ? __pfx_smpboot_thread_fn+0x10/0x10 [ 107.371746] kthread+0x10d/0x280 [ 107.372010] ? __pfx_kthread+0x10/0x10 [ 107.372310] ret_from_fork+0x44/0x70 [ 107.372655] ? __pfx_kthread+0x10/0x10 [ 107.372974] ret_from_fork_asm+0x1a/0x30 [ 107.373316] [ 107.373505] Modules linked in: af_packet_diag mptcp_diag tcp_diag udp_diag raw_diag inet_diag snd_seq_dummy snd_hrtimer snd_seq_midi snd_seq_midi_event snd_rawmidi snd_seq snd_seq_device snd_timer snd soundcore qrtr binfmt_misc intel_rapl_msr intel_rapl_common kvm_amd ccp kvm irqbypass polyval_clmulni polyval_generic ghash_clmulni_intel sha256_ssse3 sha1_ssse3 aesni_intel crypto_simd cryptd i2c_piix4 i2c_smbus input_leds joydev sch_fq_codel msr parport_pc ppdev lp parport efi_pstore nfnetlink vsock_loopback vmw_vsock_virtio_transport_common vmw_vsock_vmci_transport vsock vmw_vmci dmi_sysfs qemu_fw_cfg ip_tables x_tables autofs4 hid_generic usbhid hid psmouse serio_raw floppy bochs pata_acpi [ 107.379086] ---[ end trace 0000000000000000 ]--- Don't set the count until a ruleset is actually allocated and guard against free_ruleset() being called with a null pointer. Reported-by: Ryan Lee Fixes: 217af7e2f4de ("apparmor: refactor profile rules and attachments") Signed-off-by: John Johansen --- security/apparmor/policy.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 261a9d3a0afe..50d5345ff5cb 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -243,6 +243,9 @@ static void free_ruleset(struct aa_ruleset *rules) { int i; + if (!rules) + return; + aa_put_pdb(rules->file); aa_put_pdb(rules->policy); aa_free_cap_rules(&rules->caps); @@ -335,7 +338,6 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, profile = kzalloc(struct_size(profile, label.rules, 1), gfp); if (!profile) return NULL; - profile->n_rules = 1; if (!aa_policy_init(&profile->base, NULL, hname, gfp)) goto fail; @@ -346,6 +348,7 @@ struct aa_profile *aa_alloc_profile(const char *hname, struct aa_proxy *proxy, profile->label.rules[0] = aa_alloc_ruleset(gfp); if (!profile->label.rules[0]) goto fail; + profile->n_rules = 1; /* update being set needed by fs interface */ if (!proxy) {