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

Jérémy Jean posted 1 patch 1 month, 2 weeks ago
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(-)
[PATCH] pid: keep cad_pid in init pidns and serialize access
Posted by Jérémy Jean 1 month, 2 weeks ago
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 <Jeremy.Jean@oss.cyber.gouv.fr>
---
 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

Re: [PATCH] pid: keep cad_pid in init pidns and serialize access
Posted by Bradley Morgan 1 month, 2 weeks ago
NAK, there's already a fix to your UAF:

https://lore.kernel.org/all/20260814040944.16561-1-blbllhy@gmail.com/
Thanks!