* [PATCH v3 2/2] pid: fix cad_pid use-after-free race [not found] <20260719155842.7069-1-blbllhy@gmail.com> @ 2026-07-19 15:58 ` Cen Zhang (Microsoft) 2026-07-20 10:09 ` Pavel Tikhomirov 0 siblings, 1 reply; 2+ messages in thread From: Cen Zhang (Microsoft) @ 2026-07-19 15:58 UTC (permalink / raw) To: brauner Cc: oleg, akpm, jack, avagin, ptikhomirov, mjguzik, include, ebiederm, legion, linux-kernel, AutonomousCodeSecurity, tgopinath, kys, blbllhy, stable proc_do_cad_pid() reads the global cad_pid pointer 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. The sysctl is mode 0600, but access is checked against the owning user namespace, so an unprivileged user can reach it via userns, pidns, and a proc mount. 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. KASAN crash stack: kernel/pid.c:545 pid_nr_ns() # reads freed pid->level kernel/pid.c:556 pid_vnr() kernel/pid.c:775 proc_do_cad_pid() fs/proc/proc_sysctl.c proc_sys_call_handler() fs/read_write.c vfs_read() fs/read_write.c __x64_sys_pread64() 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/ Suggested-by: Mateusz Guzik <mjguzik@gmail.com> Suggested-by: Oleg Nesterov <oleg@redhat.com> Reviewed-by: Bradley Morgan <include@grrlz.net> Cc: stable@vger.kernel.org Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com> --- 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 +- init/main.c | 2 +- kernel/pid.c | 22 +++++++++++++++++++--- kernel/reboot.c | 2 +- 4 files changed, 22 insertions(+), 6 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/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/pid.c b/kernel/pid.c index 234ebee29375..fb7c0d18efa9 100644 --- a/kernel/pid.c +++ b/kernel/pid.c @@ -559,7 +559,13 @@ EXPORT_SYMBOL_GPL(pid_vnr); int kill_cad_pid(int sig, int priv) { - return kill_pid(cad_pid, sig, priv); + int ret; + + rcu_read_lock(); + ret = kill_pid(rcu_dereference(cad_pid), sig, priv); + rcu_read_unlock(); + + return ret; } pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type, @@ -773,11 +779,15 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe size_t *lenp, loff_t *ppos) { struct pid *new_pid; + struct pid *old_pid; pid_t tmp_pid; int r; struct ctl_table tmp_table = *table; - 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); @@ -788,7 +798,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/reboot.c b/kernel/reboot.c index 695c33e75efd..fc191a48c0e9 100644 --- a/kernel/reboot.c +++ b/kernel/reboot.c @@ -24,7 +24,7 @@ */ static int C_A_D = 1; -struct pid *cad_pid; +struct pid __rcu *cad_pid; EXPORT_SYMBOL(cad_pid); #if defined(CONFIG_ARM) -- 2.53.0 ^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH v3 2/2] pid: fix cad_pid use-after-free race 2026-07-19 15:58 ` [PATCH v3 2/2] pid: fix cad_pid use-after-free race Cen Zhang (Microsoft) @ 2026-07-20 10:09 ` Pavel Tikhomirov 0 siblings, 0 replies; 2+ messages in thread From: Pavel Tikhomirov @ 2026-07-20 10:09 UTC (permalink / raw) To: Cen Zhang (Microsoft), brauner Cc: oleg, akpm, jack, avagin, mjguzik, include, ebiederm, legion, linux-kernel, AutonomousCodeSecurity, tgopinath, kys, stable Reviewed-by: Pavel Tikhomirov <ptikhomirov@virtuozzo.com> On 7/19/26 17:58, Cen Zhang (Microsoft) wrote: > proc_do_cad_pid() reads the global cad_pid pointer 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. > > The sysctl is mode 0600, but access is checked against the owning user > namespace, so an unprivileged user can reach it via userns, pidns, and a > proc mount. > > 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. > > KASAN crash stack: > kernel/pid.c:545 pid_nr_ns() # reads freed pid->level > kernel/pid.c:556 pid_vnr() > kernel/pid.c:775 proc_do_cad_pid() > fs/proc/proc_sysctl.c proc_sys_call_handler() > fs/read_write.c vfs_read() > fs/read_write.c __x64_sys_pread64() > > 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/ > Suggested-by: Mateusz Guzik <mjguzik@gmail.com> > Suggested-by: Oleg Nesterov <oleg@redhat.com> > Reviewed-by: Bradley Morgan <include@grrlz.net> > Cc: stable@vger.kernel.org > Signed-off-by: Cen Zhang (Microsoft) <blbllhy@gmail.com> > --- > 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 +- > init/main.c | 2 +- > kernel/pid.c | 22 +++++++++++++++++++--- > kernel/reboot.c | 2 +- > 4 files changed, 22 insertions(+), 6 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/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/pid.c b/kernel/pid.c > index 234ebee29375..fb7c0d18efa9 100644 > --- a/kernel/pid.c > +++ b/kernel/pid.c > @@ -559,7 +559,13 @@ EXPORT_SYMBOL_GPL(pid_vnr); > > int kill_cad_pid(int sig, int priv) > { > - return kill_pid(cad_pid, sig, priv); > + int ret; > + > + rcu_read_lock(); > + ret = kill_pid(rcu_dereference(cad_pid), sig, priv); > + rcu_read_unlock(); > + > + return ret; > } > > pid_t __task_pid_nr_ns(struct task_struct *task, enum pid_type type, > @@ -773,11 +779,15 @@ static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffe > size_t *lenp, loff_t *ppos) > { > struct pid *new_pid; > + struct pid *old_pid; > pid_t tmp_pid; > int r; > struct ctl_table tmp_table = *table; > > - 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); > @@ -788,7 +798,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/reboot.c b/kernel/reboot.c > index 695c33e75efd..fc191a48c0e9 100644 > --- a/kernel/reboot.c > +++ b/kernel/reboot.c > @@ -24,7 +24,7 @@ > */ > > static int C_A_D = 1; > -struct pid *cad_pid; > +struct pid __rcu *cad_pid; > EXPORT_SYMBOL(cad_pid); > > #if defined(CONFIG_ARM) -- Best regards, Pavel Tikhomirov Senior Software Developer, Virtuozzo. ^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-07-20 10:09 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260719155842.7069-1-blbllhy@gmail.com>
2026-07-19 15:58 ` [PATCH v3 2/2] pid: fix cad_pid use-after-free race Cen Zhang (Microsoft)
2026-07-20 10:09 ` Pavel Tikhomirov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox