From c3382d1e69136ce0aa755fbe09512e03cdd95944 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2026 21:26:23 -0700 Subject: [PATCH 01/35] apparmor: add Georgia Garcia as co-maintainer of apparmor Georgia has agreed to help maintaining apparmor. Signed-off-by: John Johansen --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) diff --git a/MAINTAINERS b/MAINTAINERS index 2fb1c75afd16..f58c79c7ee4e 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1923,6 +1923,7 @@ F: include/uapi/linux/apm_bios.h APPARMOR SECURITY MODULE M: John Johansen M: John Johansen +M: Georgia Garcia L: apparmor@lists.ubuntu.com (moderated for non-subscribers) S: Supported W: apparmor.net From 4483efe4f21510b30c24bc97d9fd0e8feab94125 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 22 Oct 2025 23:46:19 -0700 Subject: [PATCH 02/35] apparmor: fix shadowing of plabel that prevents cache from being updated Unfortunately the plabel was being shadowed by an unused local var. This didn't affect the mediation check but did cauase the cache to not correctly be updated resulting in extra mediation checks. Fixes: 88fec3526e841 ("apparmor: make sure unix socket labeling is correctly updated.") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index fdb4a9f212c3..09ce3f6da20c 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -758,11 +758,10 @@ 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); + plabel = aa_get_newest_label(pctx->label); rcu_read_unlock(); /* no fs check of aa_unix_peer_perm because conditions above * ensure they will never be done From b1aea2c1960771a276d7e68c7424168eccd0c3da Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 24 Oct 2025 12:25:38 -0700 Subject: [PATCH 03/35] apparmor: fix race in unix socket mediation when peer_path is used The holding a reference to the peer_sk is not enough to ensure access to the peer sk path. Accessing the path outside of the state lock allows for a race with unix_release_sock(). Fix this by taking the state lock and getting a reference to the path under lock. Ideally for connected sockets we would cache this information so we don't have to take the lock here. But for now just fix the race. Fixes: bc6e5f6933b8e ("apparmor: Remove use of the double lock") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 58 ++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 09ce3f6da20c..23753cb2096e 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -748,41 +748,47 @@ int aa_unix_file_perm(const struct cred *subj_cred, struct aa_label *label, if (!peer_sk) goto out; - peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen); + if (!is_sk_fs) { + bool is_peer_fs = is_unix_fs(peer_sk); - struct path peer_path; + peer_addr = aa_sunaddr(unix_sk(peer_sk), &peer_addrlen); + if (is_peer_fs) { + 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, - is_unix_fs(peer_sk) ? &peer_path : NULL)); - } else if (!is_sk_fs) { - struct aa_sk_ctx *pctx = aa_sock(peer_sk); + unix_state_lock(peer_sk); + peer_path = unix_sk(peer_sk)->path; + if (peer_path.dentry) + path_get(&peer_path); + unix_state_unlock(peer_sk); - rcu_read_lock(); - plabel = aa_get_newest_label(pctx->label); - rcu_read_unlock(); - /* no fs check of aa_unix_peer_perm because conditions above - * ensure they will never be done - */ - last_error(error, - xcheck(unix_peer_perm(subj_cred, label, op, + last_error(error, + unix_fs_perm(op, request, subj_cred, label, + &peer_path)); + if (peer_path.dentry) + path_put(&peer_path); + } else { + struct aa_sk_ctx *pctx = aa_sock(peer_sk); + + rcu_read_lock(); + plabel = aa_get_newest_label(pctx->label); + rcu_read_unlock(); + /* no fs check of aa_unix_peer_perm because conditions + * above ensure they will never be done + */ + 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, - plabel), - unix_peer_perm(file->f_cred, plabel, op, + NULL, 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, + 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); + if (!error && !__aa_subj_label_is_cached(plabel, label)) + update_peer_ctx(peer_sk, pctx, label); + } } sock_put(peer_sk); From 6d25e7b47616cb2db43351210929c8f19dc305a3 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 24 Oct 2025 12:59:51 -0700 Subject: [PATCH 04/35] apparmor: fix refcount leak when updating the sk_ctx Currently update_sk_ctx() transfers the plabel reference, unfortunately it is also unconditionally put in the caller. Ideally we would make the caller conditionally put the reference based on whether it was transferred but for now just fix the bug by getting a reference. Fixes: 88fec3526e841 ("apparmor: make sure unix socket labeling is correctly updated.") Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 23753cb2096e..834a3b1c2f0a 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -674,9 +674,11 @@ static void update_sk_ctx(struct sock *sk, struct aa_label *label, old = rcu_dereference_protected(ctx->peer, lockdep_is_held(&unix_sk(sk)->lock)); if (old == plabel) { - rcu_assign_pointer(ctx->peer_lastupdate, plabel); + rcu_assign_pointer(ctx->peer_lastupdate, + aa_get_label(plabel)); } else if (aa_label_is_subset(plabel, old)) { - rcu_assign_pointer(ctx->peer_lastupdate, plabel); + rcu_assign_pointer(ctx->peer_lastupdate, + aa_get_label(plabel)); rcu_assign_pointer(ctx->peer, aa_get_label(plabel)); aa_put_label(old); } /* else race or a subset - don't update */ From f86ee868fd54c372255519284e1c0f4f7707c045 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Fri, 17 Apr 2026 00:51:22 -0700 Subject: [PATCH 05/35] apparmor: add a conditional version of get_newest_label get_newest_label() will always return a refcount, on the profile it returns. However there are cases where we only need the refcount if the label is stale and get_newest_label() will return a different label. Optimize this by making the get/put happen conditionally, by keeping a flag indicating if the get was performed and a put is needed. Signed-off-by: John Johansen --- security/apparmor/include/label.h | 32 +++++++++++++++++++++++++++++++ security/apparmor/label.c | 22 ++++++++++----------- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/security/apparmor/include/label.h b/security/apparmor/include/label.h index 335f21930702..b5a722a47fd2 100644 --- a/security/apparmor/include/label.h +++ b/security/apparmor/include/label.h @@ -423,6 +423,38 @@ static inline struct aa_label *aa_get_newest_label(struct aa_label *l) return aa_get_label(l); } +/** + * aa_get_newest_label_condref - find the newest version of @l + * @l: the label to check for newer versions of + * @needput: returns whether the reference needs put + * + * Returns: refcounted newest version of @l taking into account + * replacement, renames and removals + * return @l. + */ +static inline struct aa_label *aa_get_newest_label_condref(struct aa_label *l, + bool *needput) +{ + if (l && unlikely(label_is_stale(l))) { + struct aa_label *tmp; + + AA_BUG(!l->proxy); + AA_BUG(!l->proxy->label); + /* BUG: only way this can happen is @l ref count and its + * replacement count have gone to 0 and are on their way + * to destruction. ie. we have a refcounting error + */ + tmp = aa_get_label_rcu(&l->proxy->label); + AA_BUG(!tmp); + + *needput = true; + return tmp; + } + + *needput = false; + return l; +} + static inline void aa_put_label(struct aa_label *l) { if (l) diff --git a/security/apparmor/label.c b/security/apparmor/label.c index 3a721fdf1833..a8850d118c9c 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -1176,22 +1176,21 @@ static struct aa_label *__label_find_merge(struct aa_labelset *ls, struct aa_label *aa_label_find_merge(struct aa_label *a, struct aa_label *b) { struct aa_labelset *ls; - struct aa_label *label, *ar = NULL, *br = NULL; + struct aa_label *label; unsigned long flags; + bool a_needput, b_needput; AA_BUG(!a); AA_BUG(!b); - if (label_is_stale(a)) - a = ar = aa_get_newest_label(a); - if (label_is_stale(b)) - b = br = aa_get_newest_label(b); + a = aa_get_newest_label_condref(a, &a_needput); + b = aa_get_newest_label_condref(b, &b_needput); ls = labelset_of_merge(a, b); read_lock_irqsave(&ls->lock, flags); label = __label_find_merge(ls, a, b); read_unlock_irqrestore(&ls->lock, flags); - aa_put_label(ar); - aa_put_label(br); + aa_put_label_condref(a, a_needput); + aa_put_label_condref(b, b_needput); return label; } @@ -1228,9 +1227,10 @@ struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, if (!label) { struct aa_label *new; + bool a_needput, b_needput; - a = aa_get_newest_label(a); - b = aa_get_newest_label(b); + a = aa_get_newest_label_condref(a, &a_needput); + b = aa_get_newest_label_condref(b, &b_needput); /* could use label_merge_len(a, b), but requires double * comparison for small savings @@ -1242,8 +1242,8 @@ struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b, label = label_merge_insert(new, a, b); label_free_or_put_new(label, new); out: - aa_put_label(a); - aa_put_label(b); + aa_put_label_condref(a, a_needput); + aa_put_label_condref(b, b_needput); } return label; From d62d9bfe050f44f772d05a32079dba3e3523ab2a Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Wed, 3 Jun 2026 13:30:46 -0700 Subject: [PATCH 06/35] security/apparmor/apparmorfs.c: conditionally compile get_loaddata_common_ref() Some config did this: security/apparmor/apparmorfs.c:177:28: warning: 'get_loaddata_common_ref' defined but not used [-Wunused-function] 177 | static struct aa_loaddata *get_loaddata_common_ref(struct aa_common_ref *ref) get_loaddata_common_ref() is only used if CONFIG_SECURITY_APPARMOR_EXPORT_BINARY=y. (Or of course move the function into that block if maintainers perfer) Fixes: 8e135b8aee5a0 ("apparmor: fix race between freeing data and fs accessing it") Cc: John Johansen Cc: Paul Moore Cc: James Morris Cc: "Serge E. Hallyn" Signed-off-by: Andrew Morton Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index ededaf46f3ca..aeff3d2d863b 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -174,6 +174,7 @@ static struct aa_proxy *get_proxy_common_ref(struct aa_common_ref *ref) return NULL; } +#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY static struct aa_loaddata *get_loaddata_common_ref(struct aa_common_ref *ref) { if (ref) @@ -181,6 +182,7 @@ static struct aa_loaddata *get_loaddata_common_ref(struct aa_common_ref *ref) count)); return NULL; } +#endif static void aa_put_common_ref(struct aa_common_ref *ref) { From a58cafd38b46fb1a2220e2fbbcfe291ea75fa147 Mon Sep 17 00:00:00 2001 From: Ruoyu Wang Date: Mon, 8 Jun 2026 14:36:31 +0800 Subject: [PATCH 07/35] apparmor: check label build before no_new_privs test aa_change_profile() builds a replacement label with fn_label_build_in_scope() before the no_new_privs subset check. The build helper can fail and return NULL or an ERR_PTR, but the result was passed to aa_label_is_unconfined_subset() before the existing IS_ERR_OR_NULL() check. Reuse the existing target-label build failure handling immediately after the build. This preserves the current audit handling while preventing the subset helper from dereferencing an invalid label. Fixes: e00b02bb6ac2a ("apparmor: move change_profile mediation to using labels") Signed-off-by: Ruoyu Wang Signed-off-by: John Johansen --- security/apparmor/domain.c | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index f02bf770f638..6748ac74b060 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -1527,6 +1527,8 @@ int aa_change_profile(const char *fqname, int flags) new = fn_label_build_in_scope(label, profile, GFP_KERNEL, aa_get_label(target), aa_get_label(&profile->label)); + if (IS_ERR_OR_NULL(new)) + goto build_fail; /* * no new privs prevents domain transitions that would * reduce restrictions. @@ -1545,16 +1547,8 @@ int aa_change_profile(const char *fqname, int flags) /* only transition profiles in the current ns */ if (stack) new = aa_label_merge(label, target, GFP_KERNEL); - if (IS_ERR_OR_NULL(new)) { - info = "failed to build target label"; - if (!new) - error = -ENOMEM; - else - error = PTR_ERR(new); - new = NULL; - perms.allow = 0; - goto audit; - } + if (IS_ERR_OR_NULL(new)) + goto build_fail; error = aa_replace_current_label(new); } else { if (new) { @@ -1566,6 +1560,17 @@ int aa_change_profile(const char *fqname, int flags) aa_set_current_onexec(target, stack); } + goto audit; + +build_fail: + info = "failed to build target label"; + if (!new) + error = -ENOMEM; + else + error = PTR_ERR(new); + new = NULL; + perms.allow = 0; + audit: error = fn_for_each_in_scope(label, profile, aa_audit_file(subj_cred, From 654fe7505dc6889724d4094fa64f89991afabfc3 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Sat, 2 May 2026 13:21:33 +0200 Subject: [PATCH 08/35] apparmor: aa_label_alloc use aa_label_free on alloc failure aa_label_alloc() allocates a secid before allocating or taking the label proxy. If the later proxy step fails, the error path only freed the label memory, leaking any resources initialized by aa_label_init(). Use aa_label_free() on the failure path so partially initialized labels release their secid and other label resources before the backing memory is freed. Fixes: f1bd904175e81 ("apparmor: add the base fns() for domain labels") Signed-off-by: Zygmunt Krynicki 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 a8850d118c9c..c60244ed96db 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -458,7 +458,7 @@ struct aa_label *aa_label_alloc(int size, struct aa_proxy *proxy, gfp_t gfp) return new; fail: - kfree(new); + aa_label_free(new); return NULL; } From e7501736405a39fa513625ba1f81909c847e4e70 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 17 Feb 2026 11:26:48 -0800 Subject: [PATCH 09/35] apparmor: enable differential encoding Differential encoding while present has not been made broadly available, pending further review and testing. Now that has happened advertise its availability to user space. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index aeff3d2d863b..252a7ca98a9e 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -2424,6 +2424,7 @@ static struct aa_sfs_entry aa_sfs_entry_versions[] = { static struct aa_sfs_entry aa_sfs_entry_policy[] = { AA_SFS_DIR("versions", aa_sfs_entry_versions), AA_SFS_FILE_BOOLEAN("set_load", 1), + AA_SFS_FILE_BOOLEAN("diff-encode", 1), /* number of out of band transitions supported */ AA_SFS_FILE_U64("outofband", MAX_OOB_SUPPORTED), AA_SFS_FILE_U64("permstable32_version", 3), From 1c8a839442823ce5c627d645730d8c61d828aafa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20B=C3=A9lair?= Date: Wed, 11 Feb 2026 14:19:32 +0100 Subject: [PATCH 10/35] apparmor: propagate -ENOMEM correctly in unpack_table MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, if the `kvzalloc` in `unpack_table` fails, it returns NULL. This is masked by `aa_dfa_unpack` which interprets NULL as a -EPROTO, leading to confusing error messages in `apparmor_parser` [1]. The fixed behavior correctly propagates -ENOMEM on allocation failure. Link: https://gitlab.com/apparmor/apparmor/-/issues/592 Reviewed-by: Georgia Garcia Signed-off-by: Maxime Bélair Signed-off-by: John Johansen --- security/apparmor/match.c | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/security/apparmor/match.c b/security/apparmor/match.c index 3a2c6cf02b3c..d43ff34d705c 100644 --- a/security/apparmor/match.c +++ b/security/apparmor/match.c @@ -27,13 +27,13 @@ * @blob: data to unpack (NOT NULL) * @bsize: size of blob * - * Returns: pointer to table else NULL on failure + * Returns: pointer to table else ERR_PTR on failure * * NOTE: must be freed by kvfree (not kfree) */ static struct table_header *unpack_table(char *blob, size_t bsize) { - struct table_header *table = NULL; + struct table_header *table = ERR_PTR(-EPROTO); struct table_header th; size_t tsize; @@ -74,20 +74,21 @@ static struct table_header *unpack_table(char *blob, size_t bsize) else if (th.td_flags == YYTD_DATA32) UNPACK_ARRAY(table->td_data, blob, th.td_lolen, u32, __be32, get_unaligned_be32); - else - goto fail; + else { + kvfree(table); + table = ERR_PTR(-EPROTO); + goto out; + } /* if table was vmalloced make sure the page tables are synced * before it is used, as it goes live to all cpus. */ if (is_vmalloc_addr(table)) vm_unmap_aliases(); - } + } else + table = ERR_PTR(-ENOMEM); out: return table; -fail: - kvfree(table); - return NULL; } /** @@ -359,8 +360,11 @@ struct aa_dfa *aa_dfa_unpack(void *blob, size_t size, int flags) while (size > 0) { table = unpack_table(data, size); - if (!table) + if (IS_ERR(table)) { + error = PTR_ERR(table); + table = NULL; goto fail; + } switch (table->td_id) { case YYTD_ID_ACCEPT: From 1adefbd0d5f5a2dafb3f1ee4537dc1db69fb29d4 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Thu, 12 Feb 2026 13:15:14 -0800 Subject: [PATCH 11/35] apparmor: use __label_make_stale in __aa_proxy_redirect The macro is equivalent to OR-ing in the bitflag manually, but using the macro consistently makes grepping for these occurrences easier. Reviewed-by: Georgia Garcia Signed-off-by: Ryan Lee 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 c60244ed96db..3fd384d8c41a 100644 --- a/security/apparmor/label.c +++ b/security/apparmor/label.c @@ -83,7 +83,7 @@ void __aa_proxy_redirect(struct aa_label *orig, struct aa_label *new) tmp = rcu_dereference_protected(orig->proxy->label, &labels_ns(orig)->lock); rcu_assign_pointer(orig->proxy->label, aa_get_label(new)); - orig->flags |= FLAG_STALE; + __label_make_stale(orig); aa_put_label(tmp); } From ad213bbbc0e3e270ce7df2d9d80d4ce3826993d7 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 1 Mar 2026 12:24:06 -0800 Subject: [PATCH 12/35] apparmor: fix rawdata_f_data implicit flex array rawdata_f_data has a blob of data that is allocated at its end but not explicitly declared. Makes sure it is correctly declared as a flex_rray. Fixes: 63c16c3a76085 ("apparmor: Initial implementation of raw policy blob compression") Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 252a7ca98a9e..03fc18cf5fa7 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -71,10 +71,10 @@ struct rawdata_f_data { struct aa_loaddata *loaddata; + DECLARE_FLEX_ARRAY(char, data); }; #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY -#define RAWDATA_F_DATA_BUF(p) (char *)(p + 1) static void rawdata_f_data_free(struct rawdata_f_data *private) { @@ -1436,7 +1436,7 @@ static ssize_t rawdata_read(struct file *file, char __user *buf, size_t size, struct rawdata_f_data *private = file->private_data; return simple_read_from_buffer(buf, size, ppos, - RAWDATA_F_DATA_BUF(private), + private->data, private->loaddata->size); } @@ -1469,8 +1469,7 @@ static int rawdata_open(struct inode *inode, struct file *file) private->loaddata = loaddata; error = decompress_zstd(loaddata->data, loaddata->compressed_size, - RAWDATA_F_DATA_BUF(private), - loaddata->size); + private->data, loaddata->size); if (error) goto fail_decompress; From 32e92764d6f8d251c1bca62be33793287b453a81 Mon Sep 17 00:00:00 2001 From: Ryan Lee Date: Fri, 13 Feb 2026 11:29:38 -0800 Subject: [PATCH 13/35] apparmor: grab ns lock and refresh when looking up changehat child profiles There was a race condition involving change_hat and profile replacement in which replacement of the parent profile during a changehat operation could result in the list of children becoming empty and the changehat operation failing. To prevent this: - grab the namespace lock until we've built the hat transition, and - use aa_get_newest_profile to avoid using stale profile objects. Link: https://bugs.launchpad.net/bugs/2139664 Fixes: 89dbf1962aa63 ("apparmor: move change_hat mediation to using labels") Reviewed-by: Georgia Garcia Signed-off-by: Ryan Lee Signed-off-by: John Johansen --- security/apparmor/domain.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 6748ac74b060..c2652165a588 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -1109,6 +1110,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, int count, int flags) { struct aa_profile *profile, *root, *hat = NULL; + struct aa_ns *ns, *new_ns; struct aa_label *new; struct label_it it; bool sibling = false; @@ -1119,6 +1121,32 @@ static struct aa_label *change_hat(const struct cred *subj_cred, AA_BUG(!hats); AA_BUG(count < 1); + /* + * Acquire the newest label and then hold the lock until we choose a + * hat, so that profile replacement doesn't atomically truncate the + * list of potential hats. Because we are getting the namespaces from + * the profiles and label, we can rely on the namespaces being live + * and avoid incrementing their refcounts while grabbing the lock. + */ + label = aa_get_label(label); + ns = labels_ns(label); + +retry: + mutex_lock_nested(&ns->lock, ns->level); + if (label_is_stale(label)) { + new = aa_get_newest_label(label); + new_ns = labels_ns(new); + if (new_ns != ns) { + aa_put_label(new); + mutex_unlock(&ns->lock); + ns = new_ns; + label = new; + goto retry; + } + aa_put_label(label); + label = new; + } + if (PROFILE_IS_HAT(labels_profile(label))) sibling = true; @@ -1127,7 +1155,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, name = hats[i]; label_for_each_in_scope(it, labels_ns(label), label, profile) { if (sibling && PROFILE_IS_HAT(profile)) { - root = aa_get_profile_rcu(&profile->parent); + root = aa_get_profile(profile->parent); } else if (!sibling && !PROFILE_IS_HAT(profile)) { root = aa_get_profile(profile); } else { /* conflicting change type */ @@ -1187,6 +1215,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, GLOBAL_ROOT_UID, info, error); } } + mutex_unlock(&ns->lock); return ERR_PTR(error); build: @@ -1199,7 +1228,7 @@ static struct aa_label *change_hat(const struct cred *subj_cred, error = -ENOMEM; goto fail; } /* else if (IS_ERR) build_change_hat has logged error so return new */ - + mutex_unlock(&ns->lock); return new; } From b9b864fc72367ffdbe79b7952518573e9d209844 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 1 Mar 2026 12:29:06 -0800 Subject: [PATCH 14/35] apparmor: free rawdata as soon as possible profiles can be pinned by file and other references, and can live long after they have been replaced/removed. The rawdata however is no longer needed, and can be freed earlier than the rest of the profile. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/policy.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index b6a5eb4021db..1739a9de9893 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -232,6 +232,13 @@ static void __remove_profile(struct aa_profile *profile) aa_label_remove(&profile->label); __aafs_profile_rmdir(profile); __list_remove_profile(profile); + /* rawdata is only ever referenced by fs lookup, that is no + * longer possible here, so put the reference to it. This will + * enable the rawdata to be freed if for some reason the profile + * is pinned and going to live for a while. + */ + aa_put_profile_loaddata(profile->rawdata); + profile->rawdata = NULL; } /** From 7b42f95813dc9ceb6bda35afcf914630909a19f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maxime=20B=C3=A9lair?= Date: Wed, 18 Feb 2026 10:27:34 +0100 Subject: [PATCH 15/35] apparmor: fix potential UAF in aa_replace_profiles MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function aa_replace_profiles was accessing udata->size after calling aa_put_loaddata(udata), causing a potential UAF. Fixed this by saving the size to a local variable before dropping the reference. Fixes: 5ac8c355ae001 ("apparmor: allow introspecting the loaded policy pre internal transform") Reviewed-by: Georgia Garcia Signed-off-by: Maxime Bélair Signed-off-by: John Johansen --- security/apparmor/policy.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 1739a9de9893..08620984d950 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1378,13 +1378,15 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, mutex_unlock(&ns->lock); out: + ssize_t udata_sz = udata->size; + aa_put_ns(ns); aa_put_profile_loaddata(udata); kfree(ns_name); if (error) return error; - return udata->size; + return udata_sz; fail_lock: mutex_unlock(&ns->lock); From ed7cc1c6f240a0c2838c0617afb2b0466edd236f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 17 Feb 2026 08:54:10 -0700 Subject: [PATCH 16/35] apparmor: change fn_label_build() call to not return NULL Previously fn_label_build() was accepting a NULL which represented ENOMEM return and ERR_PTR for errors. Clean this up by requiring the cb fn to return an ERR_PTR or valid value. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/domain.c | 41 ++++++++++++++++++--------------- security/apparmor/include/lib.h | 12 +++++----- security/apparmor/mount.c | 17 +++++--------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index c2652165a588..4172dedaba22 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -864,6 +864,15 @@ static int profile_onexec(const struct cred *subj_cred, } /* ensure none ns domain transitions are correctly applied with onexec */ +static struct aa_label *label_merge_wrap(struct aa_label *a, struct aa_label *b, + gfp_t gfp) +{ + struct aa_label *label = aa_label_merge(a, b, gfp); + + if (!label) + return ERR_PTR(-ENOMEM); + return label; +} static struct aa_label *handle_onexec(const struct cred *subj_cred, struct aa_label *label, @@ -891,12 +900,13 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred, return ERR_PTR(error); new = fn_label_build_in_scope(label, profile, GFP_KERNEL, - stack ? aa_label_merge(&profile->label, onexec, - GFP_KERNEL) + stack ? label_merge_wrap(&profile->label, onexec, + GFP_KERNEL) : aa_get_newest_label(onexec), profile_transition(subj_cred, profile, bprm, buffer, cond, unsafe)); - if (new) + AA_BUG(!new); + if (!IS_ERR(new)) return new; /* TODO: get rid of GLOBAL_ROOT_UID */ @@ -905,7 +915,8 @@ static struct aa_label *handle_onexec(const struct cred *subj_cred, OP_CHANGE_ONEXEC, AA_MAY_ONEXEC, bprm->filename, NULL, onexec, GLOBAL_ROOT_UID, - "failed to build target label", -ENOMEM)); + "failed to build target label", + PTR_ERR(new))); return ERR_PTR(error); } @@ -968,14 +979,10 @@ int apparmor_bprm_creds_for_exec(struct linux_binprm *bprm) profile_transition(subj_cred, profile, bprm, buffer, &cond, &unsafe)); - AA_BUG(!new); if (IS_ERR(new)) { error = PTR_ERR(new); goto done; - } else if (!new) { - error = -ENOMEM; - goto done; } /* Policy has specified a domain transitions. If no_new_privs and @@ -1223,12 +1230,10 @@ static struct aa_label *change_hat(const struct cred *subj_cred, build_change_hat(subj_cred, profile, name, sibling), aa_get_label(&profile->label)); - if (!new) { - info = "label build failed"; - error = -ENOMEM; - goto fail; - } /* else if (IS_ERR) build_change_hat has logged error so return new */ mutex_unlock(&ns->lock); + AA_BUG(!new); + /* return new label or error ptr */ + return new; } @@ -1556,7 +1561,8 @@ int aa_change_profile(const char *fqname, int flags) new = fn_label_build_in_scope(label, profile, GFP_KERNEL, aa_get_label(target), aa_get_label(&profile->label)); - if (IS_ERR_OR_NULL(new)) + AA_BUG(!new); + if (IS_ERR(new)) goto build_fail; /* * no new privs prevents domain transitions that would @@ -1580,10 +1586,9 @@ int aa_change_profile(const char *fqname, int flags) goto build_fail; error = aa_replace_current_label(new); } else { - if (new) { - aa_put_label(new); - new = NULL; - } + /* new will be recomputed so at exec time. So discard */ + aa_put_label(new); + new = NULL; /* full transition will be built in exec path */ aa_set_current_onexec(target, stack); diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index 8c6ce8484552..a093304fc998 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -282,7 +282,6 @@ void aa_policy_destroy(struct aa_policy *policy); * * Returns: new label on success * ERR_PTR if build @FN fails - * NULL if label_build fails due to low memory conditions * * @FN must return a label or ERR_PTR on failure. NULL is not allowed */ @@ -298,7 +297,7 @@ void aa_policy_destroy(struct aa_policy *policy); DEFINE_VEC(label, __lvec); \ DEFINE_VEC(profile, __pvec); \ if (vec_setup(label, __lvec, (L)->size, (GFP))) { \ - __new_ = NULL; \ + __new_ = ERR_PTR(-ENOMEM); \ goto __done; \ } \ __j = 0; \ @@ -320,23 +319,24 @@ void aa_policy_destroy(struct aa_policy *policy); if (__count > 1) { \ __new_ = aa_vec_find_or_create_label(__pvec,\ __count, (GFP)); \ - /* only fails if out of Mem */ \ if (!__new_) \ - __new_ = NULL; \ + __new_ = ERR_PTR(-ENOMEM); \ } else \ __new_ = aa_get_label(&__pvec[0]->label); \ vec_cleanup(profile, __pvec, __count); \ } else \ - __new_ = NULL; \ + __new_ = ERR_PTR(-ENOMEM); \ __do_cleanup: \ vec_cleanup(label, __lvec, (L)->size); \ } else { \ (P) = labels_profile(L); \ __new_ = (FN); \ + AA_BUG(!__new_); \ } \ __done: \ - if (!__new_) \ + if (PTR_ERR(__new_)) \ AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ + AA_BUG(!__new_); \ (__new_); \ }) diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c index 523570aa1a5a..2f5d918832c1 100644 --- a/security/apparmor/mount.c +++ b/security/apparmor/mount.c @@ -735,17 +735,11 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, build_pivotroot(subj_cred, profile, new_path, new_buffer, old_path, old_buffer)); - if (!target) { - info = "label build failed"; - error = -ENOMEM; - goto fail; - } else if (!IS_ERR(target)) { + AA_BUG(!target); + if (!IS_ERR(target)) { error = aa_replace_current_label(target); - if (error) { - /* TODO: audit target */ - aa_put_label(target); - goto out; - } + if (error) + goto fail; aa_put_label(target); } else /* already audited error */ @@ -763,7 +757,8 @@ int aa_pivotroot(const struct cred *subj_cred, struct aa_label *label, NULL /*new_name */, NULL /* old_name */, NULL, NULL, - 0, NULL, AA_MAY_PIVOTROOT, &nullperms, info, + 0, target->hname, AA_MAY_PIVOTROOT, &nullperms, info, error)); + aa_put_label(target); goto out; } From 716d384ac7c905b719f3ce11cdb3a3d172c210fb Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 24 Feb 2026 09:02:04 -0700 Subject: [PATCH 17/35] apparmor: make fn_label_build() capable of handling not supported Currently fn_label_build() callback fns must provide a transition or failure. Change this so that a callback can indicate it should be skipped/not be involved in the label being built. This will be useful when building object labels based on mediation flags, as to whether the label should be set. Existing callers can keep treating NULL return as an error because none of those callback fns support skipping, but instead of the old error handling replace with AA_BUG. Reviewed-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/include/lib.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/security/apparmor/include/lib.h b/security/apparmor/include/lib.h index a093304fc998..e3c8cb044a90 100644 --- a/security/apparmor/include/lib.h +++ b/security/apparmor/include/lib.h @@ -281,14 +281,15 @@ void aa_policy_destroy(struct aa_policy *policy); * @FN: fn to call for each profile transition. @P is set to the profile * * Returns: new label on success + * NULL if all callbacks decline to specify a transition * ERR_PTR if build @FN fails * - * @FN must return a label or ERR_PTR on failure. NULL is not allowed + * @FN must return a label or ERR_PTR on failure. */ #define fn_label_build(L, P, GFP, FN) \ ({ \ __label__ __do_cleanup, __done; \ - struct aa_label *__new_; \ + struct aa_label *__new_ = NULL; \ \ if ((L)->size > 1) { \ /* TODO: add cache of transitions already done */ \ @@ -303,11 +304,15 @@ void aa_policy_destroy(struct aa_policy *policy); __j = 0; \ label_for_each(__i, (L), (P)) { \ __new_ = (FN); \ - AA_BUG(!__new_); \ + if (!__new_) \ + continue; \ if (IS_ERR(__new_)) \ goto __do_cleanup; \ __lvec[__j++] = __new_; \ } \ + if (__j == 0) \ + /* no components adding to build */ \ + goto __do_cleanup; \ for (__j = __count = 0; __j < (L)->size; __j++) \ __count += __lvec[__j]->size; \ if (!vec_setup(profile, __pvec, __count, (GFP))) { \ @@ -331,12 +336,10 @@ __do_cleanup: \ } else { \ (P) = labels_profile(L); \ __new_ = (FN); \ - AA_BUG(!__new_); \ } \ __done: \ if (PTR_ERR(__new_)) \ AA_DEBUG(DEBUG_LABEL, "label build failed\n"); \ - AA_BUG(!__new_); \ (__new_); \ }) From 7681ca43d2b1c776e62fe77e3167835fb1ab8319 Mon Sep 17 00:00:00 2001 From: Georgia Garcia Date: Wed, 6 May 2026 16:02:11 -0300 Subject: [PATCH 18/35] apparmor: fix NULL pointer dereference in unpack_pdb pdb->dfa could be NULL if unpack_dfa fails, causing a NULL pointer dereference. Fixes: 2e12c5f06017 ("apparmor: add additional flags to extended permission.") Signed-off-by: Georgia Garcia Signed-off-by: Manuel Diewald 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 9f45d5513d2c..3643c058d6f8 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1045,7 +1045,7 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, } /* accept2 is in some cases being allocated, even with perms */ - if (pdb->perms && !pdb->dfa->tables[YYTD_ID_ACCEPT2]) { + if (pdb->dfa && 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 59fe6fbc4cd45582bc8893de0a382a36562317b3 Mon Sep 17 00:00:00 2001 From: Georgia Garcia Date: Thu, 29 Jan 2026 15:39:42 -0300 Subject: [PATCH 19/35] apparmor: remove or add symlinks to rawdata according to export_binary When the export_binary parameter is set, then rawdata is available and there should be a symbolic link for the rawdata in the profile directory in apparmorfs. If the parameter is unset, then the symlinks should not exist. The issue arises when changing the value of export_binary on runtime and replacing profiles. If export_binary was set when the profile was originally loaded, then changed to 0 and the profile was reloaded, then the symbolic links would still exist but would return ENOENT because the rawdata no longer exists. On the opposite side, if export_binary was unset when the profile was originally loaded, then changed to 1 and the profile was reloaded, then the symbolic links would not exist, even though the rawdata does. Fixes: d61c57fde8191 ("apparmor: make export of raw binary profile to userspace optional") Signed-off-by: Georgia Garcia Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 102 +++++++++++++++++++------ security/apparmor/include/apparmorfs.h | 12 +++ security/apparmor/policy.c | 15 ++++ 3 files changed, 104 insertions(+), 25 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 03fc18cf5fa7..a59c4b83620f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1757,6 +1757,80 @@ static const struct inode_operations rawdata_link_abi_iops = { static const struct inode_operations rawdata_link_data_iops = { .get_link = rawdata_get_link_data, }; + +/* + * Requires: @profile->ns->lock held + */ +void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile) +{ + aafs_remove(profile->dents[AAFS_PROF_RAW_HASH]); + profile->dents[AAFS_PROF_RAW_HASH] = NULL; + aafs_remove(profile->dents[AAFS_PROF_RAW_ABI]); + profile->dents[AAFS_PROF_RAW_ABI] = NULL; + aafs_remove(profile->dents[AAFS_PROF_RAW_DATA]); + profile->dents[AAFS_PROF_RAW_DATA] = NULL; +} + +static inline int create_symlink_dent(struct aa_profile *profile, + const char *name, + enum aafs_prof_type type, + const struct inode_operations *iops) +{ + struct dentry *dent = NULL; + struct dentry *dir = prof_dir(profile); + + if (profile->dents[type]) + return 0; + + dent = aafs_create(name, S_IFLNK | 0444, dir, + &profile->label.proxy->count, NULL, NULL, iops); + if (IS_ERR(dent)) + return PTR_ERR(dent); + + profile->dents[type] = dent; + return 0; +} + +/* + * Requires: @profile->ns->lock held + */ +int __aa_create_rawdata_symlink_dents(struct aa_profile *profile) +{ + int error; + + if (!profile || + (profile->dents[AAFS_PROF_RAW_HASH] && + profile->dents[AAFS_PROF_RAW_ABI] && + profile->dents[AAFS_PROF_RAW_DATA])) + return 0; + + if (!profile->rawdata) + return 0; + + if (aa_g_hash_policy) { + error = create_symlink_dent(profile, "raw_sha256", + AAFS_PROF_RAW_HASH, + &rawdata_link_sha256_iops); + if (error) + return error; + } + + error = create_symlink_dent(profile, "raw_abi", + AAFS_PROF_RAW_ABI, + &rawdata_link_abi_iops); + if (error) + return error; + + + error = create_symlink_dent(profile, "raw_data", + AAFS_PROF_RAW_DATA, + &rawdata_link_data_iops); + if (error) + return error; + + return 0; +} + #endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ /* @@ -1832,31 +1906,9 @@ int __aafs_profile_mkdir(struct aa_profile *profile, struct dentry *parent) profile->dents[AAFS_PROF_HASH] = dent; } -#ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY - if (profile->rawdata) { - if (aa_g_hash_policy) { - dent = aafs_create("raw_sha256", S_IFLNK | 0444, dir, - &profile->label.proxy->count, NULL, - NULL, &rawdata_link_sha256_iops); - if (IS_ERR(dent)) - goto fail; - profile->dents[AAFS_PROF_RAW_HASH] = dent; - } - dent = aafs_create("raw_abi", S_IFLNK | 0444, dir, - &profile->label.proxy->count, NULL, NULL, - &rawdata_link_abi_iops); - if (IS_ERR(dent)) - goto fail; - profile->dents[AAFS_PROF_RAW_ABI] = dent; - - dent = aafs_create("raw_data", S_IFLNK | 0444, dir, - &profile->label.proxy->count, NULL, NULL, - &rawdata_link_data_iops); - if (IS_ERR(dent)) - goto fail; - profile->dents[AAFS_PROF_RAW_DATA] = dent; - } -#endif /*CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ + error = __aa_create_rawdata_symlink_dents(profile); + if (error) + goto fail2; list_for_each_entry(child, &profile->base.profiles, base.list) { error = __aafs_profile_mkdir(child, prof_child_dir(profile)); diff --git a/security/apparmor/include/apparmorfs.h b/security/apparmor/include/apparmorfs.h index dd580594dfb7..33243d11fd10 100644 --- a/security/apparmor/include/apparmorfs.h +++ b/security/apparmor/include/apparmorfs.h @@ -120,6 +120,8 @@ struct aa_loaddata; #ifdef CONFIG_SECURITY_APPARMOR_EXPORT_BINARY void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata); int __aa_fs_create_rawdata(struct aa_ns *ns, struct aa_loaddata *rawdata); +void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile); +int __aa_create_rawdata_symlink_dents(struct aa_profile *profile); #else static inline void __aa_fs_remove_rawdata(struct aa_loaddata *rawdata) { @@ -131,6 +133,16 @@ static inline int __aa_fs_create_rawdata(struct aa_ns *ns, { return 0; } + +static inline void __aa_remove_rawdata_symlink_dents(struct aa_profile *profile) +{ + /* empty stub */ +} + +static inline int __aa_create_rawdata_symlink_dents(struct aa_profile *profile) +{ + return 0; +} #endif /* CONFIG_SECURITY_APPARMOR_EXPORT_BINARY */ #endif /* __AA_APPARMORFS_H */ diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 08620984d950..847b0ff450c5 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1349,6 +1349,16 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, goto skip; } + if (!aa_g_export_binary) { + if (ent->old && ent->old->rawdata && + ent->old->dents[AAFS_LOADDATA_DIR]) { + /* remove rawdata symlinks because the symlink + * target will be removed + */ + __aa_remove_rawdata_symlink_dents(ent->old); + } + } + /* * TODO: finer dedup based on profile range in data. Load set * can differ but profile may remain unchanged @@ -1359,6 +1369,11 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, if (ent->old) { share_name(ent->old, ent->new); __replace_profile(ent->old, ent->new); + if (aa_g_export_binary) { + /* recreate rawdata symlinks */ + if (!ent->old->rawdata) + __aa_create_rawdata_symlink_dents(ent->new); + } } else { struct list_head *lh; From b7a2b49bba4e5994a476c49d662b796818079e5e Mon Sep 17 00:00:00 2001 From: Hongling Zeng Date: Sun, 3 May 2026 12:12:43 +0800 Subject: [PATCH 20/35] apparmor: Fix return in ns_mkdir_op Return NULL instead of passing to ERR_PTR while error is zero. Fixes smatch warning: - security/apparmor/apparmorfs.c:1846 ns_mkdir_op() warn: passing zero to 'ERR_PTR' Fixes: 88d5baf69082 ("Change inode_operations.mkdir to return struct dentry *") Reviewed-by: Ryan Lee Signed-off-by: Hongling Zeng Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index a59c4b83620f..94b8c7979231 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -1975,7 +1975,7 @@ static struct dentry *ns_mkdir_op(struct mnt_idmap *idmap, struct inode *dir, mutex_unlock(&parent->lock); aa_put_ns(parent); - return ERR_PTR(error); + return error ? ERR_PTR(error) : NULL; } static int ns_rmdir_op(struct inode *dir, struct dentry *dentry) From 45cf568241048e560a81aa2053f06a62069f5640 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Mon, 4 May 2026 08:32:37 +0200 Subject: [PATCH 21/35] apparmor: fail policy unpack on accept2 allocation failure unpack_pdb() may need to allocate a missing ACCEPT2 table for older policy data. If that allocation failed, it set an error message but jumped to the success path, returning a policydb with the required table missing. Return -ENOMEM through the normal failure path when the ACCEPT2 allocation fails. Remove the now-unused out label. Fixes: 2e12c5f06017 ("apparmor: add additional flags to extended permission.") Reviewed-by: Ryan Lee Signed-off-by: Zygmunt Krynicki 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 3643c058d6f8..d9dcff167c48 100644 --- a/security/apparmor/policy_unpack.c +++ b/security/apparmor/policy_unpack.c @@ -1054,7 +1054,8 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, 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; + error = -ENOMEM; + goto fail; } pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_lolen = noents; pdb->dfa->tables[YYTD_ID_ACCEPT2]->td_flags = tdflags; @@ -1079,7 +1080,6 @@ static int unpack_pdb(struct aa_ext *e, struct aa_policydb **policy, * - move free of unneeded trans table here, has to be done * after perm mapping. */ -out: *policy = pdb; return 0; From 7306c41672487a6c28430714be063bc6942c28f2 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Mon, 4 May 2026 13:13:24 +0200 Subject: [PATCH 22/35] apparmor: release exe file resources on path failure get_current_exe_path() takes both an exe_file reference and a path reference before resolving the path name. If aa_path_name() failed, it returned immediately and leaked both references. Route the failure through the common cleanup path so fput() and path_put() always run after the references are acquired. Fixes: 8d34e16f7f2b ("apparmor: userns: Add support for execpath in userns") Reviewed-by: Ryan Lee Signed-off-by: Zygmunt Krynicki Signed-off-by: John Johansen --- security/apparmor/task.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 0db0e81b4600..6445cb5f8526 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -313,9 +313,12 @@ static const char *get_current_exe_path(char *buffer, int buffer_size) p = exe_file->f_path; path_get(&p); - if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL)) - return ERR_PTR(-ENOMEM); + if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL)) { + path_str = ERR_PTR(-ENOMEM); + goto out; + } +out: fput(exe_file); path_put(&p); From e27bfb2ae9ad8522aea82d435fd6d73cccee7e17 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Wed, 6 May 2026 01:17:38 -0700 Subject: [PATCH 23/35] apparmor: remove unnecessary goto and associated label There is no need for a goto a label immediately following the conditional block when the jump is the last statement in the block. Fixes: 7306c41672487 ("apparmor: release exe file resources on path failure") Signed-off-by: John Johansen --- security/apparmor/task.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/security/apparmor/task.c b/security/apparmor/task.c index 6445cb5f8526..b9fb3738124e 100644 --- a/security/apparmor/task.c +++ b/security/apparmor/task.c @@ -313,12 +313,9 @@ static const char *get_current_exe_path(char *buffer, int buffer_size) p = exe_file->f_path; path_get(&p); - if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL)) { + if (aa_path_name(&p, FLAG_VIEW_SUBNS, buffer, &path_str, NULL, NULL)) path_str = ERR_PTR(-ENOMEM); - goto out; - } -out: fput(exe_file); path_put(&p); From fea23bf73f0cae8ccb1d0684e4a3003874771f41 Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Sat, 2 May 2026 13:37:14 +0200 Subject: [PATCH 24/35] apparmor: aa_getprocattr free procattr leak on format failure aa_getprocattr() allocates the output string before rendering the label into it. If the second aa_label_snxprint() call fails, the function returned without freeing that allocation. Free and clear the output pointer on the uncommon formatting failure path before dropping the namespace reference. Fixes: 76a1d263aba3 ("apparmor: switch getprocattr to using label_print fns()") Reviewed-by: Tyler Hicks Reviewed-by: Ryan Lee Signed-off-by: Zygmunt Krynicki Signed-off-by: John Johansen --- security/apparmor/procattr.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/apparmor/procattr.c b/security/apparmor/procattr.c index ce40f15d4952..c07b6e8fd9c9 100644 --- a/security/apparmor/procattr.c +++ b/security/apparmor/procattr.c @@ -54,6 +54,8 @@ int aa_getprocattr(struct aa_label *label, char **string, bool newline) FLAG_SHOW_MODE | FLAG_VIEW_SUBNS | FLAG_HIDDEN_UNCONFINED); if (len < 0) { + kfree(*string); + *string = NULL; aa_put_ns(current_ns); return len; } From 340372688bb87da45ff8d4e2f82ccfd1b64c65ff Mon Sep 17 00:00:00 2001 From: Zygmunt Krynicki Date: Tue, 5 May 2026 05:40:53 +0200 Subject: [PATCH 25/35] apparmor: put secmark label after secid lookup apparmor_secmark_init() parses a configured secmark label to obtain its secid. aa_label_strn_parse() returns a refcounted label, but the success path kept that reference after copying the secid. Fixes: ab9f2115081a ("apparmor: Allow filtering based on secmark policy") Signed-off-by: Zygmunt Krynicki Signed-off-by: John Johansen --- security/apparmor/net.c | 1 + 1 file changed, 1 insertion(+) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index 44c04102062f..df9cb7c00cac 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -354,6 +354,7 @@ static int apparmor_secmark_init(struct aa_secmark *secmark) return PTR_ERR(label); secmark->secid = label->secid; + aa_put_label(label); return 0; } From add2b70038bea194bcdef8a680f9153ee7f93ac0 Mon Sep 17 00:00:00 2001 From: Georgia Garcia Date: Thu, 28 May 2026 16:04:12 -0300 Subject: [PATCH 26/35] apparmor: don't audit files pointing to aa_null.dentry In commit 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") there was a change to not audit files pointing to aa_null.dentry because they provide no value, but setting the error variable instead of returning -EACCES was still causing them to be audited. Fixes: 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") Acked-by: David Disseldorp Signed-off-by: Georgia Garcia 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 694e157149e8..fc5abd5473c8 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -157,7 +157,7 @@ static int path_name(const char *op, const struct cred *subj_cred, /* don't reaudit files closed during inheritance */ if (unlikely(path->dentry == aa_null.dentry)) - error = -EACCES; + return -EACCES; else error = aa_path_name(path, flags, buffer, name, &info, labels_profile(label)->disconnected); From bcd1b34c21748531a3febaf7440632b89d8deab7 Mon Sep 17 00:00:00 2001 From: Maciek Borzecki Date: Fri, 8 May 2026 10:30:16 +0200 Subject: [PATCH 27/35] apparmor: fix uninitialised pointer passed to audit_log_untrustedstring() Commit 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") intrdouced a small bug, where path_name() may pass a potentially uninitialized *name to aa_audit_file() if the path->dentry had been replaced with aa_null.dentry earlier on. This can lead to page fault like one observed on 7.0.2 openSUSE Tumbleweed kernel: [51692.242756] [ T24690] BUG: unable to handle page fault for address: 0000000f00000003 [51692.242762] [ T24690] #PF: supervisor read access in kernel mode [51692.242763] [ T24690] #PF: error_code(0x0000) - not-present page [51692.242765] [ T24690] PGD 0 P4D 0 [51692.242768] [ T24690] Oops: Oops: 0000 [#1] SMP NOPTI [51692.242772] [ T24690] CPU: 3 UID: 1020 PID: 24690 Comm: snap-confine Tainted: G O 7.0.2-1-default #1 PREEMPT(full) openSUSE Tumbleweed ab90b4c9940707f9cafa19bdad80b2cec52dbe51 [51692.242775] [ T24690] Tainted: [O]=OOT_MODULE [51692.242777] [ T24690] Hardware name: Framework Laptop 13 (AMD Ryzen 7040Series)/FRANMDCP05, BIOS 03.18 01/08/2026 [51692.242778] [ T24690] RIP: 0010:strlen+0x4/0x30 [51692.242783] [ T24690] Code: f7 75 ec 31 c0 e9 17 9f 00 ff 48 89 f8 e9 0f 9f 00 ff 0f 1f 40 00 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 f3 0f 1e fa <80> 3f 00 74 18 48 89 f8 0f 1f 40 00 48 83 c0 01 80 38 00 75 f7 48 [51692.242785] [ T24690] RSP: 0018:ffffd015eb1e3608 EFLAGS: 00010282 [51692.242787] [ T24690] RAX: 0000000000000000 RBX: ffff89796198a360 RCX: 0000000000000000 [51692.242788] [ T24690] RDX: 00000000000000d1 RSI: 0000000f00000003 RDI: 0000000f00000003 [51692.242790] [ T24690] RBP: ffffffffb7ede090 R08: 00000000000005f5 R09: 0000000000000000 [51692.242791] [ T24690] R10: 0000000000000000 R11: 0000000000000000 R12: ffffd015eb1e3700 [51692.242792] [ T24690] R13: ffff8977a22bc380 R14: ffffffffb7ec5190 R15: ffff8977a0c8aa80 [51692.242794] [ T24690] FS: 0000000000000000(0000) GS:ffff897f640d8000(0000) knlGS:0000000000000000 [51692.242796] [ T24690] CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 [51692.242797] [ T24690] CR2: 0000000f00000003 CR3: 00000006ad15f000 CR4: 0000000000f50ef0 [51692.242799] [ T24690] PKRU: 55555554 [51692.242800] [ T24690] Call Trace: [51692.242802] [ T24690] [51692.242804] [ T24690] audit_log_untrustedstring+0x1d/0x40 [51692.242811] [ T24690] common_lsm_audit+0x71/0x1d0 [51692.242816] [ T24690] aa_audit+0x5a/0x170 [51692.242819] [ T24690] aa_audit_file+0x18a/0x1b0 [51692.242825] [ T24690] path_name+0xd2/0x100 [51692.242829] [ T24690] profile_path_perm.part.0+0x58/0xb0 [51692.242832] [ T24690] aa_path_perm+0xef/0x150 [51692.242837] [ T24690] apparmor_file_open+0x153/0x2e0 [51692.242840] [ T24690] security_file_open+0x46/0xd0 [51692.242844] [ T24690] do_dentry_open+0xe9/0x4d0 [51692.242848] [ T24690] vfs_open+0x30/0x100 While here, initialise variables which are passed down to path_name(). Fixes: 4a134723f9f1 ("apparmor: move check for aa_null file to cover all cases") Signed-off-by: Maciek Borzecki Signed-off-by: John Johansen --- security/apparmor/file.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/security/apparmor/file.c b/security/apparmor/file.c index fc5abd5473c8..c9d55fe1086f 100644 --- a/security/apparmor/file.c +++ b/security/apparmor/file.c @@ -158,9 +158,9 @@ static int path_name(const char *op, const struct cred *subj_cred, /* don't reaudit files closed during inheritance */ if (unlikely(path->dentry == aa_null.dentry)) return -EACCES; - else - error = aa_path_name(path, flags, buffer, name, &info, - labels_profile(label)->disconnected); + + error = aa_path_name(path, flags, buffer, name, &info, + labels_profile(label)->disconnected); if (error) { fn_for_each_confined(label, profile, aa_audit_file(subj_cred, @@ -250,7 +250,7 @@ static int profile_path_perm(const char *op, const struct cred *subj_cred, struct path_cond *cond, int flags, struct aa_perms *perms) { - const char *name; + const char *name = NULL; int error; if (profile_unconfined(profile)) @@ -328,7 +328,7 @@ static int profile_path_link(const struct cred *subj_cred, struct path_cond *cond) { struct aa_ruleset *rules = profile->label.rules[0]; - const char *lname, *tname = NULL; + const char *lname = NULL, *tname = NULL; struct aa_perms lperms = {}, perms; const char *info = NULL; u32 request = AA_MAY_LINK; From 5112ed5258b8d5e0769ae7d2bf9c9dea14c59703 Mon Sep 17 00:00:00 2001 From: Eduardo Vasconcelos Date: Thu, 21 May 2026 12:13:06 -0300 Subject: [PATCH 28/35] apparmor: Fix inverted comparison in cache_hold_inc() cache_hold_inc() prevents the per-CPU cache hold counter from rising above MAX_HOLD_COUNT, but the comparison is inverted (> MAX_HOLD_COUNT instead of <), so the counter never rises above 0. This breaks the cache mechanism because since the hold counter is always 0, the global pool is always attempted first before falling back to the local cache. The decrement also never occurs, thus the hold counter is effectively dead. Fix by changing > to < in cache_hold_inc(). Fixes: 0b6a6b72b329 ("apparmor: document the buffer hold, add an overflow guard") Signed-off-by: Eduardo Vasconcelos Signed-off-by: John Johansen --- security/apparmor/lsm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 3491e9f60194..b7c19805a216 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -2129,7 +2129,7 @@ static int param_set_mode(const char *val, const struct kernel_param *kp) */ static void cache_hold_inc(unsigned int *hold) { - if (*hold > MAX_HOLD_COUNT) + if (*hold < MAX_HOLD_COUNT) (*hold)++; } From 6f060496d03e4dc560a40f73770bd08335cb7a27 Mon Sep 17 00:00:00 2001 From: Ruslan Valiyev Date: Tue, 26 May 2026 00:04:46 +0200 Subject: [PATCH 29/35] apparmor: fix use-after-free in rawdata dedup loop aa_replace_profiles() walks ns->rawdata_list to dedup the incoming policy blob against entries already attached to existing profiles. Per the kernel-doc on struct aa_loaddata, list membership does not hold a reference: profiles hold pcount, and when the last pcount drops, do_ploaddata_rmfs() is queued on a workqueue that takes ns->lock and removes the entry. Between dropping the last pcount and the workqueue running, an entry remains on the list with pcount == 0. aa_get_profile_loaddata() is an unconditional kref_get() on pcount, so when the dedup loop hits such an entry, refcount hardening reports refcount_t: addition on 0; use-after-free. inside aa_replace_profiles(), and the poisoned counter then trips "saturated" and "underflow" warnings on the subsequent uses of the same loaddata. Before commit a0b7091c4de4 ("apparmor: fix race on rawdata dereference") the dedup path used a get_unless_zero-style helper on a single counter, so the existing "if (tmp)" guard was meaningful. The split-refcount refactor introduced aa_get_profile_loaddata(), which has plain kref_get() semantics, and the guard quietly became a no-op. Introduce aa_get_profile_loaddata_not0(), matching the existing _not0 convention used by aa_get_profile_not0(), and use it for the rawdata_list dedup lookup so dying entries are skipped. Reproduced on x86_64 with v7.1-rc5 in QEMU+KVM running Ubuntu 24.04 + stress-ng 0.17.06: stress-ng --apparmor 1 --klog-check --timeout 60s Without this patch the three refcount_t warnings fire within a few seconds. With it the same 60 s run is clean. Coverage is a smoke-test only; a longer soak with CONFIG_KASAN, CONFIG_KCSAN and CONFIG_PROVE_LOCKING would be welcome from anyone with the cycles. Fixes: a0b7091c4de4 ("apparmor: fix race on rawdata dereference") Reported-by: Colin Ian King Closes: https://bugzilla.kernel.org/show_bug.cgi?id=221513 Cc: stable@vger.kernel.org Signed-off-by: Ruslan Valiyev Signed-off-by: John Johansen --- security/apparmor/include/policy_unpack.h | 19 +++++++++++++++++++ security/apparmor/policy.c | 8 ++++++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index e5a95dc4da1f..b9de0fdf9ee5 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -163,6 +163,25 @@ aa_get_profile_loaddata(struct aa_loaddata *data) return data; } +/** + * aa_get_profile_loaddata_not0 - get a profile reference count if not zero + * @data: reference to get a count on + * + * Like aa_get_profile_loaddata(), but safe to call on an entry that may + * be on a list (e.g. ns->rawdata_list) where the last pcount has already + * dropped and the deferred cleanup has not yet run. + * + * Returns: pointer to reference, or %NULL if @data is NULL or its + * profile refcount has already reached zero. + */ +static inline struct aa_loaddata * +aa_get_profile_loaddata_not0(struct aa_loaddata *data) +{ + if (data && kref_get_unless_zero(&data->pcount)) + return data; + return NULL; +} + void __aa_loaddata_update(struct aa_loaddata *data, long revision); bool aa_rawdata_eq(struct aa_loaddata *l, struct aa_loaddata *r); void aa_loaddata_kref(struct kref *kref); diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index 847b0ff450c5..b59e827747da 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1230,8 +1230,12 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, if (aa_rawdata_eq(rawdata_ent, udata)) { struct aa_loaddata *tmp; - tmp = aa_get_profile_loaddata(rawdata_ent); - /* check we didn't fail the race */ + /* + * Entries remain on rawdata_list with + * pcount == 0 until do_ploaddata_rmfs() + * runs; only take a live profile ref. + */ + tmp = aa_get_profile_loaddata_not0(rawdata_ent); if (tmp) { aa_put_profile_loaddata(udata); udata = tmp; From 3e4ca50ee4d88642afa38815775e1ffa90e8dd0b Mon Sep 17 00:00:00 2001 From: Qingshuang Fu Date: Tue, 26 May 2026 09:38:26 +0800 Subject: [PATCH 30/35] security: apparmor: fix two spelling mistakes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix two spelling errors in comment: - interated → interacted - dont → don't Signed-off-by: Qingshuang Fu Signed-off-by: John Johansen --- security/apparmor/domain.c | 2 +- security/apparmor/lsm.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c index 4172dedaba22..d6958eb00e30 100644 --- a/security/apparmor/domain.c +++ b/security/apparmor/domain.c @@ -136,7 +136,7 @@ static int label_compound_match(struct aa_profile *profile, struct label_it i; struct path_cond cond = { }; - /* find first subcomponent that is in view and going to be interated with */ + /* find first subcomponent that is in view and going to be interacted with */ label_for_each(i, label, tp) { if (!aa_ns_visible(profile->ns, tp->ns, inview)) continue; diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index b7c19805a216..7457067e8192 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1493,7 +1493,7 @@ static int apparmor_socket_shutdown(struct socket *sock, int how) * * Note: can not sleep may be called with locks held * - * dont want protocol specific in __skb_recv_datagram() + * don't want protocol specific in __skb_recv_datagram() * to deny an incoming connection socket_sock_rcv_skb() */ static int apparmor_socket_sock_rcv_skb(struct sock *sk, struct sk_buff *skb) From 4e905ed27c788fbb9ea4384e93ea85b303000d57 Mon Sep 17 00:00:00 2001 From: "Mike Rapoport (Microsoft)" Date: Wed, 20 May 2026 11:18:57 +0300 Subject: [PATCH 31/35] apparmor: replace get_zeroed_page() with kzalloc() multi_transaction_new() allocates memory with get_zeroed_page() and uses it as struct multi_transaction. The usage of that structure does not require struct page access and it is better to allocate multi_transaction objects with kzalloc() that provides better scalability and more debugging possibilities. Replace use of get_zeroed_page() with kzalloc(). Link: https://lore.kernel.org/all/635405e4-9423-4a25-a6e7-e03c8ea0bcbe@redhat.com Reviewed-by: Paul Moore Signed-off-by: Mike Rapoport (Microsoft) Signed-off-by: John Johansen --- security/apparmor/apparmorfs.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c index 94b8c7979231..56155d7d5b2f 100644 --- a/security/apparmor/apparmorfs.c +++ b/security/apparmor/apparmorfs.c @@ -9,6 +9,7 @@ */ #include +#include #include #include #include @@ -906,7 +907,7 @@ static void multi_transaction_kref(struct kref *kref) struct multi_transaction *t; t = container_of(kref, struct multi_transaction, count); - free_page((unsigned long) t); + kfree(t); } static struct multi_transaction * @@ -949,7 +950,7 @@ static struct multi_transaction *multi_transaction_new(struct file *file, if (size > MULTI_TRANSACTION_LIMIT - 1) return ERR_PTR(-EFBIG); - t = (struct multi_transaction *)get_zeroed_page(GFP_KERNEL); + t = kzalloc(PAGE_SIZE, GFP_KERNEL); if (!t) return ERR_PTR(-ENOMEM); kref_init(&t->count); From d0691bd5dcaec2350039ecb04fa70faa91ac142d Mon Sep 17 00:00:00 2001 From: Rodrigo Zaiden Date: Sun, 31 May 2026 16:36:28 -0300 Subject: [PATCH 32/35] apparmor: fix kernel-doc warnings Fix two kernel-doc warnings: - non-kernel-doc comment marked with '/**' in af_unix.c - documented symbol name mismatch for aa_get_i_loaddata() in policy_unpack.h No functional changes. Signed-off-by: Rodrigo Zaiden Signed-off-by: John Johansen --- security/apparmor/af_unix.c | 2 +- security/apparmor/include/policy_unpack.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c index 834a3b1c2f0a..b9b22edae202 100644 --- a/security/apparmor/af_unix.c +++ b/security/apparmor/af_unix.c @@ -615,7 +615,7 @@ static int unix_peer_perm(const struct cred *subj_cred, peer_label, &ad)); } -/** +/* * * Requires: lock held on both @sk and @peer_sk * called by unix_stream_connect, unix_may_send diff --git a/security/apparmor/include/policy_unpack.h b/security/apparmor/include/policy_unpack.h index b9de0fdf9ee5..4ea9b6479a3e 100644 --- a/security/apparmor/include/policy_unpack.h +++ b/security/apparmor/include/policy_unpack.h @@ -131,7 +131,7 @@ struct aa_loaddata { int aa_unpack(struct aa_loaddata *udata, struct list_head *lh, const char **ns); /** - * aa_get_loaddata - get a reference count from a counted data reference + * aa_get_i_loaddata - get a reference count from a counted data reference * @data: reference to get a count on * * Returns: pointer to reference From 1ed40bd525c00d22af666016af9aef7167f8085f Mon Sep 17 00:00:00 2001 From: John Johansen Date: Sun, 14 Jun 2026 16:16:59 -0700 Subject: [PATCH 33/35] apparmor: fix label can not be immediately before a declaration Fix error reported by kernel test robot security/apparmor/policy.c:1381:2: error: a label can only be part of a statement and a declaration is not a statement All errors (new ones prefixed by >>): security/apparmor/policy.c: In function 'aa_replace_profiles': >> security/apparmor/policy.c:1381:2: error: a label can only be part of a statement and a declaration is not a statement ssize_t udata_sz = udata->size; ^~~~~ Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202606150525.npax8WiH-lkp@intel.com/ Fixes: 7b42f95813dc9 ("apparmor: fix potential UAF in aa_replace_profiles") Signed-off-by: John Johansen --- security/apparmor/policy.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c index b59e827747da..94b4a7e727cc 100644 --- a/security/apparmor/policy.c +++ b/security/apparmor/policy.c @@ -1397,9 +1397,10 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label, mutex_unlock(&ns->lock); out: + aa_put_ns(ns); + ssize_t udata_sz = udata->size; - aa_put_ns(ns); aa_put_profile_loaddata(udata); kfree(ns_name); From 4d587cd8a72155089a627130bbd4716ec0856e21 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Mon, 22 Jun 2026 15:57:38 -0500 Subject: [PATCH 34/35] apparmor: mediate the implicit connect of TCP fast open sendmsg sendmsg()/sendto() with MSG_FASTOPEN is a combination of connect(2) and write(2): it opens the connection in the SYN. apparmor_socket_sendmsg() only checks AA_MAY_SEND, so a profile that grants send but denies connect lets a confined task open an outbound TCP/MPTCP connection that connect(2) would have refused, bypassing connect mediation. Mediate the implicit connect when MSG_FASTOPEN is set and a destination is supplied. Add it to apparmor_socket_sendmsg() (not the shared aa_sock_msg_perm() helper, which recvmsg also uses) and call aa_sk_perm() directly, mirroring the selinux and tomoyo fixes. sk_is_tcp() does not cover MPTCP fast open, so the SOCK_STREAM/IPPROTO_MPTCP arm is explicit. Fixes: cf60af03ca4e ("net-tcp: Fast Open client - sendmsg(MSG_FASTOPEN)") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: John Johansen --- security/apparmor/lsm.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/security/apparmor/lsm.c b/security/apparmor/lsm.c index 7457067e8192..88d12e89d115 100644 --- a/security/apparmor/lsm.c +++ b/security/apparmor/lsm.c @@ -1422,7 +1422,21 @@ static int aa_sock_msg_perm(const char *op, u32 request, struct socket *sock, static int apparmor_socket_sendmsg(struct socket *sock, struct msghdr *msg, int size) { - return aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size); + int error = aa_sock_msg_perm(OP_SENDMSG, AA_MAY_SEND, sock, msg, size); + + if (error) + return error; + + /* TCP fast open carries connect() semantics in sendmsg(); mediate + * the implicit connect so it cannot bypass the connect permission. + */ + if ((msg->msg_flags & MSG_FASTOPEN) && msg->msg_name && + (sk_is_tcp(sock->sk) || + (sk_is_inet(sock->sk) && sock->sk->sk_type == SOCK_STREAM && + sock->sk->sk_protocol == IPPROTO_MPTCP))) + error = aa_sk_perm(OP_CONNECT, AA_MAY_CONNECT, sock->sk); + + return error; } static int apparmor_socket_recvmsg(struct socket *sock, From 2f6701a5ce6257ae7a64ddc6d89d0a08d2a034f8 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Mon, 22 Jun 2026 16:34:13 -0700 Subject: [PATCH 35/35] apparmor: advertise the tcp fast open fix is applied The fix for tcp-fast-open ensures that the connect permission is being mediated correctly but it didn't add an artifact to the feature set to advertise the fix is available. Add an artifact so that the test suite can identify if the fix has not been properly applied or a new unexpected regression has occurred. Fixes: 4d587cd8a7215 ("apparmor: mediate the implicit connect of TCP fast open sendmsg") Signed-off-by: John Johansen --- security/apparmor/net.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/security/apparmor/net.c b/security/apparmor/net.c index df9cb7c00cac..cf590dd08540 100644 --- a/security/apparmor/net.c +++ b/security/apparmor/net.c @@ -22,12 +22,14 @@ struct aa_sfs_entry aa_sfs_entry_network[] = { AA_SFS_FILE_STRING("af_mask", AA_SFS_AF_MASK), + AA_SFS_FILE_BOOLEAN("tcp-fast-open", 1), { } }; 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), + AA_SFS_FILE_BOOLEAN("tcp-fast-open", 1), { } };