--- /dev/null
+From 31e62c2ebbfdc3fe3dbdf5e02c92a9dc67087a3a Mon Sep 17 00:00:00 2001
+From: Linus Torvalds <torvalds@linux-foundation.org>
+Date: Wed, 13 May 2026 11:37:18 -0700
+Subject: ptrace: slightly saner 'get_dumpable()' logic
+
+From: Linus Torvalds <torvalds@linux-foundation.org>
+
+commit 31e62c2ebbfdc3fe3dbdf5e02c92a9dc67087a3a upstream.
+
+The 'dumpability' of a task is fundamentally about the memory image of
+the task - the concept comes from whether it can core dump or not - and
+makes no sense when you don't have an associated mm.
+
+And almost all users do in fact use it only for the case where the task
+has a mm pointer.
+
+But we have one odd special case: ptrace_may_access() uses 'dumpable' to
+check various other things entirely independently of the MM (typically
+explicitly using flags like PTRACE_MODE_READ_FSCREDS). Including for
+threads that no longer have a VM (and maybe never did, like most kernel
+threads).
+
+It's not what this flag was designed for, but it is what it is.
+
+The ptrace code does check that the uid/gid matches, so you do have to
+be uid-0 to see kernel thread details, but this means that the
+traditional "drop capabilities" model doesn't make any difference for
+this all.
+
+Make it all make a *bit* more sense by saying that if you don't have a
+MM pointer, we'll use a cached "last dumpability" flag if the thread
+ever had a MM (it will be zero for kernel threads since it is never
+set), and require a proper CAP_SYS_PTRACE capability to override.
+
+Reported-by: Qualys Security Advisory <qsa@qualys.com>
+Cc: Oleg Nesterov <oleg@redhat.com>
+Cc: Kees Cook <kees@kernel.org>
+Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
+Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
+---
+ include/linux/sched.h | 3 +++
+ kernel/exit.c | 1 +
+ kernel/ptrace.c | 22 ++++++++++++++++------
+ 3 files changed, 20 insertions(+), 6 deletions(-)
+
+--- a/include/linux/sched.h
++++ b/include/linux/sched.h
+@@ -805,6 +805,9 @@ struct task_struct {
+ */
+ unsigned sched_remote_wakeup:1;
+
++ /* Save user-dumpable when mm goes away */
++ unsigned user_dumpable:1;
++
+ /* Bit to tell LSMs we're in execve(): */
+ unsigned in_execve:1;
+ unsigned in_iowait:1;
+--- a/kernel/exit.c
++++ b/kernel/exit.c
+@@ -528,6 +528,7 @@ static void exit_mm(void)
+ BUG_ON(mm != current->active_mm);
+ /* more a memory barrier than a real lock */
+ task_lock(current);
++ current->user_dumpable = (get_dumpable(mm) == SUID_DUMP_USER);
+ current->mm = NULL;
+ mmap_read_unlock(mm);
+ enter_lazy_tlb(mm, current);
+--- a/kernel/ptrace.c
++++ b/kernel/ptrace.c
+@@ -287,11 +287,24 @@ static bool ptrace_has_cap(struct user_n
+ return ns_capable(ns, CAP_SYS_PTRACE);
+ }
+
++static bool task_still_dumpable(struct task_struct *task, unsigned int mode)
++{
++ struct mm_struct *mm = task->mm;
++ if (mm) {
++ if (get_dumpable(mm) == SUID_DUMP_USER)
++ return true;
++ return ptrace_has_cap(mm->user_ns, mode);
++ }
++
++ if (task->user_dumpable)
++ return true;
++ return ptrace_has_cap(&init_user_ns, mode);
++}
++
+ /* Returns 0 on success, -errno on denial. */
+ static int __ptrace_may_access(struct task_struct *task, unsigned int mode)
+ {
+ const struct cred *cred = current_cred(), *tcred;
+- struct mm_struct *mm;
+ kuid_t caller_uid;
+ kgid_t caller_gid;
+
+@@ -352,11 +365,8 @@ ok:
+ * Pairs with a write barrier in commit_creds().
+ */
+ smp_rmb();
+- mm = task->mm;
+- if (mm &&
+- ((get_dumpable(mm) != SUID_DUMP_USER) &&
+- !ptrace_has_cap(mm->user_ns, mode)))
+- return -EPERM;
++ if (!task_still_dumpable(task, mode))
++ return -EPERM;
+
+ return security_ptrace_access_check(task, mode);
+ }