FAILED: patch "[PATCH] apparmor: fix cred UAF caused by" failed to apply to 6.6-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.6-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 <[email protected]>.
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.6.y
git checkout FETCH_HEAD
git cherry-pick -x 3f4ae5fab613dca01d6a2a8210dd832e009fcf47
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090358-postnasal-expediter-69a3@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 3f4ae5fab613dca01d6a2a8210dd832e009fcf47 Mon Sep 17 00:00:00 2001
From: Jann Horn <[email protected]>
Date: Thu, 6 Aug 2026 17:55:02 +0200
Subject: [PATCH] apparmor: fix cred UAF caused by
begin_current_label_crit_section()
AppArmor's begin_current_label_crit_section() is a scary function called from lots of LSM hooks (in particular VFS/socket-related ones) that checks if the label referenced by the current creds is marked FLAG_STALE, and if so, attempts to use aa_replace_current_label() to replace the creds with an updated version that uses a new label.
The first problem with this is that it would directly lead to UAF of struct cred if anything in the kernel takes a pointer to the current creds and accesses these past a security hook invocation that replaces creds, like so:
``` const struct cred *cred = current_cred(); alloc_file_pseudo(...); uid_t uid = cred->euid; ```
I don't know if anything in the kernel actually does this, but I think it is very surprising that this pattern could lead to UAF.
The second problem is that things go wrong when aa_replace_current_label() runs with overridden credentials. aa_replace_current_label() bails out if current_cred() != current_real_cred() (mirroring the check in proc_pid_attr_write()), but this check can't actually reliably detect overridden credentials because the overridden creds can be the same as the objective creds.
So in approximately the following scenario, things go wrong:
1. task begins with <creds A> (as both objective and subjective creds), with refcount=2
2. task grabs an extra reference on <creds A> for overriding
3. task calls override_creds(<creds A>), which returns a pointer to the old subjective creds (<creds A>)
4. task enters AppArmor LSM hook
5. AppArmor checks that objective/subjective creds are equal
6. AppArmor replaces both cred pointers with <creds B> and drops 2 refs on <creds A>
7. task leaves AppArmor LSM hook
8. task calls revert_creds(<creds A>)
9. now task->cred is <creds A> while task->real_cred is <creds B>, but the task_struct logically holds two references to <creds B>
10. another task drops the extra reference on <creds A> that was used for overriding, refcount drops to 0
11. now task->real_cred points to freed creds
At this point, any access to current_cred() will be UAF.
I have a test case where I run aa-disable on a profile while a process using that profile is blocked on splice() from a FUSE passthrough file into a full pipe; after the profile update, the pipe becomes empty, splice() resumes, the credentials go out of sync, and a subsequent getuid() syscall results in a KASAN UAF splat.
To fix this, instead of directly replacing creds, do it via task_work that will run at the end of the current syscall. (The point in time at which the cred replacement happens should have no correctness impact; it is just a performance optimization to avoid unnecessarily touching the refcount of the new label.)
Note that AppArmor still performs direct cred replacements in the sb_pivotroot LSM hook after this change, and that direct cred replacements can still happen in VFS ->write() callbacks via proc_pid_attr_write().
There are two options for what to do with aa_dup_task_ctx(): Either explicitly reset new->label_replacement_pending after the entire aa_task_ctx has been copied, or switch to manually copying members over. I am switching to manually copying members over because that should make bugs more obvious.
security/apparmor/include/cred.h +1 -5
@@ -222,13 +222,9 @@ static inline struct aa_label *begin_current_label_crit_section(void) {struct aa_label *label =aa_current_raw_label (); -might_sleep (); -if (label_is_stale (label)) { label =aa_get_newest_label (label); -if (aa_replace_current_label (label) ==0 ) -/* task cred will keep the reference */ -aa_put_label (label); +aa_schedule_stale_label_replacement (); }return label;
security/apparmor/include/task.h +11 -4
@@ -26,15 +26,22 @@ static inline struct aa_task_ctx *task_ctx(struct task_struct *task)* @onexec: profile to transition to on next exec (MAY BE NULL) * @previous: profile the task may return to (MAY BE NULL) * @token: magic value the task must know for returning to @previous_profile +* @label_replacement_tw: for aa_schedule_stale_label_replacement() +* @label_replacement_pending: is @label_replacement_tw pending? +* +* When changing this, check if aa_dup_task_ctx() needs to be updated. */ struct aa_task_ctx {struct aa_label *nnp;struct aa_label *onexec;struct aa_label *previous;u64 token; +struct callback_head label_replacement_tw; +bool label_replacement_pending; };int aa_replace_current_label (struct aa_label *label); +void aa_schedule_stale_label_replacement (void );void aa_set_current_onexec (struct aa_label *label,bool stack);int aa_set_current_hat (struct aa_label *label,u64 token);int aa_restore_previous_label (u64 cookie);
@@ -61,10 +68,10 @@ static inline void aa_free_task_ctx(struct aa_task_ctx *ctx)static inline void aa_dup_task_ctx (struct aa_task_ctx *new,const struct aa_task_ctx *old) { - *new = *old; -aa_get_label (new->nnp); -aa_get_label (new->previous); -aa_get_label (new->onexec); + new->nnp =aa_get_label (old->nnp); + new->onexec =aa_get_label (old->onexec); + new->previous =aa_get_label (old->previous); + new->token = old->token; }/**
security/apparmor/task.c +27 -0
@@ -14,6 +14,7 @@#include <linux/gfp.h> #include <linux/ptrace.h> +#include <linux/task_work.h> #include "include/path.h" #include "include/audit.h"
@@ -89,6 +90,32 @@ int aa_replace_current_label(struct aa_label *label)return 0 ; } +static void aa_replace_stale_label_tw_func (struct callback_head *tw) +{ +struct aa_task_ctx *ctx =task_ctx (current); +struct aa_label *label; + + ctx->label_replacement_pending = false; + label =aa_current_raw_label (); +if (!label_is_stale (label)) +return ; + label =aa_get_newest_label (label); +aa_replace_current_label (label); +aa_put_label (label); +} + +/* replace the current task's stale label on syscall return */ +void aa_schedule_stale_label_replacement (void ) +{ +struct aa_task_ctx *ctx =task_ctx (current); + +if (ctx->label_replacement_pending) +return ; +init_task_work (&ctx->label_replacement_tw, aa_replace_stale_label_tw_func); +if (task_work_add (current, &ctx->label_replacement_tw,TWA_RESUME ) ==0 ) + ctx->label_replacement_pending = true; +} +/** * aa_set_current_onexec - set the tasks change_profile to happen onexec
Sasha Levin
maintainerPATCH 1/2 apparmor: make aa_set_current_onexec return void
From: Quanfa Fu <[email protected]>
[ Upstream commit 0897fcb1c1e7316375166e0a665237bce2391a09 ]
Change the return type to void since it always return 0, and no need to do the checking in aa_set_current_onexec.
Stable-dep-of: 3f4ae5fab613 ("apparmor: fix cred UAF caused by begin_current_label_crit_section()")
--- security/apparmor/domain.c | 2 +- security/apparmor/include/task.h | 2 +- security/apparmor/task.c | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-)
security/apparmor/domain.c +1 -1
@@ -1498,7 +1498,7 @@ int aa_change_profile(const char *fqname, int flags) }/* full transition will be built in exec path */ - error =aa_set_current_onexec (target, stack); +aa_set_current_onexec (target, stack); } goto audit;
security/apparmor/include/task.h +1 -1
@@ -30,7 +30,7 @@ struct aa_task_ctx { };int aa_replace_current_label (struct aa_label *label); -int aa_set_current_onexec (struct aa_label *label,bool stack); +void aa_set_current_onexec (struct aa_label *label,bool stack);int aa_set_current_hat (struct aa_label *label,u64 token);int aa_restore_previous_label (u64 cookie);struct aa_label *aa_get_task_label (struct task_struct *task);
security/apparmor/task.c +1 -4
@@ -93,9 +93,8 @@ int aa_replace_current_label(struct aa_label *label)* aa_set_current_onexec - set the tasks change_profile to happen onexec * @label: system label to set at exec (MAYBE NULL to clear value) * @stack: whether stacking should be done -* Returns: 0 or error on failure */ -int aa_set_current_onexec (struct aa_label *label,bool stack) +void aa_set_current_onexec (struct aa_label *label,bool stack) {struct aa_task_ctx *ctx =task_ctx (current);
@@ -103,8 +102,6 @@ int aa_set_current_onexec(struct aa_label *label, bool stack)aa_put_label (ctx->onexec); ctx->onexec = label; ctx->token = stack; - -return 0 ; }/**
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/2 apparmor: fix cred UAF caused by begin_current_label_crit_section()
From: Jann Horn <[email protected]>
[ Upstream commit 3f4ae5fab613dca01d6a2a8210dd832e009fcf47 ]
AppArmor's begin_current_label_crit_section() is a scary function called from lots of LSM hooks (in particular VFS/socket-related ones) that checks if the label referenced by the current creds is marked FLAG_STALE, and if so, attempts to use aa_replace_current_label() to replace the creds with an updated version that uses a new label.
The first problem with this is that it would directly lead to UAF of struct cred if anything in the kernel takes a pointer to the current creds and accesses these past a security hook invocation that replaces creds, like so:
``` const struct cred *cred = current_cred(); alloc_file_pseudo(...); uid_t uid = cred->euid; ```
I don't know if anything in the kernel actually does this, but I think it is very surprising that this pattern could lead to UAF.
The second problem is that things go wrong when aa_replace_current_label() runs with overridden credentials. aa_replace_current_label() bails out if current_cred() != current_real_cred() (mirroring the check in proc_pid_attr_write()), but this check can't actually reliably detect overridden credentials because the overridden creds can be the same as the objective creds.
So in approximately the following scenario, things go wrong:
1. task begins with <creds A> (as both objective and subjective creds), with refcount=2
2. task grabs an extra reference on <creds A> for overriding
3. task calls override_creds(<creds A>), which returns a pointer to the old subjective creds (<creds A>)
4. task enters AppArmor LSM hook
5. AppArmor checks that objective/subjective creds are equal
6. AppArmor replaces both cred pointers with <creds B> and drops 2 refs on <creds A>
7. task leaves AppArmor LSM hook
8. task calls revert_creds(<creds A>)
9. now task->cred is <creds A> while task->real_cred is <creds B>, but the task_struct logically holds two references to <creds B>
10. another task drops the extra reference on <creds A> that was used for overriding, refcount drops to 0
11. now task->real_cred points to freed creds
At this point, any access to current_cred() will be UAF.
I have a test case where I run aa-disable on a profile while a process using that profile is blocked on splice() from a FUSE passthrough file into a full pipe; after the profile update, the pipe becomes empty, splice() resumes, the credentials go out of sync, and a subsequent getuid() syscall results in a KASAN UAF splat.
To fix this, instead of directly replacing creds, do it via task_work that will run at the end of the current syscall. (The point in time at which the cred replacement happens should have no correctness impact; it is just a performance optimization to avoid unnecessarily touching the refcount of the new label.)
Note that AppArmor still performs direct cred replacements in the sb_pivotroot LSM hook after this change, and that direct cred replacements can still happen in VFS ->write() callbacks via proc_pid_attr_write().
There are two options for what to do with aa_dup_task_ctx(): Either explicitly reset new->label_replacement_pending after the entire aa_task_ctx has been copied, or switch to manually copying members over. I am switching to manually copying members over because that should make bugs more obvious.
--- security/apparmor/include/cred.h | 6 +----- security/apparmor/include/task.h | 15 +++++++++++---- security/apparmor/task.c | 27 +++++++++++++++++++++++++++ 3 files changed, 39 insertions(+), 9 deletions(-)
security/apparmor/include/cred.h +1 -5
@@ -148,13 +148,9 @@ static inline struct aa_label *begin_current_label_crit_section(void) {struct aa_label *label =aa_current_raw_label (); -might_sleep (); -if (label_is_stale (label)) { label =aa_get_newest_label (label); -if (aa_replace_current_label (label) ==0 ) -/* task cred will keep the reference */ -aa_put_label (label); +aa_schedule_stale_label_replacement (); }return label;
security/apparmor/include/task.h +11 -4
@@ -21,15 +21,22 @@ static inline struct aa_task_ctx *task_ctx(struct task_struct *task)* @onexec: profile to transition to on next exec (MAY BE NULL) * @previous: profile the task may return to (MAY BE NULL) * @token: magic value the task must know for returning to @previous_profile +* @label_replacement_tw: for aa_schedule_stale_label_replacement() +* @label_replacement_pending: is @label_replacement_tw pending? +* +* When changing this, check if aa_dup_task_ctx() needs to be updated. */ struct aa_task_ctx {struct aa_label *nnp;struct aa_label *onexec;struct aa_label *previous;u64 token; +struct callback_head label_replacement_tw; +bool label_replacement_pending; };int aa_replace_current_label (struct aa_label *label); +void aa_schedule_stale_label_replacement (void );void aa_set_current_onexec (struct aa_label *label,bool stack);int aa_set_current_hat (struct aa_label *label,u64 token);int aa_restore_previous_label (u64 cookie);
@@ -56,10 +63,10 @@ static inline void aa_free_task_ctx(struct aa_task_ctx *ctx)static inline void aa_dup_task_ctx (struct aa_task_ctx *new,const struct aa_task_ctx *old) { - *new = *old; -aa_get_label (new->nnp); -aa_get_label (new->previous); -aa_get_label (new->onexec); + new->nnp =aa_get_label (old->nnp); + new->onexec =aa_get_label (old->onexec); + new->previous =aa_get_label (old->previous); + new->token = old->token; }/**
security/apparmor/task.c +27 -0
@@ -14,6 +14,7 @@#include <linux/gfp.h> #include <linux/ptrace.h> +#include <linux/task_work.h> #include "include/audit.h" #include "include/cred.h"
@@ -88,6 +89,32 @@ int aa_replace_current_label(struct aa_label *label)return 0 ; } +static void aa_replace_stale_label_tw_func (struct callback_head *tw) +{ +struct aa_task_ctx *ctx =task_ctx (current); +struct aa_label *label; + + ctx->label_replacement_pending = false; + label =aa_current_raw_label (); +if (!label_is_stale (label)) +return ; + label =aa_get_newest_label (label); +aa_replace_current_label (label); +aa_put_label (label); +} + +/* replace the current task's stale label on syscall return */ +void aa_schedule_stale_label_replacement (void ) +{ +struct aa_task_ctx *ctx =task_ctx (current); + +if (ctx->label_replacement_pending) +return ; +init_task_work (&ctx->label_replacement_tw, aa_replace_stale_label_tw_func); +if (task_work_add (current, &ctx->label_replacement_tw,TWA_RESUME ) ==0 ) + ctx->label_replacement_pending = true; +} +/** * aa_set_current_onexec - set the tasks change_profile to happen onexec
Signature · 1 line
2.53.0