CVE-2026-97938: reboot: fix cad_pid use-after-free race
In the Linux kernel, the following vulnerability has been resolved:
reboot: fix cadpid use-after-free race
cadpid is a single kernel-wide struct pid pointer. procdocadpid() reads it and passes it to pidvnr() without protecting the lifetime of the referenced struct pid. A concurrent writer can replace cadpid and drop the final reference to the old struct pid after the reader has loaded the pointer but before pidvnr() has finished dereferencing it, causing a use-after-free.
killcadpid() has the same lifetime race when it passes cadpid to killpid().
At the time this issue was reported, an unprivileged user could reach the sysctl through user and PID namespaces because cadpid was registered in pidtable[]. Moving cadpid back to the global reboot sysctl table corrected that namespace and permission mismatch, but did not fix the underlying lifetime race.
Fix this by treating cadpid as an RCU-protected pointer at both read sites and by waiting for a grace period before dropping the old reference on the write side.
callrcu(&oldpid->rcu, ...) cannot be used here because freepid() also queues pid->rcu; queueing the same rcuhead twice can corrupt the RCU callback list.
Original KASAN crash stack: kernel/pid.c:545 pidnrns() # reads freed pid->level kernel/pid.c:556 pidvnr() # calls pidnrns() kernel/pid.c:775 procdocadpid() # calls pidvnr(cadpid)
Affected Software
Event History
Frequently Asked Questions
Who could trigger the vulnerable code path?
At the time the issue was reported, an unprivileged user could reach the cad_pid sysctl through user and PID namespaces because it was registered in pid_table[]. The affected operations also require concurrency between access to cad_pid and a writer replacing it.
Is namespace access alone sufficient to cause the use-after-free?
No. The race occurs when a reader loads cad_pid and dereferences it through pid_vnr(), or passes it to kill_pid(), while a concurrent writer replaces the pointer and drops the final reference to the old struct pid.
How was exposure through namespaces addressed?
cad_pid was moved back to the global reboot sysctl table, correcting the namespace and permission mismatch that had made it reachable through user and PID namespaces. That change alone did not resolve the underlying pointer-lifetime race.
What is the implemented mitigation for the lifetime race?
The fix treats cad_pid as an RCU-protected pointer at both read sites and waits for an RCU grace period before dropping the replaced pid reference. It avoids queueing the pid RCU head twice, since free_pid() also uses pid->rcu.