summaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2026-02-18 10:45:36 -0800
committerLinus Torvalds <torvalds@linux-foundation.org>2026-02-18 10:45:36 -0800
commit23b0f90ba871f096474e1c27c3d14f455189d2d9 (patch)
tree33d71899714ecc2e0d3aee6ff3f5be7ba34f96c7 /kernel
parent7ad54bbbc9c512ba3bc90e4368264bcf15c25759 (diff)
parentd174174c6776a340f5c25aab1ac47a2dd950f380 (diff)
Merge tag 'sysctl-7.00-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/sysctl/sysctl
Pull sysctl updates from Joel Granados: - Remove macros from proc handler converters Replace the proc converter macros with "regular" functions. Though it is more verbose than the macro version, it helps when debugging and better aligns with coding-style.rst. - General cleanup Remove superfluous ctl_table forward declarations. Const qualify the memory_allocation_profiling_sysctl and loadpin_sysctl_table arrays. Add missing kernel doc to proc_dointvec_conv. - Testing This series was run through sysctl selftests/kunit test suite in x86_64. And went into linux-next after rc4, giving it a good 3 weeks of testing * tag 'sysctl-7.00-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/sysctl/sysctl: sysctl: replace SYSCTL_INT_CONV_CUSTOM macro with functions sysctl: Replace unidirectional INT converter macros with functions sysctl: Add kernel doc to proc_douintvec_conv sysctl: Replace UINT converter macros with functions sysctl: Add CONFIG_PROC_SYSCTL guards for converter macros sysctl: clarify proc_douintvec_minmax doc sysctl: Return -ENOSYS from proc_douintvec_conv when CONFIG_PROC_SYSCTL=n sysctl: Remove unused ctl_table forward declarations loadpin: Implement custom proc_handler for enforce alloc_tag: move memory_allocation_profiling_sysctls into .rodata sysctl: Add missing kernel-doc for proc_dointvec_conv
Diffstat (limited to 'kernel')
-rw-r--r--kernel/printk/internal.h2
-rw-r--r--kernel/printk/sysctl.c1
-rw-r--r--kernel/sysctl.c290
-rw-r--r--kernel/time/jiffies.c134
4 files changed, 385 insertions, 42 deletions
diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h
index 5fdea5682756..85fbf1801cbe 100644
--- a/kernel/printk/internal.h
+++ b/kernel/printk/internal.h
@@ -4,9 +4,9 @@
*/
#include <linux/console.h>
#include <linux/types.h>
+#include <linux/sysctl.h>
#if defined(CONFIG_PRINTK) && defined(CONFIG_SYSCTL)
-struct ctl_table;
void __init printk_sysctl_init(void);
int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos);
diff --git a/kernel/printk/sysctl.c b/kernel/printk/sysctl.c
index da77f3f5c1fe..f15732e93c2e 100644
--- a/kernel/printk/sysctl.c
+++ b/kernel/printk/sysctl.c
@@ -3,7 +3,6 @@
* sysctl.c: General linux system control interface
*/
-#include <linux/sysctl.h>
#include <linux/printk.h>
#include <linux/capability.h>
#include <linux/ratelimit.h>
diff --git a/kernel/sysctl.c b/kernel/sysctl.c
index 2cd767b9680e..9d3a666ffde1 100644
--- a/kernel/sysctl.c
+++ b/kernel/sysctl.c
@@ -354,29 +354,221 @@ static void proc_put_char(void **buf, size_t *size, char c)
}
}
-static SYSCTL_USER_TO_KERN_INT_CONV(, SYSCTL_CONV_IDENTITY)
-static SYSCTL_KERN_TO_USER_INT_CONV(, SYSCTL_CONV_IDENTITY)
+/**
+ * proc_uint_u2k_conv_uop - Assign user value to a kernel pointer
+ *
+ * @u_ptr: pointer to user space variable
+ * @k_ptr: pointer to kernel variable
+ * @u_ptr_op: execute this function before assigning to k_ptr
+ *
+ * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if
+ * not NULL. Check that the values are less than UINT_MAX to avoid
+ * having to support wrap around from userspace.
+ *
+ * returns 0 on success.
+ */
+int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr,
+ ulong (*u_ptr_op)(const ulong))
+{
+ ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr;
-static SYSCTL_INT_CONV_CUSTOM(, sysctl_user_to_kern_int_conv,
- sysctl_kern_to_user_int_conv, false)
-static SYSCTL_INT_CONV_CUSTOM(_minmax, sysctl_user_to_kern_int_conv,
- sysctl_kern_to_user_int_conv, true)
+ if (u > UINT_MAX)
+ return -EINVAL;
+ WRITE_ONCE(*k_ptr, u);
+ return 0;
+}
+
+/**
+ * proc_uint_k2u_conv - Assign kernel value to a user space pointer
+ *
+ * @u_ptr: pointer to user space variable
+ * @k_ptr: pointer to kernel variable
+ *
+ * Uses READ_ONCE to assign value to u_ptr.
+ *
+ * returns 0 on success.
+ */
+int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr)
+{
+ uint val = READ_ONCE(*k_ptr);
+ *u_ptr = (ulong)val;
+ return 0;
+}
+/**
+ * proc_uint_conv - Change user or kernel pointer based on direction
+ *
+ * @u_ptr: pointer to user variable
+ * @k_ptr: pointer to kernel variable
+ * @dir: %TRUE if this is a write to the sysctl file
+ * @tbl: the sysctl table
+ * @k_ptr_range_check: Check range for k_ptr when %TRUE
+ * @user_to_kern: Callback used to assign value from user to kernel var
+ * @kern_to_user: Callback used to assign value from kernel to user var
+ *
+ * When direction is kernel to user, then the u_ptr is modified.
+ * When direction is user to kernel, then the k_ptr is modified.
+ *
+ * Returns 0 on success
+ */
+int proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir,
+ const struct ctl_table *tbl, bool k_ptr_range_check,
+ int (*user_to_kern)(const ulong *u_ptr, uint *k_ptr),
+ int (*kern_to_user)(ulong *u_ptr, const uint *k_ptr))
+{
+ if (SYSCTL_KERN_TO_USER(dir))
+ return kern_to_user(u_ptr, k_ptr);
+
+ if (k_ptr_range_check) {
+ uint tmp_k;
+ int ret;
+
+ if (!tbl)
+ return -EINVAL;
+ ret = user_to_kern(u_ptr, &tmp_k);
+ if (ret)
+ return ret;
+ if ((tbl->extra1 &&
+ *(uint *)tbl->extra1 > tmp_k) ||
+ (tbl->extra2 &&
+ *(uint *)tbl->extra2 < tmp_k))
+ return -ERANGE;
+ WRITE_ONCE(*k_ptr, tmp_k);
+ } else
+ return user_to_kern(u_ptr, k_ptr);
+ return 0;
+}
-static SYSCTL_USER_TO_KERN_UINT_CONV(, SYSCTL_CONV_IDENTITY)
+static int proc_uint_u2k_conv(const ulong *u_ptr, uint *k_ptr)
+{
+ return proc_uint_u2k_conv_uop(u_ptr, k_ptr, NULL);
+}
-int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr,
- const unsigned int *k_ptr)
+static int do_proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir,
+ const struct ctl_table *tbl)
{
- unsigned int val = READ_ONCE(*k_ptr);
- *u_ptr = (unsigned long)val;
+ return proc_uint_conv(u_ptr, k_ptr, dir, tbl, false,
+ proc_uint_u2k_conv, proc_uint_k2u_conv);
+}
+
+static int do_proc_uint_conv_minmax(ulong *u_ptr, uint *k_ptr, int dir,
+ const struct ctl_table *tbl)
+{
+ return proc_uint_conv(u_ptr, k_ptr, dir, tbl, true,
+ proc_uint_u2k_conv, proc_uint_k2u_conv);
+}
+
+/**
+ * proc_int_k2u_conv_kop - Assign kernel value to a user space pointer
+ * @u_ptr: pointer to user space variable
+ * @k_ptr: pointer to kernel variable
+ * @negp: assigned %TRUE if the converted kernel value is negative;
+ * %FALSE otherweise
+ * @k_ptr_op: execute this function before assigning to u_ptr
+ *
+ * Uses READ_ONCE to get value from k_ptr. Executes k_ptr_op before assigning
+ * to u_ptr if not NULL. Does **not** check for overflow.
+ *
+ * Returns: 0 on success.
+ */
+int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp,
+ ulong (*k_ptr_op)(const ulong))
+{
+ int val = READ_ONCE(*k_ptr);
+
+ if (val < 0) {
+ *negp = true;
+ *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val;
+ } else {
+ *negp = false;
+ *u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val;
+ }
+ return 0;
+}
+
+/**
+ * proc_int_u2k_conv_uop - Assign user value to a kernel pointer
+ * @u_ptr: pointer to user space variable
+ * @k_ptr: pointer to kernel variable
+ * @negp: If %TRUE, the converted user value is made negative.
+ * @u_ptr_op: execute this function before assigning to k_ptr
+ *
+ * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if
+ * not NULL. Check for overflow with UINT_MAX.
+ *
+ * Returns: 0 on success.
+ */
+int proc_int_u2k_conv_uop(const ulong *u_ptr, int *k_ptr, const bool *negp,
+ ulong (*u_ptr_op)(const ulong))
+{
+ ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr;
+
+ if (*negp) {
+ if (u > (ulong) INT_MAX + 1)
+ return -EINVAL;
+ WRITE_ONCE(*k_ptr, -u);
+ } else {
+ if (u > (ulong) INT_MAX)
+ return -EINVAL;
+ WRITE_ONCE(*k_ptr, u);
+ }
+ return 0;
+}
+
+int proc_int_conv(bool *negp, ulong *u_ptr, int *k_ptr, int dir,
+ const struct ctl_table *tbl, bool k_ptr_range_check,
+ int (*user_to_kern)(const bool *negp, const ulong *u_ptr, int *k_ptr),
+ int (*kern_to_user)(bool *negp, ulong *u_ptr, const int *k_ptr))
+{
+ if (SYSCTL_KERN_TO_USER(dir))
+ return kern_to_user(negp, u_ptr, k_ptr);
+
+ if (k_ptr_range_check) {
+ int tmp_k, ret;
+
+ if (!tbl)
+ return -EINVAL;
+ ret = user_to_kern(negp, u_ptr, &tmp_k);
+ if (ret)
+ return ret;
+ if ((tbl->extra1 && *(int *)tbl->extra1 > tmp_k) ||
+ (tbl->extra2 && *(int *)tbl->extra2 < tmp_k))
+ return -EINVAL;
+ WRITE_ONCE(*k_ptr, tmp_k);
+ } else
+ return user_to_kern(negp, u_ptr, k_ptr);
return 0;
}
-static SYSCTL_UINT_CONV_CUSTOM(, sysctl_user_to_kern_uint_conv,
- sysctl_kern_to_user_uint_conv, false)
-static SYSCTL_UINT_CONV_CUSTOM(_minmax, sysctl_user_to_kern_uint_conv,
- sysctl_kern_to_user_uint_conv, true)
+
+
+static int sysctl_user_to_kern_int_conv(const bool *negp, const ulong *u_ptr,
+ int *k_ptr)
+{
+ return proc_int_u2k_conv_uop(u_ptr, k_ptr, negp, NULL);
+}
+
+static int sysctl_kern_to_user_int_conv(bool *negp, ulong *u_ptr, const int *k_ptr)
+{
+ return proc_int_k2u_conv_kop(u_ptr, k_ptr, negp, NULL);
+}
+
+static int do_proc_int_conv(bool *negp, unsigned long *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,
+ sysctl_user_to_kern_int_conv,
+ sysctl_kern_to_user_int_conv);
+
+}
+
+static int do_proc_int_conv_minmax(bool *negp, unsigned long *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true,
+ sysctl_user_to_kern_int_conv,
+ sysctl_kern_to_user_int_conv);
+}
static const char proc_wspace_sep[] = { ' ', '\t', '\n' };
@@ -568,6 +760,22 @@ static int do_proc_douintvec(const struct ctl_table *table, int dir,
return do_proc_douintvec_r(table, buffer, lenp, ppos, conv);
}
+/**
+ * proc_douintvec_conv - read a vector of unsigned ints with a custom converter
+ *
+ * @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
+ * @conv: Custom converter call back
+ *
+ * Reads/writes up to table->maxlen/sizeof(unsigned int) unsigned integer
+ * values from/to the user buffer, treated as an ASCII string. Negative
+ * strings are not allowed.
+ *
+ * Returns 0 on success
+ */
int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer,
size_t *lenp, loff_t *ppos,
int (*conv)(unsigned long *u_ptr, unsigned int *k_ptr,
@@ -576,7 +784,6 @@ int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer,
return do_proc_douintvec(table, dir, buffer, lenp, ppos, conv);
}
-
/**
* proc_dobool - read/write a bool
* @table: the sysctl table
@@ -692,10 +899,10 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir,
* values from/to the user buffer, treated as an ASCII string. Negative
* strings are not allowed.
*
- * This routine will ensure the values are within the range specified by
- * table->extra1 (min) and table->extra2 (max). There is a final sanity
- * check for UINT_MAX to avoid having to support wrap around uses from
- * userspace.
+ * When changing the kernel variable, this routine will ensure the values
+ * are within the range specified by table->extra1 (min) and table->extra2
+ * (max). And Check that the values are less than UINT_MAX to avoid having to
+ * support wrap around uses from userspace.
*
* Returns 0 on success or -ERANGE when range check failes and
* SYSCTL_USER_TO_KERN(dir) == true
@@ -862,6 +1069,22 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir,
return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, 1l, 1l);
}
+/**
+ * proc_dointvec_conv - read a vector of ints with a custom converter
+ * @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
+ * @conv: Custom converter call back
+ *
+ * Reads/writes up to table->maxlen/sizeof(unsigned int) unsigned integer
+ * values from/to the user buffer, treated as an ASCII string. Negative
+ * strings are not allowed.
+ *
+ * Returns: 0 on success
+ */
+
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,
@@ -1055,6 +1278,33 @@ int proc_douintvec_minmax(const struct ctl_table *table, int dir,
return -ENOSYS;
}
+int proc_douintvec_conv(const struct ctl_table *table, int write, void *buffer,
+ size_t *lenp, loff_t *ppos,
+ int (*conv)(unsigned long *lvalp, unsigned int *valp,
+ int write, const struct ctl_table *table))
+{
+ return -ENOSYS;
+}
+
+int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr)
+{
+ return -ENOSYS;
+}
+
+int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr,
+ ulong (*u_ptr_op)(const ulong))
+{
+ return -ENOSYS;
+}
+
+int proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir,
+ const struct ctl_table *tbl, bool k_ptr_range_check,
+ int (*user_to_kern)(const ulong *u_ptr, uint *k_ptr),
+ int (*kern_to_user)(ulong *u_ptr, const uint *k_ptr))
+{
+ return -ENOSYS;
+}
+
int proc_dou8vec_minmax(const struct ctl_table *table, int dir,
void *buffer, size_t *lenp, loff_t *ppos)
{
diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c
index d31a6d40d38d..a5c7d15fce72 100644
--- a/kernel/time/jiffies.c
+++ b/kernel/time/jiffies.c
@@ -100,26 +100,120 @@ void __init register_refined_jiffies(long cycles_per_second)
__clocksource_register(&refined_jiffies);
}
-#define SYSCTL_CONV_MULT_HZ(val) ((val) * HZ)
-#define SYSCTL_CONV_DIV_HZ(val) ((val) / HZ)
-
-static SYSCTL_USER_TO_KERN_INT_CONV(_hz, SYSCTL_CONV_MULT_HZ)
-static SYSCTL_KERN_TO_USER_INT_CONV(_hz, SYSCTL_CONV_DIV_HZ)
-static SYSCTL_USER_TO_KERN_INT_CONV(_userhz, clock_t_to_jiffies)
-static SYSCTL_KERN_TO_USER_INT_CONV(_userhz, jiffies_to_clock_t)
-static SYSCTL_USER_TO_KERN_INT_CONV(_ms, msecs_to_jiffies)
-static SYSCTL_KERN_TO_USER_INT_CONV(_ms, jiffies_to_msecs)
-
-static SYSCTL_INT_CONV_CUSTOM(_jiffies, sysctl_user_to_kern_int_conv_hz,
- sysctl_kern_to_user_int_conv_hz, false)
-static SYSCTL_INT_CONV_CUSTOM(_userhz_jiffies,
- sysctl_user_to_kern_int_conv_userhz,
- sysctl_kern_to_user_int_conv_userhz, false)
-static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies, sysctl_user_to_kern_int_conv_ms,
- sysctl_kern_to_user_int_conv_ms, false)
-static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax,
- sysctl_user_to_kern_int_conv_ms,
- sysctl_kern_to_user_int_conv_ms, true)
+#ifdef CONFIG_PROC_SYSCTL
+static ulong mult_hz(const ulong val)
+{
+ return val * HZ;
+}
+
+static ulong div_hz(const ulong val)
+{
+ return val / HZ;
+}
+
+static int sysctl_u2k_int_conv_hz(const bool *negp, const ulong *u_ptr, int *k_ptr)
+{
+ return proc_int_u2k_conv_uop(u_ptr, k_ptr, negp, mult_hz);
+}
+
+static int sysctl_k2u_int_conv_hz(bool *negp, ulong *u_ptr, const int *k_ptr)
+{
+ return proc_int_k2u_conv_kop(u_ptr, k_ptr, negp, div_hz);
+}
+
+static int sysctl_u2k_int_conv_userhz(const bool *negp, const ulong *u_ptr, int *k_ptr)
+{
+ return proc_int_u2k_conv_uop(u_ptr, k_ptr, negp, clock_t_to_jiffies);
+}
+
+static ulong sysctl_jiffies_to_clock_t(const ulong val)
+{
+ return jiffies_to_clock_t(val);
+}
+
+static int sysctl_k2u_int_conv_userhz(bool *negp, ulong *u_ptr, const int *k_ptr)
+{
+ return proc_int_k2u_conv_kop(u_ptr, k_ptr, negp, sysctl_jiffies_to_clock_t);
+}
+
+static ulong sysctl_msecs_to_jiffies(const ulong val)
+{
+ return msecs_to_jiffies(val);
+}
+
+static int sysctl_u2k_int_conv_ms(const bool *negp, const ulong *u_ptr, int *k_ptr)
+{
+ return proc_int_u2k_conv_uop(u_ptr, k_ptr, negp, sysctl_msecs_to_jiffies);
+}
+
+static ulong sysctl_jiffies_to_msecs(const ulong val)
+{
+ return jiffies_to_msecs(val);
+}
+
+static int sysctl_k2u_int_conv_ms(bool *negp, ulong *u_ptr, const int *k_ptr)
+{
+ return proc_int_k2u_conv_kop(u_ptr, k_ptr, negp, sysctl_jiffies_to_msecs);
+}
+
+static int do_proc_int_conv_jiffies(bool *negp, ulong *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,
+ sysctl_u2k_int_conv_hz, sysctl_k2u_int_conv_hz);
+}
+
+static int do_proc_int_conv_userhz_jiffies(bool *negp, ulong *u_ptr,
+ int *k_ptr, int dir,
+ const struct ctl_table *tbl)
+{
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,
+ sysctl_u2k_int_conv_userhz,
+ sysctl_k2u_int_conv_userhz);
+}
+
+static int do_proc_int_conv_ms_jiffies(bool *negp, ulong *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,
+ sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms);
+}
+
+static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr,
+ int *k_ptr, int dir,
+ const struct ctl_table *tbl)
+{
+ return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,
+ sysctl_u2k_int_conv_ms, sysctl_k2u_int_conv_ms);
+}
+
+#else // CONFIG_PROC_SYSCTL
+static int do_proc_int_conv_jiffies(bool *negp, ulong *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return -ENOSYS;
+}
+
+static int do_proc_int_conv_userhz_jiffies(bool *negp, ulong *u_ptr,
+ int *k_ptr, int dir,
+ const struct ctl_table *tbl)
+{
+ return -ENOSYS;
+}
+
+static int do_proc_int_conv_ms_jiffies(bool *negp, ulong *u_ptr, int *k_ptr,
+ int dir, const struct ctl_table *tbl)
+{
+ return -ENOSYS;
+}
+
+static int do_proc_int_conv_ms_jiffies_minmax(bool *negp, ulong *u_ptr,
+ int *k_ptr, int dir,
+ const struct ctl_table *tbl)
+{
+ return -ENOSYS;
+}
+#endif
/**
* proc_dointvec_jiffies - read a vector of integers as seconds