[PATCH] sysctl: add CAP_SYS_ADMIN check to panic/ctrl-alt-del sysctls
From: wooridge
Date: Fri Apr 17 2026 - 08:59:09 EST
Several kernel sysctls that control critical system behavior use proc_dointvec() as their handler with mode 0644, but proc_dointvec() does not perform any capability checks. In a user namespace where a process has uid 0, the VFS layer allows writes based on the file permission bits (0644), enabling unprivileged modification of critical kernel parameters.
Affected sysctls: kernel/panic, kernel/panic_on_oops, kernel/panic_on_warn, kernel/ctrl-alt-del
Fix by adding proc_dointvec_sysadmin() and proc_dointvec_minmax_sysadmin() as generic wrappers that check capable(CAP_SYS_ADMIN) on writes, then delegate to proc_dointvec()/proc_dointvec_minmax(). Also remove the existing static proc_dointvec_minmax_sysadmin() from kernel/printk/sysctl.c in favor of the new shared implementation.
Signed-off-by: wooridge <yurenwang152@xxxxxxxxx>
---
include/linux/sysctl.h | 3 +++
kernel/panic.c | 6 ++---
kernel/printk/sysctl.c | 10 +-------
kernel/reboot.c | 3 ++-
kernel/sysctl.c | 58 ++++++++++++++++++++++++++++++++++++++++++
5 files changed, 67 insertions(+), 13 deletions(-)
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 2886fbceb5d6..6322822206a7 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -82,8 +82,11 @@ int proc_dobool(const struct ctl_table *table, int write, void *buffer,
size_t *lenp, loff_t *ppos);
int proc_dointvec(const struct ctl_table *, int, void *, size_t *, loff_t *);
+int proc_dointvec_sysadmin(const struct ctl_table *, int, void *, size_t *, loff_t *);
int proc_dointvec_minmax(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos);
+int proc_dointvec_minmax_sysadmin(const struct ctl_table *table, int dir, void *buffer,
+ size_t *lenp, loff_t *ppos);
int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos,
int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr,
diff --git a/kernel/panic.c b/kernel/panic.c
index c78600212b6c..a966a4c81473 100644
--- a/kernel/panic.c
+++ b/kernel/panic.c
@@ -162,14 +162,14 @@ static const struct ctl_table kern_panic_table[] = {
.data = &panic_timeout,
.maxlen = sizeof(int),
.mode = 0644,
- .proc_handler = proc_dointvec,
+ .proc_handler = proc_dointvec_sysadmin,
},
{
.procname = "panic_on_oops",
.data = &panic_on_oops,
.maxlen = sizeof(int),
.mode = 0644,
- .proc_handler = proc_dointvec,
+ .proc_handler = proc_dointvec_sysadmin,
},
{
.procname = "panic_print",
@@ -183,7 +183,7 @@ static const struct ctl_table kern_panic_table[] = {
.data = &panic_on_warn,
.maxlen = sizeof(int),
.mode = 0644,
- .proc_handler = proc_dointvec_minmax,
+ .proc_handler = proc_dointvec_minmax_sysadmin,
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_ONE,
},
diff --git a/kernel/printk/sysctl.c b/kernel/printk/sysctl.c
index f15732e93c2e..c48694739fee 100644
--- a/kernel/printk/sysctl.c
+++ b/kernel/printk/sysctl.c
@@ -5,20 +5,12 @@
#include <linux/printk.h>
#include <linux/capability.h>
+#include <linux/sysctl.h>
#include <linux/ratelimit.h>
#include "internal.h"
static const int ten_thousand = 10000;
-static int proc_dointvec_minmax_sysadmin(const struct ctl_table *table, int write,
- void *buffer, size_t *lenp, loff_t *ppos)
-{
- if (write && !capable(CAP_SYS_ADMIN))
- return -EPERM;
-
- return proc_dointvec_minmax(table, write, buffer, lenp, ppos);
-}
-
static const struct ctl_table printk_sysctls[] = {
{
.procname = "printk",
diff --git a/kernel/reboot.c b/kernel/reboot.c
index 695c33e75efd..47055fedabbc 100644
--- a/kernel/reboot.c
+++ b/kernel/reboot.c
@@ -16,6 +16,7 @@
#include <linux/reboot.h>
#include <linux/suspend.h>
#include <linux/syscalls.h>
+#include <linux/sysctl.h>
#include <linux/syscore_ops.h>
#include <linux/uaccess.h>
@@ -1379,7 +1380,7 @@ static const struct ctl_table kern_reboot_table[] = {
.data = &C_A_D,
.maxlen = sizeof(int),
.mode = 0644,
- .proc_handler = proc_dointvec,
+ .proc_handler = proc_dointvec_sysadmin,
},
};
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 9d3a666ffde1..4e0bc095ffeb 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -843,6 +843,52 @@ int proc_dointvec(const struct ctl_table *table, int dir, void *buffer,
return do_proc_dointvec(table, dir, buffer, lenp, ppos, NULL);
}
+/**
+ * proc_dointvec_sysadmin - read/write a vector of integers with CAP_SYS_ADMIN check
+ * @table: the sysctl table
+ * @dir: %TRUE if this is a write to the sysctl file
+ * @buffer: the user buffer
+ * @lenp: the size of the user buffer
+ * @ppos: file position
+ *
+ * Same as proc_dointvec, but writes require CAP_SYS_ADMIN.
+ * This prevents unprivileged writes from user namespaces where
+ * the process has uid 0 and thus passes VFS permission checks.
+ *
+ * Returns 0 on success, -EPERM if a write lacks CAP_SYS_ADMIN.
+ */
+int proc_dointvec_sysadmin(const struct ctl_table *table, int dir, void *buffer,
+ size_t *lenp, loff_t *ppos)
+{
+ if (dir && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return proc_dointvec(table, dir, buffer, lenp, ppos);
+}
+EXPORT_SYMBOL_GPL(proc_dointvec_sysadmin);
+
+/**
+ * proc_dointvec_minmax_sysadmin - read/write a vector of integers with range and CAP_SYS_ADMIN check
+ * @table: the sysctl table
+ * @dir: %TRUE if this is a write to the sysctl file
+ * @buffer: the user buffer
+ * @lenp: the size of the user buffer
+ * @ppos: file position
+ *
+ * Same as proc_dointvec_minmax, but writes require CAP_SYS_ADMIN.
+ *
+ * Returns 0 on success, -EPERM if a write lacks CAP_SYS_ADMIN.
+ */
+int proc_dointvec_minmax_sysadmin(const struct ctl_table *table, int dir,
+ void *buffer, size_t *lenp, loff_t *ppos)
+{
+ if (dir && !capable(CAP_SYS_ADMIN))
+ return -EPERM;
+
+ return proc_dointvec_minmax(table, dir, buffer, lenp, ppos);
+}
+EXPORT_SYMBOL_GPL(proc_dointvec_minmax_sysadmin);
+
/**
* proc_douintvec - read a vector of unsigned integers
* @table: the sysctl table
@@ -1260,6 +1306,12 @@ int proc_dointvec(const struct ctl_table *table, int dir,
return -ENOSYS;
}
+int proc_dointvec_sysadmin(const struct ctl_table *table, int dir,
+ void *buffer, size_t *lenp, loff_t *ppos)
+{
+ return -ENOSYS;
+}
+
int proc_douintvec(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
@@ -1272,6 +1324,12 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir,
return -ENOSYS;
}
+int proc_dointvec_minmax_sysadmin(const struct ctl_table *table, int dir,
+ void *buffer, size_t *lenp, loff_t *ppos)
+{
+ return -ENOSYS;
+}
+
int proc_douintvec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
--
2.50.1 (Apple Git-155)