* Re: [PATCH v4] reboot: fix cad_pid use-after-free race
2026-08-14 4:09 [PATCH v4] reboot: fix cad_pid use-after-free race Cen Zhang (Microsoft)
@ 2026-08-14 9:22 ` Bradley Morgan
2026-08-14 18:03 ` Oleg Nesterov
` (3 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Bradley Morgan @ 2026-08-14 9:22 UTC (permalink / raw)
To: Cen Zhang (Microsoft), joel.granados, kees
Cc: brauner, oleg, mingo, peterz, akpm, jack, avagin, ptikhomirov,
mjguzik, ebiederm, legion, linux-kernel, linux-fsdevel,
AutonomousCodeSecurity, tgopinath, kys, blbllhy, stable
On 14 August 2026 05:09:44 BST, "Cen Zhang (Microsoft)" <blbllhy@gmail.com>
wrote:
>cad_pid is a single kernel-wide struct pid pointer. proc_do_cad_pid()
>reads it and passes it to pid_vnr() without protecting the lifetime of
>the referenced struct pid. A concurrent writer can replace cad_pid and
>drop the final reference to the old struct pid after the reader has
>loaded the pointer but before pid_vnr() has finished dereferencing it,
>causing a use-after-free.
>
>kill_cad_pid() has the same lifetime race when it passes cad_pid to
>kill_pid().
>
>At the time this issue was reported, an unprivileged user could reach the
>sysctl through user and PID namespaces because cad_pid was registered in
>pid_table[]. Moving cad_pid 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 cad_pid 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.
>
>call_rcu(&old_pid->rcu, ...) cannot be used here because free_pid()
>also queues pid->rcu; queueing the same rcu_head twice can corrupt the
>RCU callback list.
>
>Original KASAN crash stack:
> kernel/pid.c:545 pid_nr_ns() # reads freed pid->level
> kernel/pid.c:556 pid_vnr() # calls pid_nr_ns()
> kernel/pid.c:775 proc_do_cad_pid() # calls pid_vnr(cad_pid)
>
>Fixes: 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid")
>Reported-by: AutonomousCodeSecurity@microsoft.com
>Closes: https://lore.kernel.org/all/20260717210143.4734-1-blbllhy@gmail.com/
>Link: https://lore.kernel.org/all/alz5ZYLE4kaq_v2P@redhat.com/
>Link: https://lore.kernel.org/all/al4ICz9biJKtdZc4@redhat.com/
>Suggested-by: Mateusz Guzik <mjguzik@gmail.com>
>Suggested-by: Bradley Morgan <include@grrlz.net>
Yk what I'm gonna do...
Reviewed-by: Bradley Morgan <include@grrlz.net>
It kinda of makes sense in reboot code
>Suggested-by: Oleg Nesterov <oleg@redhat.com>
>Suggested-by: Eric W. Biederman <ebiederm@xmission.com>
>Suggested-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
>Cc: stable@vger.kernel.org
>Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
>---
>v4:
> - Rebase on sysctl-next after Oleg's cad_pid sysctl placement change.
> - Fold the two-patch v3 series into a single lifetime fix.
> - Move kill_cad_pid() implementation to kernel/signal.c.
> - Export kill_cad_pid() and stop exporting the raw cad_pid pointer.
> - Update proc_do_cad_pid() in kernel/reboot.c.
> - Add explicit includes for the RCU API and kill_cad_pid().
> - Preserve the original unprivileged impact context while noting that
> the namespace permission path has been fixed separately.
>
>v3:
> - Keep kill_cad_pid() inside the RCU read-side critical section
> instead of taking a pid reference, as suggested by Oleg.
>
>v2:
> - Split out kill_cad_pid() deinline into a preparatory patch.
> - Annotate cad_pid as __rcu and use rcu_dereference().
> - Protect kill_cad_pid() by taking a pid reference under RCU.
> - Add a comment explaining why synchronize_rcu() is used instead of
> call_rcu().
>
> include/linux/sched.h | 2 +-
> include/linux/sched/signal.h | 5 +----
> init/main.c | 2 +-
> kernel/reboot.c | 19 +++++++++++++++----
> kernel/signal.c | 12 ++++++++++++
> 5 files changed, 30 insertions(+), 10 deletions(-)
>
>diff --git a/include/linux/sched.h b/include/linux/sched.h
>index 373bcc0598d1..31ce72b1233c 100644
>--- a/include/linux/sched.h
>+++ b/include/linux/sched.h
>@@ -1767,7 +1767,7 @@ static inline bool is_lazy_mmu_mode_active(void)
> }
> #endif
>
>-extern struct pid *cad_pid;
>+extern struct pid __rcu *cad_pid;
>
> /*
> * Per process flags
>diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
>index 584ae88b435e..d45a5476b97d 100644
>--- a/include/linux/sched/signal.h
>+++ b/include/linux/sched/signal.h
>@@ -562,10 +562,7 @@ static inline sigset_t *sigmask_to_save(void)
> return res;
> }
>
>-static inline int kill_cad_pid(int sig, int priv)
>-{
>- return kill_pid(cad_pid, sig, priv);
>-}
>+int kill_cad_pid(int sig, int priv);
>
> /* These can be the second arg to send_sig_info/send_group_sig_info. */
> #define SEND_SIG_NOINFO ((struct kernel_siginfo *) 0)
>diff --git a/init/main.c b/init/main.c
>index e363232b428b..19a10d0c2760 100644
>--- a/init/main.c
>+++ b/init/main.c
>@@ -1636,7 +1636,7 @@ static noinline void __init kernel_init_freeable(void)
> */
> set_mems_allowed(node_states[N_MEMORY]);
>
>- cad_pid = get_pid(task_pid(current));
>+ rcu_assign_pointer(cad_pid, get_pid(task_pid(current)));
>
> smp_prepare_cpus(setup_max_cpus);
>
>diff --git a/kernel/reboot.c b/kernel/reboot.c
>index f070c5c1103a..d177d89fcc33 100644
>--- a/kernel/reboot.c
>+++ b/kernel/reboot.c
>@@ -13,7 +13,9 @@
> #include <linux/kexec.h>
> #include <linux/kmod.h>
> #include <linux/kmsg_dump.h>
>+#include <linux/rcupdate.h>
> #include <linux/reboot.h>
>+#include <linux/sched/signal.h>
> #include <linux/suspend.h>
> #include <linux/syscalls.h>
> #include <linux/syscore_ops.h>
>@@ -24,8 +26,7 @@
> */
>
> static int C_A_D = 1;
>-struct pid *cad_pid;
>-EXPORT_SYMBOL(cad_pid);
>+struct pid __rcu *cad_pid;
>
> #if defined(CONFIG_ARM)
> #define DEFAULT_REBOOT_MODE = REBOOT_HARD
>@@ -1371,10 +1372,14 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
> {
> struct ctl_table tmp_table = *table;
> struct pid *new_pid;
>+ struct pid *old_pid;
> pid_t tmp_pid;
> int r;
>
>- tmp_pid = pid_vnr(cad_pid);
>+ rcu_read_lock();
>+ tmp_pid = pid_vnr(rcu_dereference(cad_pid));
>+ rcu_read_unlock();
>+
> tmp_table.data = &tmp_pid;
>
> r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
>@@ -1385,7 +1390,13 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
> if (!new_pid)
> return -ESRCH;
>
>- put_pid(xchg(&cad_pid, new_pid));
>+ old_pid = unrcu_pointer(xchg(&cad_pid, RCU_INITIALIZER(new_pid)));
>+ /*
>+ * Wait for cad_pid readers before put_pid(). We cannot use
>+ * call_rcu() here because free_pid() already owns pid->rcu.
>+ */
>+ synchronize_rcu();
>+ put_pid(old_pid);
> return 0;
> }
>
>diff --git a/kernel/signal.c b/kernel/signal.c
>index 9c2b32c4d755..464732c44554 100644
>--- a/kernel/signal.c
>+++ b/kernel/signal.c
>@@ -1891,6 +1891,18 @@ int kill_pid(struct pid *pid, int sig, int priv)
> }
> EXPORT_SYMBOL(kill_pid);
>
>+int kill_cad_pid(int sig, int priv)
>+{
>+ int ret;
>+
>+ rcu_read_lock();
>+ ret = kill_pid(rcu_dereference(cad_pid), sig, priv);
>+ rcu_read_unlock();
>+
>+ return ret;
>+}
>+EXPORT_SYMBOL(kill_cad_pid);
>+
> #ifdef CONFIG_POSIX_TIMERS
> /*
> * These functions handle POSIX timer signals. POSIX timers use
>
>base-commit: 8d75c338f0bcecaa6c9af67f86c176b67b6acf3e
>
Thanks!
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v4] reboot: fix cad_pid use-after-free race
2026-08-14 4:09 [PATCH v4] reboot: fix cad_pid use-after-free race Cen Zhang (Microsoft)
2026-08-14 9:22 ` Bradley Morgan
@ 2026-08-14 18:03 ` Oleg Nesterov
2026-08-18 12:21 ` Pavel Tikhomirov
` (2 subsequent siblings)
4 siblings, 0 replies; 6+ messages in thread
From: Oleg Nesterov @ 2026-08-14 18:03 UTC (permalink / raw)
To: Cen Zhang (Microsoft)
Cc: joel.granados, kees, brauner, mingo, peterz, akpm, jack, avagin,
ptikhomirov, mjguzik, include, ebiederm, legion, linux-kernel,
linux-fsdevel, AutonomousCodeSecurity, tgopinath, kys, stable
On 08/14, Cen Zhang (Microsoft) wrote:
>
> v4:
> - Rebase on sysctl-next after Oleg's cad_pid sysctl placement change.
> - Fold the two-patch v3 series into a single lifetime fix.
> - Move kill_cad_pid() implementation to kernel/signal.c.
> - Export kill_cad_pid() and stop exporting the raw cad_pid pointer.
> - Update proc_do_cad_pid() in kernel/reboot.c.
> - Add explicit includes for the RCU API and kill_cad_pid().
> - Preserve the original unprivileged impact context while noting that
> the namespace permission path has been fixed separately.
I don't see other changes in this version, so
Reviewed-by: Oleg Nesterov <oleg@redhat.com>
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v4] reboot: fix cad_pid use-after-free race
2026-08-14 4:09 [PATCH v4] reboot: fix cad_pid use-after-free race Cen Zhang (Microsoft)
2026-08-14 9:22 ` Bradley Morgan
2026-08-14 18:03 ` Oleg Nesterov
@ 2026-08-18 12:21 ` Pavel Tikhomirov
2026-08-18 12:53 ` Eric W. Biederman
2026-08-31 8:25 ` Christian Brauner
4 siblings, 0 replies; 6+ messages in thread
From: Pavel Tikhomirov @ 2026-08-18 12:21 UTC (permalink / raw)
To: Cen Zhang (Microsoft), joel.granados, kees
Cc: brauner, oleg, mingo, peterz, akpm, jack, avagin, mjguzik,
include, ebiederm, legion, linux-kernel, linux-fsdevel,
AutonomousCodeSecurity, tgopinath, kys, stable
Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
On 8/14/26 06:09, Cen Zhang (Microsoft) wrote:
> cad_pid is a single kernel-wide struct pid pointer. proc_do_cad_pid()
> reads it and passes it to pid_vnr() without protecting the lifetime of
> the referenced struct pid. A concurrent writer can replace cad_pid and
> drop the final reference to the old struct pid after the reader has
> loaded the pointer but before pid_vnr() has finished dereferencing it,
> causing a use-after-free.
>
> kill_cad_pid() has the same lifetime race when it passes cad_pid to
> kill_pid().
>
> At the time this issue was reported, an unprivileged user could reach the
> sysctl through user and PID namespaces because cad_pid was registered in
> pid_table[]. Moving cad_pid 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 cad_pid 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.
>
> call_rcu(&old_pid->rcu, ...) cannot be used here because free_pid()
> also queues pid->rcu; queueing the same rcu_head twice can corrupt the
> RCU callback list.
>
> Original KASAN crash stack:
> kernel/pid.c:545 pid_nr_ns() # reads freed pid->level
> kernel/pid.c:556 pid_vnr() # calls pid_nr_ns()
> kernel/pid.c:775 proc_do_cad_pid() # calls pid_vnr(cad_pid)
>
> Fixes: 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Closes: https://lore.kernel.org/all/20260717210143.4734-1-blbllhy@gmail.com/
> Link: https://lore.kernel.org/all/alz5ZYLE4kaq_v2P@redhat.com/
> Link: https://lore.kernel.org/all/al4ICz9biJKtdZc4@redhat.com/
> Suggested-by: Mateusz Guzik <mjguzik@gmail.com>
> Suggested-by: Bradley Morgan <include@grrlz.net>
> Suggested-by: Oleg Nesterov <oleg@redhat.com>
> Suggested-by: Eric W. Biederman <ebiederm@xmission.com>
> Suggested-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
> ---
> v4:
> - Rebase on sysctl-next after Oleg's cad_pid sysctl placement change.
> - Fold the two-patch v3 series into a single lifetime fix.
> - Move kill_cad_pid() implementation to kernel/signal.c.
> - Export kill_cad_pid() and stop exporting the raw cad_pid pointer.
> - Update proc_do_cad_pid() in kernel/reboot.c.
> - Add explicit includes for the RCU API and kill_cad_pid().
> - Preserve the original unprivileged impact context while noting that
> the namespace permission path has been fixed separately.
>
> v3:
> - Keep kill_cad_pid() inside the RCU read-side critical section
> instead of taking a pid reference, as suggested by Oleg.
>
> v2:
> - Split out kill_cad_pid() deinline into a preparatory patch.
> - Annotate cad_pid as __rcu and use rcu_dereference().
> - Protect kill_cad_pid() by taking a pid reference under RCU.
> - Add a comment explaining why synchronize_rcu() is used instead of
> call_rcu().
>
> include/linux/sched.h | 2 +-
> include/linux/sched/signal.h | 5 +----
> init/main.c | 2 +-
> kernel/reboot.c | 19 +++++++++++++++----
> kernel/signal.c | 12 ++++++++++++
> 5 files changed, 30 insertions(+), 10 deletions(-)
>
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 373bcc0598d1..31ce72b1233c 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1767,7 +1767,7 @@ static inline bool is_lazy_mmu_mode_active(void)
> }
> #endif
>
> -extern struct pid *cad_pid;
> +extern struct pid __rcu *cad_pid;
>
> /*
> * Per process flags
> diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
> index 584ae88b435e..d45a5476b97d 100644
> --- a/include/linux/sched/signal.h
> +++ b/include/linux/sched/signal.h
> @@ -562,10 +562,7 @@ static inline sigset_t *sigmask_to_save(void)
> return res;
> }
>
> -static inline int kill_cad_pid(int sig, int priv)
> -{
> - return kill_pid(cad_pid, sig, priv);
> -}
> +int kill_cad_pid(int sig, int priv);
>
> /* These can be the second arg to send_sig_info/send_group_sig_info. */
> #define SEND_SIG_NOINFO ((struct kernel_siginfo *) 0)
> diff --git a/init/main.c b/init/main.c
> index e363232b428b..19a10d0c2760 100644
> --- a/init/main.c
> +++ b/init/main.c
> @@ -1636,7 +1636,7 @@ static noinline void __init kernel_init_freeable(void)
> */
> set_mems_allowed(node_states[N_MEMORY]);
>
> - cad_pid = get_pid(task_pid(current));
> + rcu_assign_pointer(cad_pid, get_pid(task_pid(current)));
>
> smp_prepare_cpus(setup_max_cpus);
>
> diff --git a/kernel/reboot.c b/kernel/reboot.c
> index f070c5c1103a..d177d89fcc33 100644
> --- a/kernel/reboot.c
> +++ b/kernel/reboot.c
> @@ -13,7 +13,9 @@
> #include <linux/kexec.h>
> #include <linux/kmod.h>
> #include <linux/kmsg_dump.h>
> +#include <linux/rcupdate.h>
> #include <linux/reboot.h>
> +#include <linux/sched/signal.h>
> #include <linux/suspend.h>
> #include <linux/syscalls.h>
> #include <linux/syscore_ops.h>
> @@ -24,8 +26,7 @@
> */
>
> static int C_A_D = 1;
> -struct pid *cad_pid;
> -EXPORT_SYMBOL(cad_pid);
> +struct pid __rcu *cad_pid;
>
> #if defined(CONFIG_ARM)
> #define DEFAULT_REBOOT_MODE = REBOOT_HARD
> @@ -1371,10 +1372,14 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
> {
> struct ctl_table tmp_table = *table;
> struct pid *new_pid;
> + struct pid *old_pid;
> pid_t tmp_pid;
> int r;
>
> - tmp_pid = pid_vnr(cad_pid);
> + rcu_read_lock();
> + tmp_pid = pid_vnr(rcu_dereference(cad_pid));
> + rcu_read_unlock();
> +
> tmp_table.data = &tmp_pid;
>
> r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
> @@ -1385,7 +1390,13 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
> if (!new_pid)
> return -ESRCH;
>
> - put_pid(xchg(&cad_pid, new_pid));
> + old_pid = unrcu_pointer(xchg(&cad_pid, RCU_INITIALIZER(new_pid)));
> + /*
> + * Wait for cad_pid readers before put_pid(). We cannot use
> + * call_rcu() here because free_pid() already owns pid->rcu.
> + */
> + synchronize_rcu();
> + put_pid(old_pid);
> return 0;
> }
>
> diff --git a/kernel/signal.c b/kernel/signal.c
> index 9c2b32c4d755..464732c44554 100644
> --- a/kernel/signal.c
> +++ b/kernel/signal.c
> @@ -1891,6 +1891,18 @@ int kill_pid(struct pid *pid, int sig, int priv)
> }
> EXPORT_SYMBOL(kill_pid);
>
> +int kill_cad_pid(int sig, int priv)
> +{
> + int ret;
> +
> + rcu_read_lock();
> + ret = kill_pid(rcu_dereference(cad_pid), sig, priv);
> + rcu_read_unlock();
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(kill_cad_pid);
> +
> #ifdef CONFIG_POSIX_TIMERS
> /*
> * These functions handle POSIX timer signals. POSIX timers use
>
> base-commit: 8d75c338f0bcecaa6c9af67f86c176b67b6acf3e
--
Best regards, Pavel Tikhomirov
Senior Software Developer, Virtuozzo.
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v4] reboot: fix cad_pid use-after-free race
2026-08-14 4:09 [PATCH v4] reboot: fix cad_pid use-after-free race Cen Zhang (Microsoft)
` (2 preceding siblings ...)
2026-08-18 12:21 ` Pavel Tikhomirov
@ 2026-08-18 12:53 ` Eric W. Biederman
2026-08-31 8:25 ` Christian Brauner
4 siblings, 0 replies; 6+ messages in thread
From: Eric W. Biederman @ 2026-08-18 12:53 UTC (permalink / raw)
To: Cen Zhang (Microsoft)
Cc: joel.granados, kees, brauner, oleg, mingo, peterz, akpm, jack,
avagin, ptikhomirov, mjguzik, include, legion, linux-kernel,
linux-fsdevel, AutonomousCodeSecurity, tgopinath, kys, stable
"Cen Zhang (Microsoft)" <blbllhy@gmail.com> writes:
> cad_pid is a single kernel-wide struct pid pointer. proc_do_cad_pid()
> reads it and passes it to pid_vnr() without protecting the lifetime of
> the referenced struct pid. A concurrent writer can replace cad_pid and
> drop the final reference to the old struct pid after the reader has
> loaded the pointer but before pid_vnr() has finished dereferencing it,
> causing a use-after-free.
>
> kill_cad_pid() has the same lifetime race when it passes cad_pid to
> kill_pid().
>
> At the time this issue was reported, an unprivileged user could reach the
> sysctl through user and PID namespaces because cad_pid was registered in
> pid_table[]. Moving cad_pid 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 cad_pid 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.
>
> call_rcu(&old_pid->rcu, ...) cannot be used here because free_pid()
> also queues pid->rcu; queueing the same rcu_head twice can corrupt the
> RCU callback list.
Dumb question why not a mutex to protect cad_pid?
All of the bare rcu_dereference and rcu_assign_pointer makes me go hmmm.
Especially when we are talking an interface that is expected to be
called maybe once. RCU to protect it's data structures seems a bit
overkill.
Yes KASAN crashed but that was because someone was abusing it.
Oleg's change should have reduced the set of processes that can
abuse this.
Now it is just a matter of keeping root from doing something stupid.
AKA changing cad_pid while pressing "Ctrl-Alt-Del".
So why not a mutex?
I am still not convinced there are any users of changing CAD pid.
But I can see it being easier to write a patch to handle them,
then to figure out if they exist. Still something tricky like
using RCU seems way way overkill.
Eric
> Original KASAN crash stack:
> kernel/pid.c:545 pid_nr_ns() # reads freed pid->level
> kernel/pid.c:556 pid_vnr() # calls pid_nr_ns()
> kernel/pid.c:775 proc_do_cad_pid() # calls pid_vnr(cad_pid)
>
> Fixes: 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid")
> Reported-by: AutonomousCodeSecurity@microsoft.com
> Closes: https://lore.kernel.org/all/20260717210143.4734-1-blbllhy@gmail.com/
> Link: https://lore.kernel.org/all/alz5ZYLE4kaq_v2P@redhat.com/
> Link: https://lore.kernel.org/all/al4ICz9biJKtdZc4@redhat.com/
> Suggested-by: Mateusz Guzik <mjguzik@gmail.com>
> Suggested-by: Bradley Morgan <include@grrlz.net>
> Suggested-by: Oleg Nesterov <oleg@redhat.com>
> Suggested-by: Eric W. Biederman <ebiederm@xmission.com>
> Suggested-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com>
> Cc: stable@vger.kernel.org
> Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com>
> ---
> v4:
> - Rebase on sysctl-next after Oleg's cad_pid sysctl placement change.
> - Fold the two-patch v3 series into a single lifetime fix.
> - Move kill_cad_pid() implementation to kernel/signal.c.
> - Export kill_cad_pid() and stop exporting the raw cad_pid pointer.
> - Update proc_do_cad_pid() in kernel/reboot.c.
> - Add explicit includes for the RCU API and kill_cad_pid().
> - Preserve the original unprivileged impact context while noting that
> the namespace permission path has been fixed separately.
>
> v3:
> - Keep kill_cad_pid() inside the RCU read-side critical section
> instead of taking a pid reference, as suggested by Oleg.
>
> v2:
> - Split out kill_cad_pid() deinline into a preparatory patch.
> - Annotate cad_pid as __rcu and use rcu_dereference().
> - Protect kill_cad_pid() by taking a pid reference under RCU.
> - Add a comment explaining why synchronize_rcu() is used instead of
> call_rcu().
>
> include/linux/sched.h | 2 +-
> include/linux/sched/signal.h | 5 +----
> init/main.c | 2 +-
> kernel/reboot.c | 19 +++++++++++++++----
> kernel/signal.c | 12 ++++++++++++
> 5 files changed, 30 insertions(+), 10 deletions(-)
>
> diff --git a/include/linux/sched.h b/include/linux/sched.h
> index 373bcc0598d1..31ce72b1233c 100644
> --- a/include/linux/sched.h
> +++ b/include/linux/sched.h
> @@ -1767,7 +1767,7 @@ static inline bool is_lazy_mmu_mode_active(void)
> }
> #endif
>
> -extern struct pid *cad_pid;
> +extern struct pid __rcu *cad_pid;
>
> /*
> * Per process flags
> diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
> index 584ae88b435e..d45a5476b97d 100644
> --- a/include/linux/sched/signal.h
> +++ b/include/linux/sched/signal.h
> @@ -562,10 +562,7 @@ static inline sigset_t *sigmask_to_save(void)
> return res;
> }
>
> -static inline int kill_cad_pid(int sig, int priv)
> -{
> - return kill_pid(cad_pid, sig, priv);
> -}
> +int kill_cad_pid(int sig, int priv);
>
> /* These can be the second arg to send_sig_info/send_group_sig_info. */
> #define SEND_SIG_NOINFO ((struct kernel_siginfo *) 0)
> diff --git a/init/main.c b/init/main.c
> index e363232b428b..19a10d0c2760 100644
> --- a/init/main.c
> +++ b/init/main.c
> @@ -1636,7 +1636,7 @@ static noinline void __init kernel_init_freeable(void)
> */
> set_mems_allowed(node_states[N_MEMORY]);
>
> - cad_pid = get_pid(task_pid(current));
> + rcu_assign_pointer(cad_pid, get_pid(task_pid(current)));
>
> smp_prepare_cpus(setup_max_cpus);
>
> diff --git a/kernel/reboot.c b/kernel/reboot.c
> index f070c5c1103a..d177d89fcc33 100644
> --- a/kernel/reboot.c
> +++ b/kernel/reboot.c
> @@ -13,7 +13,9 @@
> #include <linux/kexec.h>
> #include <linux/kmod.h>
> #include <linux/kmsg_dump.h>
> +#include <linux/rcupdate.h>
> #include <linux/reboot.h>
> +#include <linux/sched/signal.h>
> #include <linux/suspend.h>
> #include <linux/syscalls.h>
> #include <linux/syscore_ops.h>
> @@ -24,8 +26,7 @@
> */
>
> static int C_A_D = 1;
> -struct pid *cad_pid;
> -EXPORT_SYMBOL(cad_pid);
> +struct pid __rcu *cad_pid;
>
> #if defined(CONFIG_ARM)
> #define DEFAULT_REBOOT_MODE = REBOOT_HARD
> @@ -1371,10 +1372,14 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
> {
> struct ctl_table tmp_table = *table;
> struct pid *new_pid;
> + struct pid *old_pid;
> pid_t tmp_pid;
> int r;
>
> - tmp_pid = pid_vnr(cad_pid);
> + rcu_read_lock();
> + tmp_pid = pid_vnr(rcu_dereference(cad_pid));
> + rcu_read_unlock();
> +
> tmp_table.data = &tmp_pid;
>
> r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
> @@ -1385,7 +1390,13 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe
> if (!new_pid)
> return -ESRCH;
>
> - put_pid(xchg(&cad_pid, new_pid));
> + old_pid = unrcu_pointer(xchg(&cad_pid, RCU_INITIALIZER(new_pid)));
> + /*
> + * Wait for cad_pid readers before put_pid(). We cannot use
> + * call_rcu() here because free_pid() already owns pid->rcu.
> + */
> + synchronize_rcu();
> + put_pid(old_pid);
> return 0;
> }
>
> diff --git a/kernel/signal.c b/kernel/signal.c
> index 9c2b32c4d755..464732c44554 100644
> --- a/kernel/signal.c
> +++ b/kernel/signal.c
> @@ -1891,6 +1891,18 @@ int kill_pid(struct pid *pid, int sig, int priv)
> }
> EXPORT_SYMBOL(kill_pid);
>
> +int kill_cad_pid(int sig, int priv)
> +{
> + int ret;
> +
> + rcu_read_lock();
> + ret = kill_pid(rcu_dereference(cad_pid), sig, priv);
> + rcu_read_unlock();
> +
> + return ret;
> +}
> +EXPORT_SYMBOL(kill_cad_pid);
> +
> #ifdef CONFIG_POSIX_TIMERS
> /*
> * These functions handle POSIX timer signals. POSIX timers use
>
> base-commit: 8d75c338f0bcecaa6c9af67f86c176b67b6acf3e
^ permalink raw reply [flat|nested] 6+ messages in thread* Re: [PATCH v4] reboot: fix cad_pid use-after-free race
2026-08-14 4:09 [PATCH v4] reboot: fix cad_pid use-after-free race Cen Zhang (Microsoft)
` (3 preceding siblings ...)
2026-08-18 12:53 ` Eric W. Biederman
@ 2026-08-31 8:25 ` Christian Brauner
4 siblings, 0 replies; 6+ messages in thread
From: Christian Brauner @ 2026-08-31 8:25 UTC (permalink / raw)
To: joel.granados, kees, Cen Zhang (Microsoft)
Cc: oleg, mingo, peterz, akpm, jack, avagin, ptikhomirov, mjguzik,
include, ebiederm, legion, linux-kernel, linux-fsdevel,
AutonomousCodeSecurity, tgopinath, kys, stable
On Fri, 14 Aug 2026 00:09:44 -0400, Cen Zhang (Microsoft) wrote:
> reboot: fix cad_pid use-after-free race
Applied to the vfs.fixes branch of the vfs/vfs.git tree.
Patches in the vfs.fixes branch should appear in linux-next soon.
Please report any outstanding bugs that were missed during review in a
new review to the original patch series allowing us to drop it.
It's encouraged to provide Acked-bys and Reviewed-bys even though the
patch has now been applied. If possible patch trailers will be updated.
Note that commit hashes shown below are subject to change due to rebase,
trailer updates or similar. If in doubt, please check the listed branch.
tree: https://git.kernel.org/pub/scm/linux/kernel/git/vfs/vfs.git
branch: vfs.fixes
[1/1] reboot: fix cad_pid use-after-free race
https://git.kernel.org/vfs/vfs/c/5a88f78df753
^ permalink raw reply [flat|nested] 6+ messages in thread