[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