From: Sasha Levin Date: Sun, 26 Jul 2026 21:24:30 +0000 (-0400) Subject: Fixes for all trees X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=b38e4191ba3fee77812800bc2a4bd2bfb0608521;p=thirdparty%2Fkernel%2Fstable-queue.git Fixes for all trees Signed-off-by: Sasha Levin --- diff --git a/queue-5.10/kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch b/queue-5.10/kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch new file mode 100644 index 0000000000..0d36a4af79 --- /dev/null +++ b/queue-5.10/kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch @@ -0,0 +1,70 @@ +From b2d8d89ca4f8d971fd1298ed4bda41fde4935db3 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Sun, 26 Jul 2026 18:44:59 +0800 +Subject: KVM: VMX: Make vmread_error_trampoline() uncallable from C code + +From: Sean Christopherson + +[ Upstream commit 0b5e7a16a0a79a3742f0df9e45bca46f01b40e6a ] + +Declare vmread_error_trampoline() as an opaque symbol so that it cannot +be called from C code, at least not without some serious fudging. The +trampoline always passes parameters on the stack so that the inline +VMREAD sequence doesn't need to clobber registers. regparm(0) was +originally added to document the stack behavior, but it ended up being +confusing because regparm(0) is a nop for 64-bit targets. + +Opportunustically wrap the trampoline and its declaration in #ifdeffery +to make it even harder to invoke incorrectly, to document why it exists, +and so that it's not left behind if/when CONFIG_CC_HAS_ASM_GOTO_OUTPUT +is true for all supported toolchains. + +No functional change intended. + +Cc: Uros Bizjak +Signed-off-by: Sean Christopherson +Link: https://lore.kernel.org/r/20220928232015.745948-1-seanjc@google.com +[ Vivian: Adjust context. Remove check for CONFIG_CC_HAS_ASM_GOTO_OUTPUT + which does not apply for old kernel. ] +Signed-off-by: Vivian Wang +Signed-off-by: Sasha Levin +--- + arch/x86/kvm/vmx/vmx_ops.h | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/arch/x86/kvm/vmx/vmx_ops.h b/arch/x86/kvm/vmx/vmx_ops.h +index 692b0c31c9c82d..b80902686c1e41 100644 +--- a/arch/x86/kvm/vmx/vmx_ops.h ++++ b/arch/x86/kvm/vmx/vmx_ops.h +@@ -13,14 +13,26 @@ + #define __ex(x) __kvm_handle_fault_on_reboot(x) + + asmlinkage void vmread_error(unsigned long field, bool fault); +-__attribute__((regparm(0))) void vmread_error_trampoline(unsigned long field, +- bool fault); + void vmwrite_error(unsigned long field, unsigned long value); + void vmclear_error(struct vmcs *vmcs, u64 phys_addr); + void vmptrld_error(struct vmcs *vmcs, u64 phys_addr); + void invvpid_error(unsigned long ext, u16 vpid, gva_t gva); + void invept_error(unsigned long ext, u64 eptp, gpa_t gpa); + ++/* ++ * The VMREAD error trampoline _always_ uses the stack to pass parameters, even ++ * for 64-bit targets. Preserving all registers allows the VMREAD inline asm ++ * blob to avoid clobbering GPRs, which in turn allows the compiler to better ++ * optimize sequences of VMREADs. ++ * ++ * Declare the trampoline as an opaque label as it's not safe to call from C ++ * code; there is no way to tell the compiler to pass params on the stack for ++ * 64-bit targets. ++ * ++ * void vmread_error_trampoline(unsigned long field, bool fault); ++ */ ++extern unsigned long vmread_error_trampoline; ++ + static __always_inline void vmcs_check16(unsigned long field) + { + BUILD_BUG_ON_MSG(__builtin_constant_p(field) && ((field) & 0x6001) == 0x2000, +-- +2.53.0 + diff --git a/queue-5.10/series b/queue-5.10/series index c4a8a5edea..f8ba8cc0a3 100644 --- a/queue-5.10/series +++ b/queue-5.10/series @@ -7,3 +7,4 @@ can-isotp-fix-use-after-free-race-with-concurrent-ne.patch can-isotp-serialize-tx-state-transitions-under-so-rx.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch +kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch diff --git a/queue-5.15/kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch b/queue-5.15/kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch new file mode 100644 index 0000000000..b31eb3f547 --- /dev/null +++ b/queue-5.15/kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch @@ -0,0 +1,70 @@ +From cb150f985bd456ba99fe8c115425f02217e188bf Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Sun, 26 Jul 2026 18:44:59 +0800 +Subject: KVM: VMX: Make vmread_error_trampoline() uncallable from C code + +From: Sean Christopherson + +[ Upstream commit 0b5e7a16a0a79a3742f0df9e45bca46f01b40e6a ] + +Declare vmread_error_trampoline() as an opaque symbol so that it cannot +be called from C code, at least not without some serious fudging. The +trampoline always passes parameters on the stack so that the inline +VMREAD sequence doesn't need to clobber registers. regparm(0) was +originally added to document the stack behavior, but it ended up being +confusing because regparm(0) is a nop for 64-bit targets. + +Opportunustically wrap the trampoline and its declaration in #ifdeffery +to make it even harder to invoke incorrectly, to document why it exists, +and so that it's not left behind if/when CONFIG_CC_HAS_ASM_GOTO_OUTPUT +is true for all supported toolchains. + +No functional change intended. + +Cc: Uros Bizjak +Signed-off-by: Sean Christopherson +Link: https://lore.kernel.org/r/20220928232015.745948-1-seanjc@google.com +[ Vivian: Adjust context. Remove check for CONFIG_CC_HAS_ASM_GOTO_OUTPUT + which does not apply for old kernel. ] +Signed-off-by: Vivian Wang +Signed-off-by: Sasha Levin +--- + arch/x86/kvm/vmx/vmx_ops.h | 16 ++++++++++++++-- + 1 file changed, 14 insertions(+), 2 deletions(-) + +diff --git a/arch/x86/kvm/vmx/vmx_ops.h b/arch/x86/kvm/vmx/vmx_ops.h +index 9e9ef47e988c12..6d342e4f592082 100644 +--- a/arch/x86/kvm/vmx/vmx_ops.h ++++ b/arch/x86/kvm/vmx/vmx_ops.h +@@ -11,14 +11,26 @@ + #include "x86.h" + + asmlinkage void vmread_error(unsigned long field, bool fault); +-__attribute__((regparm(0))) void vmread_error_trampoline(unsigned long field, +- bool fault); + void vmwrite_error(unsigned long field, unsigned long value); + void vmclear_error(struct vmcs *vmcs, u64 phys_addr); + void vmptrld_error(struct vmcs *vmcs, u64 phys_addr); + void invvpid_error(unsigned long ext, u16 vpid, gva_t gva); + void invept_error(unsigned long ext, u64 eptp, gpa_t gpa); + ++/* ++ * The VMREAD error trampoline _always_ uses the stack to pass parameters, even ++ * for 64-bit targets. Preserving all registers allows the VMREAD inline asm ++ * blob to avoid clobbering GPRs, which in turn allows the compiler to better ++ * optimize sequences of VMREADs. ++ * ++ * Declare the trampoline as an opaque label as it's not safe to call from C ++ * code; there is no way to tell the compiler to pass params on the stack for ++ * 64-bit targets. ++ * ++ * void vmread_error_trampoline(unsigned long field, bool fault); ++ */ ++extern unsigned long vmread_error_trampoline; ++ + static __always_inline void vmcs_check16(unsigned long field) + { + BUILD_BUG_ON_MSG(__builtin_constant_p(field) && ((field) & 0x6001) == 0x2000, +-- +2.53.0 + diff --git a/queue-5.15/series b/queue-5.15/series index 18bca0a39a..5cbf8214f8 100644 --- a/queue-5.15/series +++ b/queue-5.15/series @@ -8,3 +8,4 @@ can-isotp-serialize-tx-state-transitions-under-so-rx.patch dmaengine-sh-rz-dmac-move-interrupt-request-after-ev.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch +kvm-vmx-make-vmread_error_trampoline-uncallable-from.patch diff --git a/queue-6.1/mm-refactor-mm_access-to-not-return-null.patch b/queue-6.1/mm-refactor-mm_access-to-not-return-null.patch new file mode 100644 index 0000000000..888428c747 --- /dev/null +++ b/queue-6.1/mm-refactor-mm_access-to-not-return-null.patch @@ -0,0 +1,132 @@ +From 5309c17af0356d024321c89ff6c1771737e0c2cf Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 24 Sep 2024 21:10:23 +0100 +Subject: mm: refactor mm_access() to not return NULL + +From: Lorenzo Stoakes + +[ Upstream commit cd3f8467afd470ccab0de2fbc7c76664af4a0bac ] + +mm_access() can return NULL if the mm is not found, but this is handled +the same as an error in all callers, with some translating this into an +-ESRCH error. + +Only proc_mem_open() returns NULL if no mm is found, however in this case +it is clearer and makes more sense to explicitly handle the error. +Additionally we take the opportunity to refactor the function to eliminate +unnecessary nesting. + +Simplify things by simply returning -ESRCH if no mm is found - this both +eliminates confusing use of the IS_ERR_OR_NULL() macro, and simplifies +callers which would return -ESRCH by returning this error directly. + +[lorenzo.stoakes@oracle.com: prefer neater pointer error comparison] + Link: https://lkml.kernel.org/r/2fae1834-749a-45e1-8594-5e5979cf7103@lucifer.local +Link: https://lkml.kernel.org/r/20240924201023.193135-1-lorenzo.stoakes@oracle.com +Signed-off-by: Lorenzo Stoakes +Suggested-by: Arnd Bergmann +Cc: Al Viro +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +--- + fs/proc/base.c | 26 ++++++++++++++------------ + kernel/fork.c | 5 +++-- + mm/madvise.c | 4 ++-- + mm/process_vm_access.c | 4 ++-- + 4 files changed, 21 insertions(+), 18 deletions(-) + +diff --git a/fs/proc/base.c b/fs/proc/base.c +index b89e6fbf23266c..6f24023b64f31e 100644 +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -811,19 +811,21 @@ static const struct file_operations proc_single_file_operations = { + struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) + { + struct task_struct *task = get_proc_task(inode); +- struct mm_struct *mm = ERR_PTR(-ESRCH); ++ struct mm_struct *mm; + +- if (task) { +- mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); +- put_task_struct(task); ++ if (!task) ++ return ERR_PTR(-ESRCH); + +- if (!IS_ERR_OR_NULL(mm)) { +- /* ensure this mm_struct can't be freed */ +- mmgrab(mm); +- /* but do not pin its memory */ +- mmput(mm); +- } +- } ++ mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); ++ put_task_struct(task); ++ ++ if (IS_ERR(mm)) ++ return mm == ERR_PTR(-ESRCH) ? NULL : mm; ++ ++ /* ensure this mm_struct can't be freed */ ++ mmgrab(mm); ++ /* but do not pin its memory */ ++ mmput(mm); + + return mm; + } +@@ -2201,7 +2203,7 @@ static int map_files_d_revalidate(struct dentry *dentry, unsigned int flags) + goto out_notask; + + mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); +- if (IS_ERR_OR_NULL(mm)) ++ if (IS_ERR(mm)) + goto out; + + if (!dname_to_vma_addr(dentry, &vm_start, &vm_end)) { +diff --git a/kernel/fork.c b/kernel/fork.c +index db2a9016f636f4..63f56a292f756a 100644 +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -1418,8 +1418,9 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode) + return ERR_PTR(err); + + mm = get_task_mm(task); +- if (mm && mm != current->mm && +- !ptrace_may_access(task, mode)) { ++ if (!mm) { ++ mm = ERR_PTR(-ESRCH); ++ } else if (mm != current->mm && !ptrace_may_access(task, mode)) { + mmput(mm); + mm = ERR_PTR(-EACCES); + } +diff --git a/mm/madvise.c b/mm/madvise.c +index 06c5adcaec5955..a3a3a423e9510c 100644 +--- a/mm/madvise.c ++++ b/mm/madvise.c +@@ -1485,8 +1485,8 @@ SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec, + + /* Require PTRACE_MODE_READ to avoid leaking ASLR metadata. */ + mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); +- if (IS_ERR_OR_NULL(mm)) { +- ret = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ if (IS_ERR(mm)) { ++ ret = PTR_ERR(mm); + goto release_task; + } + +diff --git a/mm/process_vm_access.c b/mm/process_vm_access.c +index 78dfaf9e8990af..025cba94e63ff3 100644 +--- a/mm/process_vm_access.c ++++ b/mm/process_vm_access.c +@@ -200,8 +200,8 @@ static ssize_t process_vm_rw_core(pid_t pid, struct iov_iter *iter, + } + + mm = mm_access(task, PTRACE_MODE_ATTACH_REALCREDS); +- if (!mm || IS_ERR(mm)) { +- rc = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ if (IS_ERR(mm)) { ++ rc = PTR_ERR(mm); + /* + * Explicitly map EACCES to EPERM as EPERM is a more + * appropriate error code for process_vw_readv/writev +-- +2.53.0 + diff --git a/queue-6.1/series b/queue-6.1/series index bd3fdd805f..e4555fae43 100644 --- a/queue-6.1/series +++ b/queue-6.1/series @@ -21,3 +21,4 @@ gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch xprtrdma-clear-receive-side-ownership-pointers-on-re.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch +mm-refactor-mm_access-to-not-return-null.patch diff --git a/queue-6.12/mm-refactor-mm_access-to-not-return-null.patch b/queue-6.12/mm-refactor-mm_access-to-not-return-null.patch new file mode 100644 index 0000000000..aa7bee6bbb --- /dev/null +++ b/queue-6.12/mm-refactor-mm_access-to-not-return-null.patch @@ -0,0 +1,132 @@ +From 7e51752907c9bd2ed0203b7bec215834b16c11ce Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 24 Sep 2024 21:10:23 +0100 +Subject: mm: refactor mm_access() to not return NULL + +From: Lorenzo Stoakes + +[ Upstream commit cd3f8467afd470ccab0de2fbc7c76664af4a0bac ] + +mm_access() can return NULL if the mm is not found, but this is handled +the same as an error in all callers, with some translating this into an +-ESRCH error. + +Only proc_mem_open() returns NULL if no mm is found, however in this case +it is clearer and makes more sense to explicitly handle the error. +Additionally we take the opportunity to refactor the function to eliminate +unnecessary nesting. + +Simplify things by simply returning -ESRCH if no mm is found - this both +eliminates confusing use of the IS_ERR_OR_NULL() macro, and simplifies +callers which would return -ESRCH by returning this error directly. + +[lorenzo.stoakes@oracle.com: prefer neater pointer error comparison] + Link: https://lkml.kernel.org/r/2fae1834-749a-45e1-8594-5e5979cf7103@lucifer.local +Link: https://lkml.kernel.org/r/20240924201023.193135-1-lorenzo.stoakes@oracle.com +Signed-off-by: Lorenzo Stoakes +Suggested-by: Arnd Bergmann +Cc: Al Viro +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +--- + fs/proc/base.c | 26 ++++++++++++++------------ + kernel/fork.c | 5 +++-- + mm/madvise.c | 4 ++-- + mm/process_vm_access.c | 4 ++-- + 4 files changed, 21 insertions(+), 18 deletions(-) + +diff --git a/fs/proc/base.c b/fs/proc/base.c +index 9f032236849783..427fb5abb07c7c 100644 +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -817,19 +817,21 @@ static const struct file_operations proc_single_file_operations = { + struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) + { + struct task_struct *task = get_proc_task(inode); +- struct mm_struct *mm = ERR_PTR(-ESRCH); ++ struct mm_struct *mm; + +- if (task) { +- mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); +- put_task_struct(task); ++ if (!task) ++ return ERR_PTR(-ESRCH); + +- if (!IS_ERR_OR_NULL(mm)) { +- /* ensure this mm_struct can't be freed */ +- mmgrab(mm); +- /* but do not pin its memory */ +- mmput(mm); +- } +- } ++ mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); ++ put_task_struct(task); ++ ++ if (IS_ERR(mm)) ++ return mm == ERR_PTR(-ESRCH) ? NULL : mm; ++ ++ /* ensure this mm_struct can't be freed */ ++ mmgrab(mm); ++ /* but do not pin its memory */ ++ mmput(mm); + + return mm; + } +@@ -2199,7 +2201,7 @@ static int map_files_d_revalidate(struct dentry *dentry, unsigned int flags) + goto out_notask; + + mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); +- if (IS_ERR_OR_NULL(mm)) ++ if (IS_ERR(mm)) + goto out; + + if (!dname_to_vma_addr(dentry, &vm_start, &vm_end)) { +diff --git a/kernel/fork.c b/kernel/fork.c +index 6191f1f8bde86d..2b7577edffa2da 100644 +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -1580,8 +1580,9 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode) + return ERR_PTR(err); + + mm = get_task_mm(task); +- if (mm && mm != current->mm && +- !ptrace_may_access(task, mode)) { ++ if (!mm) { ++ mm = ERR_PTR(-ESRCH); ++ } else if (mm != current->mm && !ptrace_may_access(task, mode)) { + mmput(mm); + mm = ERR_PTR(-EACCES); + } +diff --git a/mm/madvise.c b/mm/madvise.c +index 4a3b609a33e60d..d2b315993a1c01 100644 +--- a/mm/madvise.c ++++ b/mm/madvise.c +@@ -1521,8 +1521,8 @@ SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec, + + /* Require PTRACE_MODE_READ to avoid leaking ASLR metadata. */ + mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); +- if (IS_ERR_OR_NULL(mm)) { +- ret = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ if (IS_ERR(mm)) { ++ ret = PTR_ERR(mm); + goto release_task; + } + +diff --git a/mm/process_vm_access.c b/mm/process_vm_access.c +index b308e96cd05a28..656d3e88755b6f 100644 +--- a/mm/process_vm_access.c ++++ b/mm/process_vm_access.c +@@ -201,8 +201,8 @@ static ssize_t process_vm_rw_core(pid_t pid, struct iov_iter *iter, + } + + mm = mm_access(task, PTRACE_MODE_ATTACH_REALCREDS); +- if (!mm || IS_ERR(mm)) { +- rc = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ if (IS_ERR(mm)) { ++ rc = PTR_ERR(mm); + /* + * Explicitly map EACCES to EPERM as EPERM is a more + * appropriate error code for process_vw_readv/writev +-- +2.53.0 + diff --git a/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch b/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch new file mode 100644 index 0000000000..56080dc573 --- /dev/null +++ b/queue-6.12/posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch @@ -0,0 +1,429 @@ +From cf7b5a3c34e508b49afdda31e7e32ab0d99c7ef9 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Sat, 25 Jul 2026 22:10:32 +0200 +Subject: posix-cpu-timers: Prevent UAF caused by non-leader exec() race + +From: Thomas Gleixner + +commit 920f893f735e92ba3a1cd9256899a186b161928d upstream. + +Wongi and Jungwoo decoded and reported a non-leader exec() related race +which can result in an UAF: + + sys_timer_delete() exec() + posix_cpu_timer_del() + // Observes old leader + p = pid_task(pid, pid_type); de_thread() + switch_leader(); + release_task(old_leader) + __exit_signal(old_leader) + sighand = lock(old_leader, sighand); + posix_cpu_timers*_exit(); + sighand = lock_task_sighand(p) unhash_task(old_leader); + sh = lock(p, sighand) old_leader->sighand = NULL; + unlock(sighand); + (p->sighand == NULL) + unlock(sh) + return NULL; + + // Returns without action + if(!sighand) + return 0; + free_posix_timer(); + +This is "harmless" unless the deleted timer was armed and enqueued in +p->signal because on exec() a TGID targeted timer is inherited. + +As sys_timer_delete() freed the underlying posix timer object +run_posix_cpu_timers() or any timerqueue related add/delete operations on +other timers will access the freed object's timerqueue node, which results +in an UAF. + +There is a similar problem vs. posix_cpu_timer_set(). For regular posix +timers it just transiently returns -ESRCH to user space, but for the use +case in do_cpu_nanosleep() it's the same UAF just that the k_itimer is +allocated on the stack. + +Also posix_cpu_timer_rearm() fails to rearm the timer, which means it stops +to expire. + +While debating solutions Frederic pointed out another problem: + + posix_cpu_timer_del(tmr) + __exit_signal(p) + posix_cpu_timers*_exit(p); + unhash_task(p); + p->sighand = NULL; + sh = lock_task_sighand(p) + sighand = p->sighand; + if (!sighand) + return NULL; + lock(sighand); + + if (!sh) + WARN_ON_ONCE(timer_queued(tmr)); + +On weakly ordered architectures it is not guaranteed that +posix_cpu_timer_del() will observe the stores in posix_cpu_timers*_exit() +when p->sighand is observed as NULL, which means the WARN() can be a false +positive. + +Solve these issues by: + + 1) Changing the store in __exit_signal() to smp_store_release(). + + 2) Adding a smp_acquire__after_ctrl_dep() into the !sighand path + of lock_task_sighand(). + + 3) Creating a helper function for looking up the task and locking sighand + which does not return when sighand == NULL. Instead it retries the + task lookup and only if that fails it gives up. + + 4) Using that helper in the three affected functions. + +#1/#2 ensures that the reader side which observes sighand == NULL also +observes all preceeding stores, i.e. the stores in posix_cpu_timers*_exit() +and the ones in unhash_task(). + +#3 ensures that the above described non-leader exec() situation is handled +gracefully. When the task lookup returns the old leader, but sighand == +NULL then it retries. In the non-leader exec() case the subsequent task +lookup will observe the new leader due to #1/#2. In normal exit() scenarios +the subsequent lookup fails. + +When the task lookup fails, the function also checks whether the timer is +still enqueued and issues a warning if that's the case. Unfortunately there +is nothing which can be done about it, but as the task is already not +longer visible the timer should not be accessed anymore. This check also +requires memory ordering, which is not provided when the first lookup +fails. To achieve that the check is preceeded by a smp_rmb() which pairs +with the smp_wmb() in write_seqlock() in __exit_signal(). That ensures that +the stores in posix_cpu_timers*_exit() are visible. + +The history of the non-leader exec() issue goes back to the early days of +posix CPU timers, which stored a pointer to the group leader task in the +timer. That obviously fails when a non-leader exec() switches the leader. +commit e0a70217107e ("posix-cpu-timers: workaround to suppress the problems +with mt exec") added a temporary workaround for that in 2010 which survived +about 10 years. The fix for the workaround changed the task pointer to a +pid pointer, but failed to see the subtle race described above. So the +Fixes tag picks that commit, which seems to be halfways accurate. + +Thanks to Frederic Weissbecker, Oleg Nesterov and Peter Zijlstra for +review, feedback and suggestions and to Wongi and Jungwoo for the excellent +bug report and analysis! + +Fixes: 55e8c8eb2c7b ("posix-cpu-timers: Store a reference to a pid not a task") +Reported-by: Wongi Lee +Reported-by: Jungwoo Lee +Signed-off-by: Thomas Gleixner +Reviewed-by: Oleg Nesterov +Cc: stable@vger.kernel.org +Signed-off-by: Sasha Levin +--- + kernel/exit.c | 8 +- + kernel/signal.c | 10 +- + kernel/time/posix-cpu-timers.c | 176 ++++++++++++++++++++++----------- + 3 files changed, 132 insertions(+), 62 deletions(-) + +diff --git a/kernel/exit.c b/kernel/exit.c +index e798078f958c89..8ae45b2b8cf948 100644 +--- a/kernel/exit.c ++++ b/kernel/exit.c +@@ -205,7 +205,13 @@ static void __exit_signal(struct task_struct *tsk) + * doing sigqueue_free() if we have SIGQUEUE_PREALLOC signals. + */ + flush_sigqueue(&tsk->pending); +- tsk->sighand = NULL; ++ ++ /* ++ * Ensure that all preceeding state is visible. Pairs with ++ * the smp_acquire__after_ctrl_dep() in the sighand == NULL ++ * path of lock_task_sighand(). ++ */ ++ smp_store_release(&tsk->sighand, NULL); + spin_unlock(&sighand->siglock); + + __cleanup_sighand(sighand); +diff --git a/kernel/signal.c b/kernel/signal.c +index b832158a9c4608..e6e812f8b80ffc 100644 +--- a/kernel/signal.c ++++ b/kernel/signal.c +@@ -1395,8 +1395,16 @@ struct sighand_struct *__lock_task_sighand(struct task_struct *tsk, + rcu_read_lock(); + for (;;) { + sighand = rcu_dereference(tsk->sighand); +- if (unlikely(sighand == NULL)) ++ if (unlikely(sighand == NULL)) { ++ /* ++ * Pairs with the smp_store_release() in ++ * __exit_signal(). It ensures that all state ++ * modifications to the task preceeding the store are ++ * visible to the callers of lock_task_sighand(). ++ */ ++ smp_acquire__after_ctrl_dep(); + break; ++ } + + /* + * This sighand can be already freed and even reused, but +diff --git a/kernel/time/posix-cpu-timers.c b/kernel/time/posix-cpu-timers.c +index 635fac5f5fbd58..f0471fbe87288b 100644 +--- a/kernel/time/posix-cpu-timers.c ++++ b/kernel/time/posix-cpu-timers.c +@@ -462,6 +462,109 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + trigger_base_recalc_expires(timer, p); + } + ++/* ++ * Lookup the task via timer->it.cpu.pid and attempt to lock the task's sighand. ++ * ++ * This can race with the reaping of the task: ++ * ++ * CPU0 CPU1 ++ * ++ * // Finds task ++ * p = pid_task(pid, pid_type); __exit_signal(p) ++ * lock(p, sighand); ++ * posix_cpu_timers*_exit(); ++ * sighand = lock_task_sighand(p); unhash_task(p); ++ * p->sighand = NULL; ++ * unlock(sighand); ++ * ++ * In this case sighand is NULL, which means the task and the associated timer ++ * queue cannot be longer accessed safely. ++ * ++ * __exit_signal() invokes posix_cpu_timers_exit() and if the thread group is ++ * dead it also invokes posix_cpu_timers_group_exit(). These functions delete ++ * all pending timers from the related timer queues. The POSIX timers (k_itimer) ++ * themself are still accessible, but not longer connected to the task. ++ * ++ * exec() works slightly differently. The task which exec()'s terminates all ++ * other threads in the thread group and runs __exit_signal() on them. As the ++ * thread group is not dead they only clean up the per task timers via ++ * posix_cpu_timers_exit(). ++ * ++ * As the TGID on exec() stays the same per process timers stay queued, if they ++ * are armed. This works without a problem when exec() is done by the thread ++ * group leader. If a non-leader thread exec()'s this can end up in the ++ * following scenario: ++ * ++ * CPU0 CPU1 ++ * // Returns old leader ++ * p = pid_task(pid, pid_type); de_thread() ++ * switch_leader() ++ * release_task(old leader) ++ * __exit_signal() ++ * old_leader->sighand = NULL; ++ * // Returns NULL ++ * sighand = lock_task_sighand(p) ++ * ++ * That's problematic for several functions: ++ * ++ * - posix_cpu_timer_del(): If the timer is still enqueued on the task the ++ * underlying k_itimer will be freed which results in a UAF in ++ * run_posix_cpu_timers() or on timerqueue related add/delete operations. ++ * If the timer is not enqueued, the failure is harmless ++ * ++ * - posix_cpu_timer_set(): Independent of the enqueued state that results in a ++ * transient failure which is user space visible (-ESRCH) for regular posix ++ * timers. But for the use case in do_cpu_nanosleep() it's the same UAF ++ * problem just that the timer is allocated on the stack. ++ * ++ * - posix_cpu_timer_rearm(): Timer is not enqueued at that point, but this ++ * silently ignores the rearm request, which is a functional problem as the ++ * timer wont expire anymore. ++ */ ++static struct task_struct *timer_lock_sighand(struct k_itimer *timer, unsigned long *flags) ++{ ++ enum pid_type type = clock_pid_type(timer->it_clock); ++ struct cpu_timer *ctmr = &timer->it.cpu; ++ ++ guard(rcu)(); ++ ++ for (;;) { ++ struct task_struct *t = pid_task(timer->it.cpu.pid, type); ++ ++ /* Fail if the task cannot be found. */ ++ if (!t) ++ break; ++ ++ /* Try to lock the task's sighand */ ++ if (lock_task_sighand(t, flags)) ++ return t; ++ ++ /* ++ * The next PID lookup might either fail or return the new ++ * leader. This is correct for both exit() and exec(). ++ */ ++ } ++ ++ /* ++ * If the timer is still enqueued, warn. There is nothing safe to do ++ * here as there might be two timers in there which are removed in ++ * parallel and that will cause more damage than good. This should never ++ * happen! ++ * ++ * Ensure that the stores to the timer and timerqueue are visible: ++ * ++ * __exit_signal() ++ * posix_cpu_timers*_exit() ++ * write_seqlock(seqlock) ++ * smp_wmb(); <------- ++ * __unhash_process() | !pid_task() ++ * ----> smp_rmb(); ++ * WARN_ON_ONCE(...) ++ */ ++ smp_rmb(); ++ WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); ++ return NULL; ++} + + /* + * Clean up a CPU-clock timer that is about to be destroyed. +@@ -471,29 +574,13 @@ static void disarm_timer(struct k_itimer *timer, struct task_struct *p) + */ + static int posix_cpu_timer_del(struct k_itimer *timer) + { +- struct cpu_timer *ctmr = &timer->it.cpu; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; ++ p = timer_lock_sighand(timer, &flags); + +- /* +- * Protect against sighand release/switch in exit/exec and process/ +- * thread timer list entry concurrent read/writes. +- */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) { +- /* +- * This raced with the reaping of the task. The exit cleanup +- * should have removed this timer from the timer queue. +- */ +- WARN_ON_ONCE(ctmr->head || timerqueue_node_queued(&ctmr->node)); +- } else { ++ if (likely(p)) { + if (timer->it.cpu.firing) + ret = TIMER_RETRY; + else +@@ -502,10 +589,8 @@ static int posix_cpu_timer_del(struct k_itimer *timer) + unlock_task_sighand(p, &flags); + } + +-out: +- rcu_read_unlock(); + if (!ret) +- put_pid(ctmr->pid); ++ put_pid(timer->it.cpu.pid); + + return ret; + } +@@ -627,21 +712,17 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + struct cpu_timer *ctmr = &timer->it.cpu; + u64 old_expires, new_expires, now; +- struct sighand_struct *sighand; + struct task_struct *p; + unsigned long flags; + int ret = 0; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) { +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- rcu_read_unlock(); ++ p = timer_lock_sighand(timer, &flags); ++ /* ++ * If p has just been reaped, we can no longer get any information about ++ * it at all. ++ */ ++ if (!p) + return -ESRCH; +- } + + /* + * Use the to_ktime conversion because that clamps the maximum +@@ -649,20 +730,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + */ + new_expires = ktime_to_ns(timespec64_to_ktime(new->it_value)); + +- /* +- * Protect against sighand release/switch in exit/exec and p->cpu_timers +- * and p->signal->cpu_timers read/write in arm_timer() +- */ +- sighand = lock_task_sighand(p, &flags); +- /* +- * If p has just been reaped, we can no +- * longer get any information about it at all. +- */ +- if (unlikely(sighand == NULL)) { +- rcu_read_unlock(); +- return -ESRCH; +- } +- + /* Retrieve the current expiry time before disarming the timer */ + old_expires = cpu_timer_getexpires(ctmr); + +@@ -693,7 +760,7 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + /* Retry if the timer expiry is running concurrently */ + if (unlikely(ret)) { + unlock_task_sighand(p, &flags); +- goto out; ++ return ret; + } + + /* Convert relative expiry time to absolute */ +@@ -728,8 +795,6 @@ static int posix_cpu_timer_set(struct k_itimer *timer, int timer_flags, + */ + if (!sigev_none && new_expires && now >= new_expires) + cpu_timer_fire(timer); +-out: +- rcu_read_unlock(); + return ret; + } + +@@ -1011,19 +1076,12 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + { + clockid_t clkid = CPUCLOCK_WHICH(timer->it_clock); + struct task_struct *p; +- struct sighand_struct *sighand; + unsigned long flags; + u64 now; + +- rcu_read_lock(); +- p = cpu_timer_task_rcu(timer); +- if (!p) +- goto out; +- +- /* Protect timer list r/w in arm_timer() */ +- sighand = lock_task_sighand(p, &flags); +- if (unlikely(sighand == NULL)) +- goto out; ++ p = timer_lock_sighand(timer, &flags); ++ if (unlikely(!p)) ++ return; + + /* + * Fetch the current sample and update the timer's expiry time. +@@ -1040,8 +1098,6 @@ static void posix_cpu_timer_rearm(struct k_itimer *timer) + */ + arm_timer(timer, p); + unlock_task_sighand(p, &flags); +-out: +- rcu_read_unlock(); + } + + /** +-- +2.53.0 + diff --git a/queue-6.12/series b/queue-6.12/series index f8f3aa0e24..385379a618 100644 --- a/queue-6.12/series +++ b/queue-6.12/series @@ -33,3 +33,5 @@ xprtrdma-clear-receive-side-ownership-pointers-on-re.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch soc-qcom-ice-allow-explicit-votes-on-iface-clock-for.patch +mm-refactor-mm_access-to-not-return-null.patch +posix-cpu-timers-prevent-uaf-caused-by-non-leader-ex.patch diff --git a/queue-6.18/fuse-fix-writeback-array-overflow-when-max_pages-is-.patch b/queue-6.18/fuse-fix-writeback-array-overflow-when-max_pages-is-.patch new file mode 100644 index 0000000000..4ffda8f22d --- /dev/null +++ b/queue-6.18/fuse-fix-writeback-array-overflow-when-max_pages-is-.patch @@ -0,0 +1,56 @@ +From 857a53c685e453c0dad7c6bb50418a597b759fe1 Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Sat, 25 Jul 2026 17:04:08 +0800 +Subject: fuse: fix writeback array overflow when max_pages is one + +From: Junxi Qian + +[ Upstream commit c3880a7b10e487e033dc6f388bda118436566f7a ] + +fuse_iomap_writeback_range() appends one folio pointer and one +fuse_folio_desc for every dirty range that is merged into the current +writeback request. The merge decision checks the byte budget against +fc->max_pages and fc->max_write, but it does not check whether the folio +and descriptor arrays still have another free slot. + +This is not sufficient for fuseblk, where the filesystem block size can +be smaller than PAGE_SIZE. With writeback cache enabled and max_pages +negotiated as one, contiguous sub-page dirty ranges can fit within the +byte budget while spanning more than one folio. The next append can then +write past the one-slot folios and descs arrays. + +Split the request when the number of already attached folios has reached +fc->max_pages. This keeps the folio/descriptor slot accounting in sync +with the send decision. + +Fixes: ef7e7cbb323f ("fuse: use iomap for writeback") +Cc: stable@vger.kernel.org +Reviewed-by: Joanne Koong +Signed-off-by: Junxi Qian +Link: https://patch.msgid.link/20260506122415.205340-1-qjx1298677004@gmail.com +Acked-by: Miklos Szeredi +Signed-off-by: Christian Brauner +Signed-off-by: Sasha Levin +--- + fs/fuse/file.c | 5 ++++- + 1 file changed, 4 insertions(+), 1 deletion(-) + +diff --git a/fs/fuse/file.c b/fs/fuse/file.c +index 79d1b502e73195..585dd90361b654 100644 +--- a/fs/fuse/file.c ++++ b/fs/fuse/file.c +@@ -2140,7 +2140,10 @@ static bool fuse_writepage_need_send(struct fuse_conn *fc, loff_t pos, + + WARN_ON(!ap->num_folios); + +- /* Reached max pages */ ++ /* Reached max pages or max folio slots */ ++ if (ap->num_folios >= fc->max_pages) ++ return true; ++ + if ((bytes + PAGE_SIZE - 1) >> PAGE_SHIFT > fc->max_pages) + return true; + +-- +2.53.0 + diff --git a/queue-6.18/series b/queue-6.18/series index da7a5c9889..05c7a67a5a 100644 --- a/queue-6.18/series +++ b/queue-6.18/series @@ -29,3 +29,4 @@ crypto-tegra-don-t-touch-bo-refcount-in-host1x-bo-pi.patch xprtrdma-clear-receive-side-ownership-pointers-on-re.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch +fuse-fix-writeback-array-overflow-when-max_pages-is-.patch diff --git a/queue-6.6/mm-refactor-mm_access-to-not-return-null.patch b/queue-6.6/mm-refactor-mm_access-to-not-return-null.patch new file mode 100644 index 0000000000..88bdb6857a --- /dev/null +++ b/queue-6.6/mm-refactor-mm_access-to-not-return-null.patch @@ -0,0 +1,132 @@ +From f99ae878d27857cf473a19743d6a59f22b22506e Mon Sep 17 00:00:00 2001 +From: Sasha Levin +Date: Tue, 24 Sep 2024 21:10:23 +0100 +Subject: mm: refactor mm_access() to not return NULL + +From: Lorenzo Stoakes + +[ Upstream commit cd3f8467afd470ccab0de2fbc7c76664af4a0bac ] + +mm_access() can return NULL if the mm is not found, but this is handled +the same as an error in all callers, with some translating this into an +-ESRCH error. + +Only proc_mem_open() returns NULL if no mm is found, however in this case +it is clearer and makes more sense to explicitly handle the error. +Additionally we take the opportunity to refactor the function to eliminate +unnecessary nesting. + +Simplify things by simply returning -ESRCH if no mm is found - this both +eliminates confusing use of the IS_ERR_OR_NULL() macro, and simplifies +callers which would return -ESRCH by returning this error directly. + +[lorenzo.stoakes@oracle.com: prefer neater pointer error comparison] + Link: https://lkml.kernel.org/r/2fae1834-749a-45e1-8594-5e5979cf7103@lucifer.local +Link: https://lkml.kernel.org/r/20240924201023.193135-1-lorenzo.stoakes@oracle.com +Signed-off-by: Lorenzo Stoakes +Suggested-by: Arnd Bergmann +Cc: Al Viro +Signed-off-by: Andrew Morton +Signed-off-by: Sasha Levin +--- + fs/proc/base.c | 26 ++++++++++++++------------ + kernel/fork.c | 5 +++-- + mm/madvise.c | 4 ++-- + mm/process_vm_access.c | 4 ++-- + 4 files changed, 21 insertions(+), 18 deletions(-) + +diff --git a/fs/proc/base.c b/fs/proc/base.c +index da5c436ea36fd6..881c2d4846d7ce 100644 +--- a/fs/proc/base.c ++++ b/fs/proc/base.c +@@ -811,19 +811,21 @@ static const struct file_operations proc_single_file_operations = { + struct mm_struct *proc_mem_open(struct inode *inode, unsigned int mode) + { + struct task_struct *task = get_proc_task(inode); +- struct mm_struct *mm = ERR_PTR(-ESRCH); ++ struct mm_struct *mm; + +- if (task) { +- mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); +- put_task_struct(task); ++ if (!task) ++ return ERR_PTR(-ESRCH); + +- if (!IS_ERR_OR_NULL(mm)) { +- /* ensure this mm_struct can't be freed */ +- mmgrab(mm); +- /* but do not pin its memory */ +- mmput(mm); +- } +- } ++ mm = mm_access(task, mode | PTRACE_MODE_FSCREDS); ++ put_task_struct(task); ++ ++ if (IS_ERR(mm)) ++ return mm == ERR_PTR(-ESRCH) ? NULL : mm; ++ ++ /* ensure this mm_struct can't be freed */ ++ mmgrab(mm); ++ /* but do not pin its memory */ ++ mmput(mm); + + return mm; + } +@@ -2201,7 +2203,7 @@ static int map_files_d_revalidate(struct dentry *dentry, unsigned int flags) + goto out_notask; + + mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); +- if (IS_ERR_OR_NULL(mm)) ++ if (IS_ERR(mm)) + goto out; + + if (!dname_to_vma_addr(dentry, &vm_start, &vm_end)) { +diff --git a/kernel/fork.c b/kernel/fork.c +index 724040ac589501..36854aaec482d6 100644 +--- a/kernel/fork.c ++++ b/kernel/fork.c +@@ -1570,8 +1570,9 @@ struct mm_struct *mm_access(struct task_struct *task, unsigned int mode) + return ERR_PTR(err); + + mm = get_task_mm(task); +- if (mm && mm != current->mm && +- !ptrace_may_access(task, mode)) { ++ if (!mm) { ++ mm = ERR_PTR(-ESRCH); ++ } else if (mm != current->mm && !ptrace_may_access(task, mode)) { + mmput(mm); + mm = ERR_PTR(-EACCES); + } +diff --git a/mm/madvise.c b/mm/madvise.c +index 73ea053c9003ab..0c30710bfd859f 100644 +--- a/mm/madvise.c ++++ b/mm/madvise.c +@@ -1492,8 +1492,8 @@ SYSCALL_DEFINE5(process_madvise, int, pidfd, const struct iovec __user *, vec, + + /* Require PTRACE_MODE_READ to avoid leaking ASLR metadata. */ + mm = mm_access(task, PTRACE_MODE_READ_FSCREDS); +- if (IS_ERR_OR_NULL(mm)) { +- ret = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ if (IS_ERR(mm)) { ++ ret = PTR_ERR(mm); + goto release_task; + } + +diff --git a/mm/process_vm_access.c b/mm/process_vm_access.c +index 0523edab03a6a5..5be8e91aa87209 100644 +--- a/mm/process_vm_access.c ++++ b/mm/process_vm_access.c +@@ -200,8 +200,8 @@ static ssize_t process_vm_rw_core(pid_t pid, struct iov_iter *iter, + } + + mm = mm_access(task, PTRACE_MODE_ATTACH_REALCREDS); +- if (!mm || IS_ERR(mm)) { +- rc = IS_ERR(mm) ? PTR_ERR(mm) : -ESRCH; ++ if (IS_ERR(mm)) { ++ rc = PTR_ERR(mm); + /* + * Explicitly map EACCES to EPERM as EPERM is a more + * appropriate error code for process_vw_readv/writev +-- +2.53.0 + diff --git a/queue-6.6/series b/queue-6.6/series index 008cb2be13..f1b0ecf287 100644 --- a/queue-6.6/series +++ b/queue-6.6/series @@ -26,3 +26,4 @@ gpu-host1x-fix-use-after-free-in-host1x_bo_clear_cac.patch xprtrdma-clear-receive-side-ownership-pointers-on-re.patch input-ims-pcu-fix-heap-buffer-overflow-in-ims_pcu_pr.patch input-ims-pcu-fix-logic-error-in-packet-reset.patch +mm-refactor-mm_access-to-not-return-null.patch