reboot: fix cad_pid use-after-free race
Subsystems (3)
PATCH v4 1/1 reboot: fix cad_pid use-after-free race
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)
---
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(-)
include/linux/sched.h +1 -1
@@ -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
include/linux/sched/signal.h +1 -4
@@ -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)
init/main.c +1 -1
@@ -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);
kernel/reboot.c +15 -4
@@ -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 *buffeif (!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 ; }
kernel/signal.c +12 -0
@@ -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
Signature · 1 line
2.43.0
Quoted 29 lines · Cen Zhang (Microsoft)
cad_pidis a single kernel-wide struct pid pointer.proc_do_cad_pid()reads it and passes it topid_vnr()without protecting the lifetime of the referenced struct pid. A concurrent writer can replacecad_pidand drop the final reference to the old struct pid after the reader has loaded the pointer but beforepid_vnr()has finished dereferencing it, causing a use-after-free.
kill_cad_pid()has the same lifetime race when it passescad_pidtokill_pid().At the time this issue was reported, an unprivileged user could reach the sysctl through user and PID namespaces because
cad_pidwas registered in pid_table[]. Movingcad_pidback 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_pidas 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 becausefree_pid()also queuespid->rcu; queueing the samercu_headtwice 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)
Quoted 7 lines
Fixes 9ec52099e4b8 ("[PATCH] replace cad_pid by a struct pid")Reported-by [email protected]Suggested-by Mateusz Guzik <[email protected]>Suggested-by Bradley Morgan <[email protected]>
Yk what I'm gonna do...
It kinda of makes sense in reboot code
Quoted 155 lines
Suggested-by Oleg Nesterov <[email protected]>Suggested-by Eric W. Biederman <[email protected]>Suggested-by Pavel Tikhomirov <[email protected]>Signed-off-by Cen Zhang (Microsoft) <[email protected]>---
v4:
- Rebase on sysctl-next after Oleg'scad_pidsysctl placement change.
- Fold the two-patch v3 series into a single lifetime fix.
- Movekill_cad_pid()implementation tokernel/signal.c.
- Exportkill_cad_pid()and stop exporting the rawcad_pidpointer.
- Updateproc_do_cad_pid()inkernel/reboot.c.
- Add explicit includes for the RCU API andkill_cad_pid().
- Preserve the original unprivileged impact context while noting that the namespace permission path has been fixed separately.v3:
- Keepkill_cad_pid()inside the RCU read-side critical section instead of taking a pid reference, as suggested by Oleg.v2:
- Split outkill_cad_pid()deinline into a preparatory patch.
- Annotatecad_pidas__rcuand usercu_dereference().
- Protectkill_cad_pid()by taking a pid reference under RCU.
- Add a comment explaining whysynchronize_rcu()is used instead ofcall_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(-)include/linux/sched.h +1
-1@@ -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 include/linux/sched/signal.h +1
-4@@ -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)init/main.c +1
-1@@ -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);kernel/reboot.c +15
-4@@ -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 *buffeif (!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 ; }kernel/signal.c +12
-0@@ -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
Quoted 2 lines
base-commit:
8d75c338f0bcecaa6c9af67f86c176b67b6acf3e
Thanks!
Quoted 10 lines · Cen Zhang (Microsoft)
v4:
- Rebase on sysctl-next after Oleg'scad_pidsysctl placement change.
- Fold the two-patch v3 series into a single lifetime fix.
- Movekill_cad_pid()implementation tokernel/signal.c.
- Exportkill_cad_pid()and stop exporting the rawcad_pidpointer.
- Updateproc_do_cad_pid()inkernel/reboot.c.
- Add explicit includes for the RCU API andkill_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
Quoted 192 lines · Cen Zhang (Microsoft)
cad_pidis a single kernel-wide struct pid pointer.proc_do_cad_pid()reads it and passes it topid_vnr()without protecting the lifetime of the referenced struct pid. A concurrent writer can replacecad_pidand drop the final reference to the old struct pid after the reader has loaded the pointer but beforepid_vnr()has finished dereferencing it, causing a use-after-free.
kill_cad_pid()has the same lifetime race when it passescad_pidtokill_pid().At the time this issue was reported, an unprivileged user could reach the sysctl through user and PID namespaces because
cad_pidwas registered in pid_table[]. Movingcad_pidback 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_pidas 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 becausefree_pid()also queuespid->rcu; queueing the samercu_headtwice 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 [email protected]Suggested-by Mateusz Guzik <[email protected]>Suggested-by Bradley Morgan <[email protected]>Suggested-by Oleg Nesterov <[email protected]>Suggested-by Eric W. Biederman <[email protected]>Suggested-by Pavel Tikhomirov <[email protected]>Signed-off-by Cen Zhang (Microsoft) <[email protected]>---
v4:
- Rebase on sysctl-next after Oleg'scad_pidsysctl placement change.
- Fold the two-patch v3 series into a single lifetime fix.
- Movekill_cad_pid()implementation tokernel/signal.c.
- Exportkill_cad_pid()and stop exporting the rawcad_pidpointer.
- Updateproc_do_cad_pid()inkernel/reboot.c.
- Add explicit includes for the RCU API andkill_cad_pid().
- Preserve the original unprivileged impact context while noting that the namespace permission path has been fixed separately.v3:
- Keepkill_cad_pid()inside the RCU read-side critical section instead of taking a pid reference, as suggested by Oleg.v2:
- Split outkill_cad_pid()deinline into a preparatory patch.
- Annotatecad_pidas__rcuand usercu_dereference().
- Protectkill_cad_pid()by taking a pid reference under RCU.
- Add a comment explaining whysynchronize_rcu()is used instead ofcall_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(-)include/linux/sched.h +1
-1@@ -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 include/linux/sched/signal.h +1
-4@@ -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)init/main.c +1
-1@@ -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);kernel/reboot.c +15
-4@@ -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 *buffeif (!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 ; }kernel/signal.c +12
-0@@ -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
Signature · 2 lines
Best regards, Pavel Tikhomirov Senior Software Developer, Virtuozzo.
Quoted 16 lines · Cen Zhang (Microsoft)
cad_pidis a single kernel-wide struct pid pointer.proc_do_cad_pid()reads it and passes it topid_vnr()without protecting the lifetime of the referenced struct pid. A concurrent writer can replacecad_pidand drop the final reference to the old struct pid after the reader has loaded the pointer but beforepid_vnr()has finished dereferencing it, causing a use-after-free.
kill_cad_pid()has the same lifetime race when it passescad_pidtokill_pid().At the time this issue was reported, an unprivileged user could reach the sysctl through user and PID namespaces because
cad_pidwas registered in pid_table[]. Movingcad_pidback to the global reboot sysctl table corrected that namespace and permission mismatch, but did not fix the underlying lifetime race.
Quoted 7 lines
Fix this by treating
cad_pidas 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 becausefree_pid()also queuespid->rcu; queueing the samercu_headtwice 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
Quoted 168 lines
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 [email protected]Suggested-by Mateusz Guzik <[email protected]>Suggested-by Bradley Morgan <[email protected]>Suggested-by Oleg Nesterov <[email protected]>Suggested-by Eric W. Biederman <[email protected]>Suggested-by Pavel Tikhomirov <[email protected]>Signed-off-by Cen Zhang (Microsoft) <[email protected]>---
v4:
- Rebase on sysctl-next after Oleg'scad_pidsysctl placement change.
- Fold the two-patch v3 series into a single lifetime fix.
- Movekill_cad_pid()implementation tokernel/signal.c.
- Exportkill_cad_pid()and stop exporting the rawcad_pidpointer.
- Updateproc_do_cad_pid()inkernel/reboot.c.
- Add explicit includes for the RCU API andkill_cad_pid().
- Preserve the original unprivileged impact context while noting that the namespace permission path has been fixed separately.v3:
- Keepkill_cad_pid()inside the RCU read-side critical section instead of taking a pid reference, as suggested by Oleg.v2:
- Split outkill_cad_pid()deinline into a preparatory patch.
- Annotatecad_pidas__rcuand usercu_dereference().
- Protectkill_cad_pid()by taking a pid reference under RCU.
- Add a comment explaining whysynchronize_rcu()is used instead ofcall_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(-)include/linux/sched.h +1
-1@@ -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 include/linux/sched/signal.h +1
-4@@ -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)init/main.c +1
-1@@ -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);kernel/reboot.c +15
-4@@ -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 *buffeif (!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 ; }kernel/signal.c +12
-0@@ -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
Quoted 1 line · Cen Zhang (Microsoft)
reboot: fix
cad_piduse-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