Skip to contentSkip to conversation

FAILED: patch "[PATCH] exit: hold a reference to thread_pid across proc_flush_pid" failed to apply to 6.18-stable tree

PATCH In review [email protected] 3 participants 6 messages Sep 16 Stable branch

lore
Share & tools
lore

[email protected]

author maintainer
#0 lore raw

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 <[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.18.y
git checkout FETCH_HEAD
git cherry-pick -x cdd812d0683dee14ead02c9eded568685e61b23f
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026091657-curry-myself-be92@gregkh' --subject-prefix 'PATCH 6.18.y' 'HEAD^..'

Possible dependencies:

thanks,

greg k-h

------------------ original commit in Linus's tree ------------------

From cdd812d0683dee14ead02c9eded568685e61b23f Mon Sep 17 00:00:00 2001
From: Daehyeon Ko <[email protected]>
Date: Mon, 31 Aug 2026 09:12:21 +0900
Subject: [PATCH] exit: hold a reference to thread_pid across proc_flush_pid

Commit 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") removed the reference around proc_flush_pid(). It assumed that free_pids(post.pids) at the end of release_task() would keep thread_pid alive until then.

That assumption is wrong. __change_pid() only records a detached PID in post.pids when pid_has_task() is false for every PIDTYPE. If another task still uses the exiting task's PID as its process group or session ID, __unhash_process() removes the exiting task's PIDTYPE_PID link but leaves the PID out of post.pids. release_task() therefore holds no reference to it after dropping tasklist_lock.

The other task can then remove the remaining PIDTYPE links. Its free_pids() call schedules delayed_put_pid(), and the RCU callback can free the PID before the first release_task() reaches proc_flush_pid().

An unprivileged reproducer races wait4(-1) against setsid() to trigger this ordering. Three of three fresh v7.2 KASAN boots reported:

    BUG: KASAN: slab-use-after-free in
    proc_invalidate_siblings_dcache+0x3e2/0x3f0
    Read of size 8 by task h7_pid_reaper/1921

    Call Trace:
     proc_invalidate_siblings_dcache
     release_task
     wait_consider_task
     __do_wait
     do_wait
     kernel_wait4

    Freed by task 0:
     kmem_cache_free
     put_pid
     delayed_put_pid
     rcu_core

    Last potentially related work creation:
     __call_rcu_common
     free_pids
     ksys_setsid

KASAN identified a 144-byte object from the pid cache and located the bad read 80 bytes into the freed object, matching pid->inodes. With an explicit reference, three of three fresh boots completed without a KASAN report. The concurrent RCU callback dropped its reference while proc_flush_pid() was protected, and the balancing put_pid() performed the final free afterward.

Take a reference before __unhash_process() clears p->thread_pid and release it after proc_flush_pid() completes.

A tested source reproducer is available privately on request. No controlled read or write, information leak, or privilege escalation is claimed. The mainline patch applies directly to v6.19.y and newer; v6.16.y through v6.18.y need a context-adjusted backport.

kernel/exit.c +6 -3
index 182c06671c78..e12072f1507c 100644
@@ -261,8 +261,11 @@ void release_task(struct task_struct *p)
 	pidfs_exit(p);
 	cgroup_task_release(p);
 
-	/* Retrieve @thread_pid before __unhash_process() may set it to NULL. */
-	thread_pid = task_pid(p);
+	/*
+	 * Pin @thread_pid before __unhash_process() clears it. The last
+	 * PIDTYPE detach can otherwise free it before proc_flush_pid().
+	 */
+	thread_pid = get_pid(task_pid(p));
 
 	write_lock_irq(&tasklist_lock);
 	ptrace_release_task(p);
@@ -291,8 +294,8 @@ void release_task(struct task_struct *p)
 	}
 
 	write_unlock_irq(&tasklist_lock);
-	/* @thread_pid can't go away until free_pids() below */
 	proc_flush_pid(thread_pid);
+	put_pid(thread_pid);
 	exit_cred_namespaces(p);
 	add_device_randomness(&p->se.sum_exec_runtime,
 			      sizeof(p->se.sum_exec_runtime));
maintainer
#1 lore raw

PATCH 1/3 ns: add __ns_ref_read()

From: Christian Brauner <[email protected]>

[ Upstream commit 6b053576edb12c7739ea9c7c9900031361922631 ]

Implement ns_ref_read() the same way as ns_ref_{get,put}(). No point in making that any more special or different from the other helpers.

Stable-dep-of: cdd812d0683d ("exit: hold a reference to thread_pid across proc_flush_pid")

---
 include/linux/ns_common.h | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)
include/linux/ns_common.h +6 -1
index f3c52904343e7..08ff44e631456 100644
@@ -156,7 +156,12 @@ static __always_inline __must_check bool __ns_ref_get(struct ns_common *ns)
 	return refcount_inc_not_zero(&ns->__ns_ref);
 }
 
-#define ns_ref_read(__ns) refcount_read(&to_ns_common((__ns))->__ns_ref)
+static __always_inline __must_check int __ns_ref_read(const struct ns_common *ns)
+{
+	return refcount_read(&ns->__ns_ref);
+}
+
+#define ns_ref_read(__ns) __ns_ref_read(to_ns_common((__ns)))
 #define ns_ref_inc(__ns) refcount_inc(&to_ns_common((__ns))->__ns_ref)
 #define ns_ref_get(__ns) __ns_ref_get(to_ns_common((__ns)))
 #define ns_ref_put(__ns) __ns_ref_put(to_ns_common((__ns)))
Signature · 1 line
2.53.0
maintainer
#2 lore raw

PATCH 2/3 ns: rename to exit_nsproxy_namespaces()

From: Christian Brauner <[email protected]>

[ Upstream commit 4b06b70c8244b442d58ae0fb59870cf31fdb422e ]

The current naming is very misleading as this really isn't exiting all of the task's namespaces. It is only exiting the namespaces that hang of off nsproxy. Reflect that in the name.

Stable-dep-of: cdd812d0683d ("exit: hold a reference to thread_pid across proc_flush_pid")

---
 include/linux/nsproxy.h | 2 +-
 kernel/cgroup/cgroup.c  | 6 +++---
 kernel/exit.c           | 2 +-
 kernel/fork.c           | 2 +-
 kernel/nsproxy.c        | 2 +-
 5 files changed, 7 insertions(+), 7 deletions(-)
include/linux/nsproxy.h +1 -1
index bd118a187dec1..538ba8dba1840 100644
@@ -93,7 +93,7 @@ static inline struct cred *nsset_cred(struct nsset *set)
  */
 
 int copy_namespaces(u64 flags, struct task_struct *tsk);
-void exit_task_namespaces(struct task_struct *tsk);
+void exit_nsproxy_namespaces(struct task_struct *tsk);
 void switch_task_namespaces(struct task_struct *tsk, struct nsproxy *new);
 int exec_task_namespaces(void);
 void free_nsproxy(struct nsproxy *ns);
kernel/cgroup/cgroup.c +3 -3
index b2bb2368d93dd..5ae4c27e3b3d3 100644
@@ -1522,9 +1522,9 @@ static struct cgroup *current_cgns_cgroup_dfl(void)
 	} else {
 		/*
 		 * NOTE: This function may be called from bpf_cgroup_from_id()
-		 * on a task which has already passed exit_task_namespaces() and
-		 * nsproxy == NULL. Fall back to cgrp_dfl_root which will make all
-		 * cgroups visible for lookups.
+		 * on a task which has already passed exit_nsproxy_namespaces()
+		 * and nsproxy == NULL. Fall back to cgrp_dfl_root which will
+		 * make all cgroups visible for lookups.
 		 */
 		return &cgrp_dfl_root.cgrp;
 	}
kernel/exit.c +1 -1
index a32bc65569a50..b22ede84c0ec9 100644
@@ -973,7 +973,7 @@ void __noreturn do_exit(long code)
 	exit_fs(tsk);
 	if (group_dead)
 		disassociate_ctty(1);
-	exit_task_namespaces(tsk);
+	exit_nsproxy_namespaces(tsk);
 	exit_task_work(tsk);
 	exit_thread(tsk);
 
kernel/fork.c +1 -1
index 91f1512a1d312..295cf8cf719ea 100644
@@ -2455,7 +2455,7 @@ __latent_entropy struct task_struct *copy_process(
 	if (p->io_context)
 		exit_io_context(p);
 bad_fork_cleanup_namespaces:
-	exit_task_namespaces(p);
+	exit_nsproxy_namespaces(p);
 bad_fork_cleanup_mm:
 	if (p->mm) {
 		mm_clear_owner(p->mm, p);
kernel/nsproxy.c +1 -1
index 19aa64ab08c83..6ce76a0278ab4 100644
@@ -241,7 +241,7 @@ void switch_task_namespaces(struct task_struct *p, struct nsproxy *new)
 		put_nsproxy(ns);
 }
 
-void exit_task_namespaces(struct task_struct *p)
+void exit_nsproxy_namespaces(struct task_struct *p)
 {
 	switch_task_namespaces(p, NULL);
 }
Signature · 1 line
2.53.0
maintainer
#3 lore raw

PATCH 3/3 exit: hold a reference to thread_pid across proc_flush_pid

In reply to Sasha Levin

From: Daehyeon Ko <[email protected]>

[ Upstream commit cdd812d0683dee14ead02c9eded568685e61b23f ]

Commit 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") removed the reference around proc_flush_pid(). It assumed that free_pids(post.pids) at the end of release_task() would keep thread_pid alive until then.

That assumption is wrong. __change_pid() only records a detached PID in post.pids when pid_has_task() is false for every PIDTYPE. If another task still uses the exiting task's PID as its process group or session ID, __unhash_process() removes the exiting task's PIDTYPE_PID link but leaves the PID out of post.pids. release_task() therefore holds no reference to it after dropping tasklist_lock.

The other task can then remove the remaining PIDTYPE links. Its free_pids() call schedules delayed_put_pid(), and the RCU callback can free the PID before the first release_task() reaches proc_flush_pid().

An unprivileged reproducer races wait4(-1) against setsid() to trigger this ordering. Three of three fresh v7.2 KASAN boots reported:

    BUG: KASAN: slab-use-after-free in
    proc_invalidate_siblings_dcache+0x3e2/0x3f0
    Read of size 8 by task h7_pid_reaper/1921

    Call Trace:
     proc_invalidate_siblings_dcache
     release_task
     wait_consider_task
     __do_wait
     do_wait
     kernel_wait4

    Freed by task 0:
     kmem_cache_free
     put_pid
     delayed_put_pid
     rcu_core

    Last potentially related work creation:
     __call_rcu_common
     free_pids
     ksys_setsid

KASAN identified a 144-byte object from the pid cache and located the bad read 80 bytes into the freed object, matching pid->inodes. With an explicit reference, three of three fresh boots completed without a KASAN report. The concurrent RCU callback dropped its reference while proc_flush_pid() was protected, and the balancing put_pid() performed the final free afterward.

Take a reference before __unhash_process() clears p->thread_pid and release it after proc_flush_pid() completes.

A tested source reproducer is available privately on request. No controlled read or write, information leak, or privilege escalation is claimed. The mainline patch applies directly to v6.19.y and newer; v6.16.y through v6.18.y need a context-adjusted backport.

Fixes 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)")
Reported-by [email protected]
Reported-by [email protected]
Cc [email protected] # see patch description, needs adjustments for 6.16.y-6.18.y
[ Adjusted context for cgroup_release() and the missing exit_cred_namespaces() call. ]
---
 kernel/exit.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)
kernel/exit.c +6 -3
index b22ede84c0ec9..d60c1241923a9 100644
@@ -264,8 +264,11 @@ void release_task(struct task_struct *p)
 	pidfs_exit(p);
 	cgroup_release(p);
 
-	/* Retrieve @thread_pid before __unhash_process() may set it to NULL. */
-	thread_pid = task_pid(p);
+	/*
+	 * Pin @thread_pid before __unhash_process() clears it. The last
+	 * PIDTYPE detach can otherwise free it before proc_flush_pid().
+	 */
+	thread_pid = get_pid(task_pid(p));
 
 	write_lock_irq(&tasklist_lock);
 	ptrace_release_task(p);
@@ -294,8 +297,8 @@ void release_task(struct task_struct *p)
 	}
 
 	write_unlock_irq(&tasklist_lock);
-	/* @thread_pid can't go away until free_pids() below */
 	proc_flush_pid(thread_pid);
+	put_pid(thread_pid);
 	add_device_randomness(&p->se.sum_exec_runtime,
 			      sizeof(p->se.sum_exec_runtime));
 	free_pids(post.pids);
Signature · 1 line
2.53.0
#4 lore raw

Re: [PATCH 6.18.y 3/3] exit: hold a reference to thread_pid across proc_flush_pid

Quoted 75 lines · Sasha Levin

From: Daehyeon Ko <[email protected]>

[ Upstream commit cdd812d0683dee14ead02c9eded568685e61b23f ]

Commit 0a36bad01731 ("release_task: kill the no longer needed get/put_pid(thread_pid)") removed the reference around proc_flush_pid(). It assumed that free_pids(post.pids) at the end of release_task() would keep thread_pid alive until then.

That assumption is wrong. __change_pid() only records a detached PID in post.pids when pid_has_task() is false for every PIDTYPE. If another task still uses the exiting task's PID as its process group or session ID, __unhash_process() removes the exiting task's PIDTYPE_PID link but leaves the PID out of post.pids. release_task() therefore holds no reference to it after dropping tasklist_lock.

The other task can then remove the remaining PIDTYPE links. Its free_pids() call schedules delayed_put_pid(), and the RCU callback can free
the PID before the first release_task() reaches proc_flush_pid().

An unprivileged reproducer races wait4(-1) against setsid() to trigger this
ordering. Three of three fresh v7.2 KASAN boots reported:

   BUG: KASAN: slab-use-after-free in
   proc_invalidate_siblings_dcache+0x3e2/0x3f0
   Read of size 8 by task h7_pid_reaper/1921

   Call Trace:
    proc_invalidate_siblings_dcache
    release_task
    wait_consider_task
    __do_wait
    do_wait
    kernel_wait4

   Freed by task 0:
    kmem_cache_free
    put_pid
    delayed_put_pid
    rcu_core

   Last potentially related work creation:
    __call_rcu_common
    free_pids
    ksys_setsid

KASAN identified a 144-byte object from the pid cache and located the bad read 80 bytes into the freed object, matching pid->inodes. With an explicit reference, three of three fresh boots completed without a KASAN report. The concurrent RCU callback dropped its reference while proc_flush_pid() was protected, and the balancing put_pid() performed the final free afterward.

Take a reference before __unhash_process() clears p->thread_pid and release
it after proc_flush_pid() completes.

A tested source reproducer is available privately on request. No controlled read or write, information leak, or privilege escalation is claimed. The mainline patch applies directly to v6.19.y and newer; v6.16.y through v6.18.y need a context-adjusted backport.

Quoted 2 lines

[ Adjusted context for cgroup_release() and the missing exit_cred_namespaces() call. ]

Do you manually do all these? Or use a AI program to do these.

If you use a AI program to do automated backporting, please add something like:

[LLM: Thing it's done to fix the conflict]

If you do all these manually then Im shocked (also thank you)

Quoted 25 lines

---
kernel/exit.c | 9 ++++++---

1 file changed, 6 insertions(+), 3 deletions(-)
kernel/exit.c +5 -2
index b22ede84c0ec9..d60c1241923a9 100644
@@ -264,8 +264,11 @@ void release_task(struct task_struct *p)
 	pidfs_exit(p);
 	cgroup_release(p);
 
-	/* Retrieve @thread_pid before __unhash_process() may set it to NULL. */
-	thread_pid = task_pid(p);
+	/*
+	 * Pin @thread_pid before __unhash_process() clears it. The last
+	 * PIDTYPE detach can otherwise free it before proc_flush_pid().
+	 */
+	thread_pid = get_pid(task_pid(p));
 
 	write_lock_irq(&tasklist_lock);
 	ptrace_release_task(p);

kernel/exit.c void release_task(struct task_struct *p) · line 304

Quoted 9 lines
kernel/exit.c +1 -1
@@ -294,8 +297,8 @@ void release_task(struct task_struct *p)
 	}
 
 	write_unlock_irq(&tasklist_lock);
-	/* @thread_pid can't go away until free_pids() below */
 	proc_flush_pid(thread_pid);
+	put_pid(thread_pid);
 	add_device_randomness(&p->se.sum_exec_runtime,
 			      sizeof(p->se.sum_exec_runtime));
 	free_pids(post.pids);

--- Thanks!
https://lore.kernel.org/all/[email protected]/

maintainer
#5 lore raw

Re: [PATCH 6.18.y 3/3] exit: hold a reference to thread_pid across proc_flush_pid

Quoted 1 line · Bradley Morgan

Do you manually do all these? Or use a AI program to do these.

Some LLM, and some other tools that don't involve LLMs.

Quoted 4 lines

If you use a AI program to do automated backporting, please add something like:

[LLM: Thing it's done to fix the conflict]

That gets added on commits that get modified by the LLM to backport cleanly. An example would be:

	https://lore.kernel.org/all/[email protected]/

In this instance, there were no LLM tools involved in the resolution of the conflict. Instead, I took two prerequesite commits which allowed this last one to apply cleanly, thus there is no backport note on it.

Signature · 2 lines
Thanks,
Sasha