[PATCH] pid: keep cad_pid in init pidns and serialize access

Jérémy Jean <[email protected]>
Newsgroups org.kernel.vger.linux-kernel
Message-ID <[email protected]>
Commit e054bcbe7e7a ("sysctl: move cad_pid into kernel/pid.c")
made the global cad_pid target visible through every PID namespace's
sysctl table. proc_do_cad_pid() then read that pointer without taking
a reference while a concurrent writer could replace it and drop the
old struct pid, leaving readers and kill_cad_pid() with a use-after-
free window.

Keep the sysctl entry in the initial PID namespace only and serialize
cad_pid access through get/set helpers that hold a stable struct pid
reference for readers. Preserve pid_max registration for child
namespaces when CONFIG_PROC_SYSCTL is disabled.

Fixes: e054bcbe7e7a ("sysctl: move cad_pid into kernel/pid.c")
Assisted-by: Codex:gpt-5
Signed-off-by: Jérémy Jean <[email protected]>
---
 include/linux/sched/signal.h | 14 +++++++++++++-
 init/main.c                  |  3 ++-
 kernel/pid.c                 | 16 +++++++++++-----
 kernel/reboot.c              | 29 +++++++++++++++++++++++++++++
 4 files changed, 55 insertions(+), 7 deletions(-)

diff --git a/include/linux/sched/signal.h b/include/linux/sched/signal.h
index 584ae88b435e..f2fc428d32c4 100644
--- a/include/linux/sched/signal.h
+++ b/include/linux/sched/signal.h
@@ -562,9 +562,21 @@ static inline sigset_t *sigmask_to_save(void)
 	return res;
 }
 
+/*
+ * get_cad_pid() returns a referenced pid.
+ * set_cad_pid() consumes the caller's reference.
+ */
+struct pid *get_cad_pid(void);
+void set_cad_pid(struct pid *pid);
+
 static inline int kill_cad_pid(int sig, int priv)
 {
-	return kill_pid(cad_pid, sig, priv);
+	struct pid *pid = get_cad_pid();
+	int ret;
+
+	ret = kill_pid(pid, sig, priv);
+	put_pid(pid);
+	return ret;
 }
 
 /* These can be the second arg to send_sig_info/send_group_sig_info.  */
diff --git a/init/main.c b/init/main.c
index e363232b428b..3531a2efb054 100644
--- a/init/main.c
+++ b/init/main.c
@@ -74,6 +74,7 @@
 #include <linux/kthread.h>
 #include <linux/sched.h>
 #include <linux/sched/init.h>
+#include <linux/sched/signal.h>
 #include <linux/signal.h>
 #include <linux/idr.h>
 #include <linux/kgdb.h>
@@ -1636,7 +1637,7 @@ static noinline void __init kernel_init_freeable(void)
 	 */
 	set_mems_allowed(node_states[N_MEMORY]);
 
-	cad_pid = get_pid(task_pid(current));
+	set_cad_pid(get_pid(task_pid(current)));
 
 	smp_prepare_cpus(setup_max_cpus);
 
diff --git a/kernel/pid.c b/kernel/pid.c
index f55189a3d07d..4a0afd156d2a 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -767,12 +767,14 @@ static struct ctl_table_root pid_table_root = {
 static int proc_do_cad_pid(const struct ctl_table *table, int write, void *buffer,
 		size_t *lenp, loff_t *ppos)
 {
-	struct pid *new_pid;
+	struct pid *pid, *new_pid;
 	pid_t tmp_pid;
 	int r;
 	struct ctl_table tmp_table = *table;
 
-	tmp_pid = pid_vnr(cad_pid);
+	pid = get_cad_pid();
+	tmp_pid = pid_vnr(pid);
+	put_pid(pid);
 	tmp_table.data = &tmp_pid;
 
 	r = proc_dointvec(&tmp_table, write, buffer, lenp, ppos);
@@ -783,7 +785,7 @@ 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));
+	set_cad_pid(new_pid);
 	return 0;
 }
 
@@ -812,10 +814,14 @@ int register_pidns_sysctls(struct pid_namespace *pidns)
 {
 #ifdef CONFIG_SYSCTL
 	struct ctl_table *tbl;
+	size_t table_size = ARRAY_SIZE(pid_table);
+
+	if (IS_ENABLED(CONFIG_PROC_SYSCTL) && pidns != &init_pid_ns)
+		table_size--;
 
 	setup_sysctl_set(&pidns->set, &pid_table_root, set_is_seen);
 
-	tbl = kmemdup(pid_table, sizeof(pid_table), GFP_KERNEL);
+	tbl = kmemdup(pid_table, table_size * sizeof(*tbl), GFP_KERNEL);
 	if (!tbl)
 		return -ENOMEM;
 	tbl->data = &pidns->pid_max;
@@ -823,7 +829,7 @@ int register_pidns_sysctls(struct pid_namespace *pidns)
 			     PIDS_PER_CPU_DEFAULT * num_possible_cpus()));
 
 	pidns->sysctls = __register_sysctl_table(&pidns->set, "kernel", tbl,
-						 ARRAY_SIZE(pid_table));
+						 table_size);
 	if (!pidns->sysctls) {
 		kfree(tbl);
 		retire_sysctl_set(&pidns->set);
diff --git a/kernel/reboot.c b/kernel/reboot.c
index 695c33e75efd..226ac81ecf82 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/pid.h>
 #include <linux/reboot.h>
+#include <linux/spinlock.h>
 #include <linux/suspend.h>
 #include <linux/syscalls.h>
 #include <linux/syscore_ops.h>
@@ -26,6 +28,33 @@
 static int C_A_D = 1;
 struct pid *cad_pid;
 EXPORT_SYMBOL(cad_pid);
+static DEFINE_SPINLOCK(cad_pid_lock);
+
+struct pid *get_cad_pid(void)
+{
+	unsigned long flags;
+	struct pid *pid;
+
+	spin_lock_irqsave(&cad_pid_lock, flags);
+	pid = get_pid(cad_pid);
+	spin_unlock_irqrestore(&cad_pid_lock, flags);
+	return pid;
+}
+EXPORT_SYMBOL_GPL(get_cad_pid);
+
+void set_cad_pid(struct pid *pid)
+{
+	unsigned long flags;
+	struct pid *old_pid;
+
+	spin_lock_irqsave(&cad_pid_lock, flags);
+	old_pid = cad_pid;
+	cad_pid = pid;
+	spin_unlock_irqrestore(&cad_pid_lock, flags);
+
+	put_pid(old_pid);
+}
+EXPORT_SYMBOL_GPL(set_cad_pid);
 
 #if defined(CONFIG_ARM)
 #define DEFAULT_REBOOT_MODE		= REBOOT_HARD
-- 
2.47.3
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.