* FAILED: patch "[PATCH] apparmor: fix profile leak when a policy load fails" failed to apply to 6.18-stable tree
@ 2026-11-07 10:15 Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 1/4] apparmor: refactor xattr attachment, to take the file path Sasha Levin
0 siblings, 1 reply; 5+ messages in thread
From: Sasha Levin @ 2026-11-07 10:15 UTC (permalink / raw)
To: georgia.garcia, john.johansen; +Cc: stable
The patch below does not apply to the 6.18-stable tree.
If someone wants it applied there, or to any other stable or longterm
tree, then please email the backport, including the original git commit
id to <stable@vger.kernel.org>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.18.y
git checkout FETCH_HEAD
git cherry-pick -x ce94927fa4173fcc1eca27bdc65a140064c8f1e6
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<stable@vger.kernel.org>' --in-reply-to '20261107051557-failed-6.18-ce94927fa417@kernel.org' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'
Thanks,
Sasha
------------------ original commit in Linus's tree ------------------
From ce94927fa4173fcc1eca27bdc65a140064c8f1e6 Mon Sep 17 00:00:00 2001
From: Georgia Garcia <georgia.garcia@canonical.com>
Date: Wed, 14 Oct 2026 19:08:21 -0300
Subject: [PATCH] apparmor: fix profile leak when a policy load fails
The error paths of aa_replace_profiles() and aa_unpack() do not break
the refcount cycle between label and proxy. They just aa_put_profile()
the new profiles, which drops the label refcount from 2 to 1, and the
proxy keeps the profile alive. Every failed atomic policy load
(e.g. "profile can not be replaced", or a later profile in the blob
failing to unpack) leaks every new profile in the set, along with its
proxy, rules and its loaddata reference.
The leak is usually not visible from userspace, because the raw_data
directory for a failed load is not created, but it becomes visible
when the blob is deduplicated onto an existing loaddata, which then
keeps its policy/raw_data/<rev> directory forever.
Add aa_put_unpublished_profile(), which drops the profile's own proxy
before putting the profile, and use it on those error paths. The
proxy of an unpacked profile is always freshly allocated and not
referenced by anything else before the profile is published, so
putting it is safe.
Fixes: 637f688dc3dc ("apparmor: switch from profiles to using labels on contexts")
Cc: stable@vger.kernel.org
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
---
security/apparmor/include/policy.h | 20 ++++++++++++++++++++
security/apparmor/policy.c | 3 +++
security/apparmor/policy_unpack.c | 4 +++-
3 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 0dcbf8cf1029a..bb851654cb80f 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -436,6 +436,26 @@ static inline void aa_put_profile(struct aa_profile *p)
kref_put(&p->label.count.count, aa_label_kref);
}
+/**
+ * aa_put_unpublished_profile - put a profile that was never made visible
+ * @p: profile (MAYBE NULL)
+ *
+ * A newly allocated profile and its proxy reference each other
+ * (proxy->label holds a label refcount). That cycle is normally broken
+ * when the profile is replaced or removed, which redirects the proxy.
+ * A profile that is discarded before being added to the policy tree
+ * never goes through either path, so break the cycle here, otherwise
+ * the profile, its proxy and its rawdata are leaked.
+ */
+static inline void aa_put_unpublished_profile(struct aa_profile *p)
+{
+ if (p && p->label.proxy) {
+ aa_put_proxy(p->label.proxy);
+ p->label.proxy = NULL;
+ }
+ aa_put_profile(p);
+}
+
static inline int AUDIT_MODE(const struct aa_profile *profile)
{
if (aa_g_audit != AUDIT_NORMAL)
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index bfe969b869577..63f7821b4dfff 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -1462,6 +1462,9 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
}
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
+ /* new was never added to the policy tree */
+ aa_put_unpublished_profile(ent->new);
+ ent->new = NULL;
aa_load_ent_free(ent);
}
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index 7ebffb4ff74fa..35dfa3b451f39 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -1822,13 +1822,15 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
fail_profile:
kfree(ns_name);
- aa_put_profile(profile);
+ aa_put_unpublished_profile(profile);
fail:
if (compressed_data)
kvfree(compressed_data);
list_for_each_entry_safe(ent, tmp, lh, list) {
list_del_init(&ent->list);
+ aa_put_unpublished_profile(ent->new);
+ ent->new = NULL;
aa_load_ent_free(ent);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 6.18.y 1/4] apparmor: refactor xattr attachment, to take the file path
2026-11-07 10:15 FAILED: patch "[PATCH] apparmor: fix profile leak when a policy load fails" failed to apply to 6.18-stable tree Sasha Levin
@ 2026-11-12 4:05 ` Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 2/4] apparmor: constify aa_perms parameters that are read-only Sasha Levin
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Sasha Levin @ 2026-11-12 4:05 UTC (permalink / raw)
To: stable; +Cc: John Johansen, Georgia Garcia, Sasha Levin
From: John Johansen <john.johansen@canonical.com>
[ Upstream commit 812aa0aa67d50c251d9788f1d225438e747507f0 ]
The full bprm does not need to be passed into xattrs_match, so only
pass in the path, and propagate the change backup the call stack until
bprm is actually needed.
Reviewed-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
Stable-dep-of: ce94927fa417 ("apparmor: fix profile leak when a policy load fails")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/apparmor/domain.c | 34 +++++++++++++++++++---------------
1 file changed, 19 insertions(+), 15 deletions(-)
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 8e478ac99ff6d..57e886b8ef461 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -303,28 +303,31 @@ static int change_profile_perms(struct aa_profile *profile,
/**
* aa_xattrs_match - check whether a file matches the xattrs defined in profile
- * @bprm: binprm struct for the process to validate
+ * @path: path for file being matched (NOT NULL)
* @profile: profile to match against (NOT NULL)
* @state: state to start match in
*
* Returns: number of extended attributes that matched, or < 0 on error
*/
-static int aa_xattrs_match(const struct linux_binprm *bprm,
+static int aa_xattrs_match(const struct path *path,
struct aa_profile *profile, aa_state_t state)
{
+ AA_BUG(!path);
+ AA_BUG(!profile);
+
int i;
struct dentry *d;
char *value = NULL;
struct aa_attachment *attach = &profile->attach;
int size, value_size = 0, ret = attach->xattr_count;
- if (!bprm || !attach->xattr_count)
+ if (!attach->xattr_count)
return 0;
might_sleep();
/* transition from exec match to xattr set */
state = aa_dfa_outofband_transition(attach->xmatch->dfa, state);
- d = bprm->file->f_path.dentry;
+ d = path->dentry;
for (i = 0; i < attach->xattr_count; i++) {
size = vfs_getxattr_alloc(&nop_mnt_idmap, d, attach->xattrs[i],
@@ -372,7 +375,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm,
/**
* find_attach - do attachment search for unconfined processes
- * @bprm: binprm structure of transitioning task
+ * @path: path of file in question (NOT NULL)
* @ns: the current namespace (NOT NULL)
* @head: profile list to walk (NOT NULL)
* @name: to match against (NOT NULL)
@@ -387,7 +390,7 @@ static int aa_xattrs_match(const struct linux_binprm *bprm,
*
* Returns: label or NULL if no match found
*/
-static struct aa_label *find_attach(const struct linux_binprm *bprm,
+static struct aa_label *find_attach(const struct path *path,
struct aa_ns *ns, struct list_head *head,
const char *name, const char **info)
{
@@ -395,6 +398,7 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm,
bool conflict = false;
struct aa_profile *profile, *candidate = NULL;
+ AA_BUG(!path);
AA_BUG(!name);
AA_BUG(!head);
@@ -434,13 +438,13 @@ static struct aa_label *find_attach(const struct linux_binprm *bprm,
if (count < candidate_len)
continue;
- if (bprm && attach->xattr_count) {
+ if (attach->xattr_count) {
long rev = READ_ONCE(ns->revision);
if (!aa_get_profile_not0(profile))
goto restart;
rcu_read_unlock();
- ret = aa_xattrs_match(bprm, profile,
+ ret = aa_xattrs_match(path, profile,
state);
rcu_read_lock();
aa_put_profile(profile);
@@ -556,7 +560,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
/**
* x_to_label - get target label for a given xindex
* @profile: current profile (NOT NULL)
- * @bprm: binprm structure of transitioning task
+ * @path: path of file in question
* @name: name to lookup (NOT NULL)
* @xindex: index into x transition table
* @lookupname: returns: name used in lookup if one was specified (NOT NULL)
@@ -567,7 +571,7 @@ struct aa_label *x_table_lookup(struct aa_profile *profile, u32 xindex,
* Returns: refcounted label or NULL if not found available
*/
static struct aa_label *x_to_label(struct aa_profile *profile,
- const struct linux_binprm *bprm,
+ const struct path *path,
const char *name, u32 xindex,
const char **lookupname,
const char **info)
@@ -598,11 +602,11 @@ static struct aa_label *x_to_label(struct aa_profile *profile,
case AA_X_NAME:
if (xindex & AA_X_CHILD)
/* released by caller */
- new = find_attach(bprm, ns, &profile->base.profiles,
+ new = find_attach(path, ns, &profile->base.profiles,
name, info);
else
/* released by caller */
- new = find_attach(bprm, ns, &ns->base.profiles,
+ new = find_attach(path, ns, &ns->base.profiles,
name, info);
*lookupname = name;
break;
@@ -690,7 +694,7 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
}
if (profile_unconfined(profile)) {
- new = find_attach(bprm, profile->ns,
+ new = find_attach(&bprm->file->f_path, profile->ns,
&profile->ns->base.profiles, name, &info);
/* info set -> something unusual that we should report
* Currently this is only conflicting attachments, but other
@@ -720,8 +724,8 @@ static struct aa_label *profile_transition(const struct cred *subj_cred,
state = aa_str_perms(rules->file, state, name, cond, &perms);
if (perms.allow & MAY_EXEC) {
/* exec permission determine how to transition */
- new = x_to_label(profile, bprm, name, perms.xindex, &target,
- &info);
+ new = x_to_label(profile, &bprm->file->f_path, 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
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 6.18.y 2/4] apparmor: constify aa_perms parameters that are read-only
2026-11-12 4:05 ` [PATCH 6.18.y 1/4] apparmor: refactor xattr attachment, to take the file path Sasha Levin
@ 2026-11-12 4:05 ` Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 3/4] apparmor: constify aa_profile parameters on read-only compute paths Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 4/4] apparmor: fix profile leak when a policy load fails Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-11-12 4:05 UTC (permalink / raw)
To: stable; +Cc: John Johansen, Sasha Levin
From: John Johansen <john.johansen@canonical.com>
[ Upstream commit e3bc982007fe5e704c6a7f5a189502ece056f68d ]
Several functions take a struct aa_perms * argument that is only ever
read from and never modified through the pointer. Mark those parameters
const struct aa_perms * to document intent and let the compiler enforce
that the permission set is not mutated.
The converted functions are:
- aa_check_perms()
- aa_do_perms()
- do_perms() (af_inet)
- match_label() (af_unix)
- verify_perm()
- aa_perms_accum() / aa_perms_accum_raw() (@addend only)
No functional change.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Assisted-by: Claude:claude-opus-4.8
Stable-dep-of: ce94927fa417 ("apparmor: fix profile leak when a policy load fails")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/apparmor/af_unix.c | 2 +-
security/apparmor/include/net.h | 2 +-
security/apparmor/include/perms.h | 11 ++++++-----
security/apparmor/lib.c | 2 +-
security/apparmor/net.c | 2 +-
security/apparmor/policy_unpack.c | 2 +-
6 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/security/apparmor/af_unix.c b/security/apparmor/af_unix.c
index aa2b31d5598cd..7ba775017586b 100644
--- a/security/apparmor/af_unix.c
+++ b/security/apparmor/af_unix.c
@@ -172,7 +172,7 @@ static aa_state_t match_to_peer(struct aa_policydb *policy, aa_state_t 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,
+ const struct aa_perms *p,
struct apparmor_audit_data *ad)
{
AA_BUG(!profile);
diff --git a/security/apparmor/include/net.h b/security/apparmor/include/net.h
index 0d0b0ce427238..021a35b94bd7e 100644
--- a/security/apparmor/include/net.h
+++ b/security/apparmor/include/net.h
@@ -86,7 +86,7 @@ extern struct aa_sfs_entry aa_sfs_entry_network[];
extern struct aa_sfs_entry aa_sfs_entry_networkv9[];
int aa_do_perms(struct aa_profile *profile, struct aa_policydb *policy,
- aa_state_t state, u32 request, struct aa_perms *p,
+ aa_state_t state, u32 request, const 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,
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h
index 37a3781b99a08..695d9b0572d4f 100644
--- a/security/apparmor/include/perms.h
+++ b/security/apparmor/include/perms.h
@@ -105,7 +105,7 @@ extern struct aa_perms allperms;
* @addend: perms struct to add to @accum
*/
static inline void aa_perms_accum_raw(struct aa_perms *accum,
- struct aa_perms *addend)
+ const struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~addend->deny;
@@ -132,7 +132,7 @@ static inline void aa_perms_accum_raw(struct aa_perms *accum,
* @addend: perms struct to add to @accum
*/
static inline void aa_perms_accum(struct aa_perms *accum,
- struct aa_perms *addend)
+ const struct aa_perms *addend)
{
accum->deny |= addend->deny;
accum->allow &= addend->allow & ~accum->deny;
@@ -208,12 +208,13 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
u32 chrsmask, const char * const *names, u32 namesmask);
void aa_apply_modes_to_perms(struct aa_profile *profile,
struct aa_perms *perms);
-void aa_perms_accum(struct aa_perms *accum, struct aa_perms *addend);
-void aa_perms_accum_raw(struct aa_perms *accum, struct aa_perms *addend);
+void aa_perms_accum(struct aa_perms *accum, const struct aa_perms *addend);
+void aa_perms_accum_raw(struct aa_perms *accum, const struct aa_perms *addend);
void aa_profile_match_label(struct aa_profile *profile,
struct aa_ruleset *rules, struct aa_label *label,
int type, u32 request, struct aa_perms *perms);
-int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms,
u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *));
#endif /* __AA_PERM_H */
+
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index 5d53c15e46a9e..3b982770c4ceb 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -415,7 +415,7 @@ void aa_profile_match_label(struct aa_profile *profile,
* error code will indicate whether there was an explicit deny
* with a positive value.
*/
-int aa_check_perms(struct aa_profile *profile, struct aa_perms *perms,
+int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms,
u32 request, struct apparmor_audit_data *ad,
void (*cb)(struct audit_buffer *, void *))
{
diff --git a/security/apparmor/net.c b/security/apparmor/net.c
index cf590dd08540c..73a272576d5d6 100644
--- a/security/apparmor/net.c
+++ b/security/apparmor/net.c
@@ -166,7 +166,7 @@ void audit_net_cb(struct audit_buffer *ab, void *va)
/* standard permission lookup pattern - supports early bailout */
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)
+ const struct aa_perms *p, struct apparmor_audit_data *ad)
{
struct aa_perms perms;
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index 391665c27ede1..8dc2adedc7ae1 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -1257,7 +1257,7 @@ static bool verify_dfa_accept_index(struct aa_dfa *dfa, int table_size)
return true;
}
-static bool verify_perm(struct aa_perms *perm)
+static bool verify_perm(const struct aa_perms *perm)
{
/* TODO: allow option to just force the perms into a valid state */
if (perm->allow & perm->deny)
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 6.18.y 3/4] apparmor: constify aa_profile parameters on read-only compute paths
2026-11-12 4:05 ` [PATCH 6.18.y 1/4] apparmor: refactor xattr attachment, to take the file path Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 2/4] apparmor: constify aa_perms parameters that are read-only Sasha Levin
@ 2026-11-12 4:05 ` Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 4/4] apparmor: fix profile leak when a policy load fails Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-11-12 4:05 UTC (permalink / raw)
To: stable; +Cc: John Johansen, Sasha Levin
From: John Johansen <john.johansen@canonical.com>
[ Upstream commit 08066bcdeed037a99c47f7c8456cf30ee3055048 ]
A number of functions take a struct aa_profile * argument that is only
ever read from: they compute DFA matches or apply the profile's mode
flags without modifying the profile, taking a reference on it, or
touching its embedded label. Mark those parameters const struct
aa_profile * to document intent and let the compiler enforce it.
The converted functions are the permission "compute" path plus a few
pure readers:
- aa_apply_modes_to_perms(), aa_profile_match_label()
- AUDIT_MODE()
- aa_label_match() and its match_component()/label_compound_match()/
label_components_match() helpers (label.c)
- match_component()/label_compound_match()/label_components_match()/
label_match()/change_profile_perms()/aa_xattrs_match() (domain.c)
- match_iface()/match_addr_iface()/match_addr_iface_label()/
skb_match_to_sk()/skb_match_to_cmd() (af_inet.c)
- aa_profile_capget(), path_flags(), profile_query_cb()
The remaining aa_profile * parameters cannot be made const: the audit
path stores &profile->label into the owned, refcounted
apparmor_audit_data.subj_label/peer fields, and the domain/lifecycle
paths take references on the profile's embedded label
(aa_get_label()/aa_get_newest_label()/aa_get_profile()) or write
profile fields.
No functional change.
Signed-off-by: John Johansen <john.johansen@canonical.com>
Assisted-by: Claude:claude-opus-4.8
Stable-dep-of: ce94927fa417 ("apparmor: fix profile leak when a policy load fails")
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/apparmor/apparmorfs.c | 3 ++-
security/apparmor/capability.c | 2 +-
security/apparmor/domain.c | 16 ++++++++--------
security/apparmor/include/capability.h | 2 +-
security/apparmor/include/label.h | 2 +-
security/apparmor/include/perms.h | 4 ++--
security/apparmor/include/policy.h | 2 +-
security/apparmor/label.c | 10 +++++-----
security/apparmor/lib.c | 5 +++--
security/apparmor/mount.c | 2 +-
10 files changed, 25 insertions(+), 23 deletions(-)
diff --git a/security/apparmor/apparmorfs.c b/security/apparmor/apparmorfs.c
index 4dfca0c6e938d..7db41581b7b21 100644
--- a/security/apparmor/apparmorfs.c
+++ b/security/apparmor/apparmorfs.c
@@ -690,7 +690,8 @@ static const struct file_operations aa_fs_ns_revision_fops = {
.release = ns_revision_release,
};
-static void profile_query_cb(struct aa_profile *profile, struct aa_perms *perms,
+static void profile_query_cb(const struct aa_profile *profile,
+ struct aa_perms *perms,
const char *match_str, size_t match_len)
{
struct aa_ruleset *rules = profile->label.rules[0];
diff --git a/security/apparmor/capability.c b/security/apparmor/capability.c
index b9ea6bc45c1a7..2f3c6c84358de 100644
--- a/security/apparmor/capability.c
+++ b/security/apparmor/capability.c
@@ -191,7 +191,7 @@ int aa_capable(const struct cred *subj_cred, struct aa_label *label,
return error;
}
-kernel_cap_t aa_profile_capget(struct aa_profile *profile)
+kernel_cap_t aa_profile_capget(const struct aa_profile *profile)
{
struct aa_ruleset *rules = profile->label.rules[0];
aa_state_t state;
diff --git a/security/apparmor/domain.c b/security/apparmor/domain.c
index 57e886b8ef461..1bfd36d8dfde2 100644
--- a/security/apparmor/domain.c
+++ b/security/apparmor/domain.c
@@ -90,8 +90,8 @@ static int may_change_ptraced_domain(const struct cred *to_cred,
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
-static inline aa_state_t match_component(struct aa_profile *profile,
- struct aa_profile *tp,
+static inline aa_state_t match_component(const struct aa_profile *profile,
+ const struct aa_profile *tp,
bool stack, aa_state_t state)
{
struct aa_ruleset *rules = profile->label.rules[0];
@@ -126,7 +126,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_compound_match(struct aa_profile *profile,
+static int label_compound_match(const struct aa_profile *profile,
struct aa_label *label, bool stack,
aa_state_t state, bool inview, u32 request,
struct aa_perms *perms)
@@ -188,7 +188,7 @@ static int label_compound_match(struct aa_profile *profile,
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_components_match(struct aa_profile *profile,
+static int label_components_match(const struct aa_profile *profile,
struct aa_label *label, bool stack,
aa_state_t start, bool inview, u32 request,
struct aa_perms *perms)
@@ -252,7 +252,7 @@ static int label_components_match(struct aa_profile *profile,
*
* Returns: the state the match finished in, may be the none matching state
*/
-static int label_match(struct aa_profile *profile, struct aa_label *label,
+static int label_match(const struct aa_profile *profile, struct aa_label *label,
bool stack, aa_state_t state, bool inview, u32 request,
struct aa_perms *perms)
{
@@ -286,7 +286,7 @@ static int label_match(struct aa_profile *profile, struct aa_label *label,
* currently only matches full label A//&B//&C or individual components A, B, C
* not arbitrary combinations. Eg. A//&B, C
*/
-static int change_profile_perms(struct aa_profile *profile,
+static int change_profile_perms(const struct aa_profile *profile,
struct aa_label *target, bool stack,
u32 request, aa_state_t start,
struct aa_perms *perms)
@@ -310,7 +310,7 @@ static int change_profile_perms(struct aa_profile *profile,
* Returns: number of extended attributes that matched, or < 0 on error
*/
static int aa_xattrs_match(const struct path *path,
- struct aa_profile *profile, aa_state_t state)
+ const struct aa_profile *profile, aa_state_t state)
{
AA_BUG(!path);
AA_BUG(!profile);
@@ -318,7 +318,7 @@ static int aa_xattrs_match(const struct path *path,
int i;
struct dentry *d;
char *value = NULL;
- struct aa_attachment *attach = &profile->attach;
+ const struct aa_attachment *attach = &profile->attach;
int size, value_size = 0, ret = attach->xattr_count;
if (!attach->xattr_count)
diff --git a/security/apparmor/include/capability.h b/security/apparmor/include/capability.h
index 1ddcec2d1160d..ec4bf70e3410b 100644
--- a/security/apparmor/include/capability.h
+++ b/security/apparmor/include/capability.h
@@ -36,7 +36,7 @@ struct aa_caps {
extern struct aa_sfs_entry aa_sfs_entry_caps[];
-kernel_cap_t aa_profile_capget(struct aa_profile *profile);
+kernel_cap_t aa_profile_capget(const 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/include/label.h b/security/apparmor/include/label.h
index 75c49e713a749..42fcff72e1899 100644
--- a/security/apparmor/include/label.h
+++ b/security/apparmor/include/label.h
@@ -346,7 +346,7 @@ static inline const char *aa_label_str_split(const char *str)
struct aa_perms;
struct aa_ruleset;
-int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
+int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool subns,
u32 request, struct aa_perms *perms);
diff --git a/security/apparmor/include/perms.h b/security/apparmor/include/perms.h
index 695d9b0572d4f..5cc0b3c0225a9 100644
--- a/security/apparmor/include/perms.h
+++ b/security/apparmor/include/perms.h
@@ -206,11 +206,11 @@ void aa_audit_perm_names(struct audit_buffer *ab, const char * const *names,
u32 mask);
void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
u32 chrsmask, const char * const *names, u32 namesmask);
-void aa_apply_modes_to_perms(struct aa_profile *profile,
+void aa_apply_modes_to_perms(const struct aa_profile *profile,
struct aa_perms *perms);
void aa_perms_accum(struct aa_perms *accum, const struct aa_perms *addend);
void aa_perms_accum_raw(struct aa_perms *accum, const struct aa_perms *addend);
-void aa_profile_match_label(struct aa_profile *profile,
+void aa_profile_match_label(const struct aa_profile *profile,
struct aa_ruleset *rules, struct aa_label *label,
int type, u32 request, struct aa_perms *perms);
int aa_check_perms(struct aa_profile *profile, const struct aa_perms *perms,
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 2eaa1bee31bc0..69e6c93d2ff04 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -408,7 +408,7 @@ static inline void aa_put_profile(struct aa_profile *p)
kref_put(&p->label.count.count, aa_label_kref);
}
-static inline int AUDIT_MODE(struct aa_profile *profile)
+static inline int AUDIT_MODE(const struct aa_profile *profile)
{
if (aa_g_audit != AUDIT_NORMAL)
return aa_g_audit;
diff --git a/security/apparmor/label.c b/security/apparmor/label.c
index 667f20363ebf1..d0610e9d7961a 100644
--- a/security/apparmor/label.c
+++ b/security/apparmor/label.c
@@ -1253,9 +1253,9 @@ struct aa_label *aa_label_merge(struct aa_label *a, struct aa_label *b,
* If a subns profile is not to be matched should be prescreened with
* visibility test.
*/
-static inline aa_state_t match_component(struct aa_profile *profile,
+static inline aa_state_t match_component(const struct aa_profile *profile,
struct aa_ruleset *rules,
- struct aa_profile *tp,
+ const struct aa_profile *tp,
aa_state_t state)
{
const char *ns_name;
@@ -1287,7 +1287,7 @@ static inline aa_state_t match_component(struct aa_profile *profile,
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_compound_match(struct aa_profile *profile,
+static int label_compound_match(const struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label,
aa_state_t state, bool inview, u32 request,
@@ -1343,7 +1343,7 @@ static int label_compound_match(struct aa_profile *profile,
* @perms should be preinitialized with allperms OR a previous permission
* check to be stacked.
*/
-static int label_components_match(struct aa_profile *profile,
+static int label_components_match(const struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label, aa_state_t start,
bool inview, u32 request,
@@ -1402,7 +1402,7 @@ static int label_components_match(struct aa_profile *profile,
*
* Returns: the state the match finished in, may be the none matching state
*/
-int aa_label_match(struct aa_profile *profile, struct aa_ruleset *rules,
+int aa_label_match(const struct aa_profile *profile, struct aa_ruleset *rules,
struct aa_label *label, aa_state_t state, bool inview,
u32 request, struct aa_perms *perms)
{
diff --git a/security/apparmor/lib.c b/security/apparmor/lib.c
index 3b982770c4ceb..3a65dc82fd8eb 100644
--- a/security/apparmor/lib.c
+++ b/security/apparmor/lib.c
@@ -358,7 +358,8 @@ void aa_audit_perm_mask(struct audit_buffer *ab, u32 mask, const char *chrs,
*
* TODO: split into profile and ns based flags for when accumulating perms
*/
-void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
+void aa_apply_modes_to_perms(const struct aa_profile *profile,
+ struct aa_perms *perms)
{
switch (AUDIT_MODE(profile)) {
case AUDIT_ALL:
@@ -383,7 +384,7 @@ void aa_apply_modes_to_perms(struct aa_profile *profile, struct aa_perms *perms)
perms->prompt = ALL_PERMS_MASK;
}
-void aa_profile_match_label(struct aa_profile *profile,
+void aa_profile_match_label(const struct aa_profile *profile,
struct aa_ruleset *rules,
struct aa_label *label,
int type, u32 request, struct aa_perms *perms)
diff --git a/security/apparmor/mount.c b/security/apparmor/mount.c
index 31f792817643c..f2cf3ec853e7d 100644
--- a/security/apparmor/mount.c
+++ b/security/apparmor/mount.c
@@ -278,7 +278,7 @@ static int do_match_mnt(struct aa_policydb *policy, aa_state_t start,
}
-static int path_flags(struct aa_profile *profile, const struct path *path)
+static int path_flags(const struct aa_profile *profile, const struct path *path)
{
AA_BUG(!profile);
AA_BUG(!path);
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH 6.18.y 4/4] apparmor: fix profile leak when a policy load fails
2026-11-12 4:05 ` [PATCH 6.18.y 1/4] apparmor: refactor xattr attachment, to take the file path Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 2/4] apparmor: constify aa_perms parameters that are read-only Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 3/4] apparmor: constify aa_profile parameters on read-only compute paths Sasha Levin
@ 2026-11-12 4:05 ` Sasha Levin
2 siblings, 0 replies; 5+ messages in thread
From: Sasha Levin @ 2026-11-12 4:05 UTC (permalink / raw)
To: stable; +Cc: Georgia Garcia, John Johansen, Sasha Levin
From: Georgia Garcia <georgia.garcia@canonical.com>
[ Upstream commit ce94927fa4173fcc1eca27bdc65a140064c8f1e6 ]
The error paths of aa_replace_profiles() and aa_unpack() do not break
the refcount cycle between label and proxy. They just aa_put_profile()
the new profiles, which drops the label refcount from 2 to 1, and the
proxy keeps the profile alive. Every failed atomic policy load
(e.g. "profile can not be replaced", or a later profile in the blob
failing to unpack) leaks every new profile in the set, along with its
proxy, rules and its loaddata reference.
The leak is usually not visible from userspace, because the raw_data
directory for a failed load is not created, but it becomes visible
when the blob is deduplicated onto an existing loaddata, which then
keeps its policy/raw_data/<rev> directory forever.
Add aa_put_unpublished_profile(), which drops the profile's own proxy
before putting the profile, and use it on those error paths. The
proxy of an unpacked profile is always freshly allocated and not
referenced by anything else before the profile is published, so
putting it is safe.
Fixes: 637f688dc3dc ("apparmor: switch from profiles to using labels on contexts")
Cc: stable@vger.kernel.org
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: John Johansen <john.johansen@canonical.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
security/apparmor/include/policy.h | 20 ++++++++++++++++++++
security/apparmor/policy.c | 3 +++
security/apparmor/policy_unpack.c | 4 +++-
3 files changed, 26 insertions(+), 1 deletion(-)
diff --git a/security/apparmor/include/policy.h b/security/apparmor/include/policy.h
index 69e6c93d2ff04..50dcf0147ae2b 100644
--- a/security/apparmor/include/policy.h
+++ b/security/apparmor/include/policy.h
@@ -408,6 +408,26 @@ static inline void aa_put_profile(struct aa_profile *p)
kref_put(&p->label.count.count, aa_label_kref);
}
+/**
+ * aa_put_unpublished_profile - put a profile that was never made visible
+ * @p: profile (MAYBE NULL)
+ *
+ * A newly allocated profile and its proxy reference each other
+ * (proxy->label holds a label refcount). That cycle is normally broken
+ * when the profile is replaced or removed, which redirects the proxy.
+ * A profile that is discarded before being added to the policy tree
+ * never goes through either path, so break the cycle here, otherwise
+ * the profile, its proxy and its rawdata are leaked.
+ */
+static inline void aa_put_unpublished_profile(struct aa_profile *p)
+{
+ if (p && p->label.proxy) {
+ aa_put_proxy(p->label.proxy);
+ p->label.proxy = NULL;
+ }
+ aa_put_profile(p);
+}
+
static inline int AUDIT_MODE(const struct aa_profile *profile)
{
if (aa_g_audit != AUDIT_NORMAL)
diff --git a/security/apparmor/policy.c b/security/apparmor/policy.c
index 230d8428b4d31..e582df9327037 100644
--- a/security/apparmor/policy.c
+++ b/security/apparmor/policy.c
@@ -1434,6 +1434,9 @@ ssize_t aa_replace_profiles(struct aa_ns *policy_ns, struct aa_label *label,
}
list_for_each_entry_safe(ent, tmp, &lh, list) {
list_del_init(&ent->list);
+ /* new was never added to the policy tree */
+ aa_put_unpublished_profile(ent->new);
+ ent->new = NULL;
aa_load_ent_free(ent);
}
diff --git a/security/apparmor/policy_unpack.c b/security/apparmor/policy_unpack.c
index 8dc2adedc7ae1..3e16612d2284e 100644
--- a/security/apparmor/policy_unpack.c
+++ b/security/apparmor/policy_unpack.c
@@ -1556,11 +1556,13 @@ int aa_unpack(struct aa_loaddata *udata, struct list_head *lh,
fail_profile:
kfree(ns_name);
- aa_put_profile(profile);
+ aa_put_unpublished_profile(profile);
fail:
list_for_each_entry_safe(ent, tmp, lh, list) {
list_del_init(&ent->list);
+ aa_put_unpublished_profile(ent->new);
+ ent->new = NULL;
aa_load_ent_free(ent);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-11-12 4:05 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-07 10:15 FAILED: patch "[PATCH] apparmor: fix profile leak when a policy load fails" failed to apply to 6.18-stable tree Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 1/4] apparmor: refactor xattr attachment, to take the file path Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 2/4] apparmor: constify aa_perms parameters that are read-only Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 3/4] apparmor: constify aa_profile parameters on read-only compute paths Sasha Levin
2026-11-12 4:05 ` [PATCH 6.18.y 4/4] apparmor: fix profile leak when a policy load fails Sasha Levin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox