release_task() keeps a task's thread_pid across the point where it drops tasklist_lock and calls proc_flush_pid(). The commit named in Fixes removed the PID reference. It assumed that the PID cannot go away before this release_task() invocation calls free_pids(). That assumption does not cover references represented only by PIDTYPE links. Process B can still use exiting process A's PID as its session and process group ID. A is reaped with wait4(-1); PID-specific waits take their own PID reference and mask the bug. After A's reaper drops tasklist_lock, B can call setsid(), remove the final PIDTYPE links, and queue the PID from B's own free_pids() call. The RCU callback can then free the object before the reaper dereferences pid->inodes and pid->lock in proc_flush_pid(). A timing-only diagnostic forced this legal ordering on final v7.2. It only gated the real setsid(), RCU callback, and proc_flush_pid() operations; it did not change PID links or reference counts. Three of three fresh 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 CPU: 0 UID: 65534 PID: 1921 Comm: h7_pid_reaper 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. Restoring the balanced PID reference completed without a KASAN report on three of three fresh boots: the concurrent RCU callback reduced the count from two to one, proc_flush_pid() completed, and the balancing put_pid() performed the final free. Public syzbot reports have independently observed proc_flush_pid() reading a freed pid-cache object, including a last reference released by a proc inode callback. Pin thread_pid explicitly so every last-reference path is excluded until 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: syzbot+0aee5e8066eddbbe7397@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0aee5e8066eddbbe7397 Reported-by: syzbot+e8b3520b53e78e90034e@syzkaller.appspotmail.com Link: https://syzkaller.appspot.com/bug?extid=e8b3520b53e78e90034e Cc: # see patch description, needs adjustments for 6.16.y-6.18.y Assisted-by: LLM Signed-off-by: Daehyeon Ko <4ncienth@gmail.com> --- kernel/exit.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kernel/exit.c b/kernel/exit.c index 97686af895013b..461e2834fcb45f 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -261,8 +261,8 @@ 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() may set it to NULL. */ + thread_pid = get_pid(task_pid(p)); write_lock_irq(&tasklist_lock); ptrace_release_task(p); @@ -291,8 +291,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)); base-commit: cf72cbb39da84b6f02f90c07f33b102fc10b16f0 -- 2.54.0