From 18c4e028847092003c11f824796d1309bc01cd69 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Mon, 29 Sep 2025 14:43:54 +0200 Subject: [PATCH 01/21] watchdog: move nmi_watchdog sysctl into .rodata Move nmi_watchdog into the watchdog_sysctls array to prevent it from unnecessary modification. This move effectively moves it inside the .rodata section. Initially moved out into its own non-const array in commit 9ec272c586b0 ("watchdog/hardlockup: keep kernel.nmi_watchdog sysctl as 0444 if probe fails"), which made it writable only when watchdog_hardlockup_available was true. Moving it back to watchdog_sysctl keeps this behavior as writing to nmi_watchdog still fails when watchdog_hardlockup_available is false. Reviewed-by: Petr Mladek Signed-off-by: Joel Granados --- kernel/watchdog.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/kernel/watchdog.c b/kernel/watchdog.c index 5b62d1002783..659f5844393c 100644 --- a/kernel/watchdog.c +++ b/kernel/watchdog.c @@ -1231,14 +1231,11 @@ static const struct ctl_table watchdog_sysctls[] = { }, #endif /* CONFIG_SMP */ #endif -}; - -static struct ctl_table watchdog_hardlockup_sysctl[] = { { .procname = "nmi_watchdog", .data = &watchdog_hardlockup_user_enabled, .maxlen = sizeof(int), - .mode = 0444, + .mode = 0644, .proc_handler = proc_nmi_watchdog, .extra1 = SYSCTL_ZERO, .extra2 = SYSCTL_ONE, @@ -1248,10 +1245,6 @@ static struct ctl_table watchdog_hardlockup_sysctl[] = { static void __init watchdog_sysctl_init(void) { register_sysctl_init("kernel", watchdog_sysctls); - - if (watchdog_hardlockup_available) - watchdog_hardlockup_sysctl[0].mode = 0644; - register_sysctl_init("kernel", watchdog_hardlockup_sysctl); } #else From 74a7b4f18396f07e87c7fda5c19d1fcfb8c1dd44 Mon Sep 17 00:00:00 2001 From: Randy Dunlap Date: Fri, 17 Oct 2025 00:08:02 -0700 Subject: [PATCH 02/21] sysctl: fix kernel-doc format warning Describe the "type" struct member using '@type' and move it together with the rest of the doc for ctl_table_header to avoid a kernel-doc warning: Warning: include/linux/sysctl.h:178 Incorrect use of kernel-doc format: * enum type - Enumeration to differentiate between ctl target types Fixes: 2f2665c13af4 ("sysctl: replace child with an enumeration") Signed-off-by: Randy Dunlap Signed-off-by: Joel Granados --- include/linux/sysctl.h | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 92e9146b1104..28c4a997fd21 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -156,6 +156,10 @@ struct ctl_node { * @nreg: When nreg drops to 0 the ctl_table_header will be unregistered. * @rcu: Delays the freeing of the inode. Introduced with "unfuck proc_sysctl ->d_compare()" * + * @type: Enumeration to differentiate between ctl target types + * @type.SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations + * @type.SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Identifies a permanently empty dir + * target to serve as a mount point */ struct ctl_table_header { union { @@ -175,13 +179,6 @@ struct ctl_table_header { struct ctl_dir *parent; struct ctl_node *node; struct hlist_head inodes; /* head for proc_inode->sysctl_inodes */ - /** - * enum type - Enumeration to differentiate between ctl target types - * @SYSCTL_TABLE_TYPE_DEFAULT: ctl target with no special considerations - * @SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY: Used to identify a permanently - * empty directory target to serve - * as mount point. - */ enum { SYSCTL_TABLE_TYPE_DEFAULT, SYSCTL_TABLE_TYPE_PERMANENTLY_EMPTY, From 6ca07a9b63ff4ac24931a21086542cd7092ad74f Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Wed, 1 Oct 2025 15:46:36 +0200 Subject: [PATCH 03/21] sysctl: Replace void pointer with const pointer to ctl_table * Replace void* data in the converter functions with a const struct ctl_table* table as it was only getting forwarding values from ctl_table->extra{1,2}. * Remove the void* data in the do_proc_* functions as they already had a pointer to the ctl_table. * Remove min/max structures do_proc_do{uint,int}vec_minmax_conv_param; the min/max values get passed directly in ctl_table. * Keep min/max initialization in extra{1,2} in proc_dou8vec_minmax. * The do_proc_douintvec was adjusted outside sysctl.c as it is exported to fs/pipe.c. Signed-off-by: Joel Granados --- fs/pipe.c | 6 +- include/linux/sysctl.h | 5 +- kernel/sysctl.c | 180 +++++++++++++++-------------------------- 3 files changed, 71 insertions(+), 120 deletions(-) diff --git a/fs/pipe.c b/fs/pipe.c index 42fead1efe52..9411d4fc2f43 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -1482,8 +1482,8 @@ static struct file_system_type pipe_fs_type = { #ifdef CONFIG_SYSCTL static int do_proc_dopipe_max_size_conv(unsigned long *lvalp, - unsigned int *valp, - int write, void *data) + unsigned int *valp, int write, + const struct ctl_table *table) { if (write) { unsigned int val; @@ -1505,7 +1505,7 @@ static int proc_dopipe_max_size(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_douintvec(table, write, buffer, lenp, ppos, - do_proc_dopipe_max_size_conv, NULL); + do_proc_dopipe_max_size_conv); } static const struct ctl_table fs_pipe_sysctls[] = { diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 28c4a997fd21..436191e569da 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -235,9 +235,8 @@ bool sysctl_is_alias(char *param); int do_proc_douintvec(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, void *data), - void *data); + unsigned int *valp, int write, + const struct ctl_table *table)); extern int unaligned_enabled; extern int no_unaligned_warning; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index cb6196e3fa99..f0a691ffb290 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -355,8 +355,8 @@ static void proc_put_char(void **buf, size_t *size, char c) } static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, - int *valp, - int write, void *data) + int *valp, int write, + const struct ctl_table *table) { if (write) { if (*negp) { @@ -382,8 +382,8 @@ static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, } static int do_proc_douintvec_conv(unsigned long *lvalp, - unsigned int *valp, - int write, void *data) + unsigned int *valp, int write, + const struct ctl_table *table) { if (write) { if (*lvalp > UINT_MAX) @@ -402,8 +402,7 @@ static int __do_proc_dointvec(void *tbl_data, const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, unsigned long *lvalp, int *valp, - int write, void *data), - void *data) + int write, const struct ctl_table *table)) { int *i, vleft, first = 1, err = 0; size_t left; @@ -444,12 +443,12 @@ static int __do_proc_dointvec(void *tbl_data, const struct ctl_table *table, sizeof(proc_wspace_sep), NULL); if (err) break; - if (conv(&neg, &lval, i, 1, data)) { + if (conv(&neg, &lval, i, 1, table)) { err = -EINVAL; break; } } else { - if (conv(&neg, &lval, i, 0, data)) { + if (conv(&neg, &lval, i, 0, table)) { err = -EINVAL; break; } @@ -474,11 +473,10 @@ static int __do_proc_dointvec(void *tbl_data, const struct ctl_table *table, static int do_proc_dointvec(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, unsigned long *lvalp, int *valp, - int write, void *data), - void *data) + int write, const struct ctl_table *table)) { return __do_proc_dointvec(table->data, table, write, - buffer, lenp, ppos, conv, data); + buffer, lenp, ppos, conv); } static int do_proc_douintvec_w(unsigned int *tbl_data, @@ -486,9 +484,8 @@ static int do_proc_douintvec_w(unsigned int *tbl_data, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, - unsigned int *valp, - int write, void *data), - void *data) + unsigned int *valp, int write, + const struct ctl_table *table)) { unsigned long lval; int err = 0; @@ -518,7 +515,7 @@ static int do_proc_douintvec_w(unsigned int *tbl_data, goto out_free; } - if (conv(&lval, tbl_data, 1, data)) { + if (conv(&lval, tbl_data, 1, table)) { err = -EINVAL; goto out_free; } @@ -538,12 +535,12 @@ static int do_proc_douintvec_w(unsigned int *tbl_data, return err; } -static int do_proc_douintvec_r(unsigned int *tbl_data, void *buffer, +static int do_proc_douintvec_r(unsigned int *tbl_data, + const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, - unsigned int *valp, - int write, void *data), - void *data) + unsigned int *valp, int write, + const struct ctl_table *table)) { unsigned long lval; int err = 0; @@ -551,7 +548,7 @@ static int do_proc_douintvec_r(unsigned int *tbl_data, void *buffer, left = *lenp; - if (conv(&lval, tbl_data, 0, data)) { + if (conv(&lval, tbl_data, 0, table)) { err = -EINVAL; goto out; } @@ -573,9 +570,8 @@ static int __do_proc_douintvec(void *tbl_data, 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, void *data), - void *data) + unsigned int *valp, int write, + const struct ctl_table *table)) { unsigned int *i, vleft; @@ -601,19 +597,18 @@ static int __do_proc_douintvec(void *tbl_data, const struct ctl_table *table, if (write) return do_proc_douintvec_w(i, table, buffer, lenp, ppos, - conv, data); - return do_proc_douintvec_r(i, buffer, lenp, ppos, conv, data); + conv); + return do_proc_douintvec_r(i, table, buffer, lenp, ppos, conv); } int do_proc_douintvec(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, void *data), - void *data) + unsigned int *valp, int write, + const struct ctl_table *table)) { - return __do_proc_douintvec(table->data, table, write, - buffer, lenp, ppos, conv, data); + return __do_proc_douintvec(table->data, table, write, buffer, lenp, + ppos, conv); } /** @@ -672,7 +667,7 @@ int proc_dobool(const struct ctl_table *table, int write, void *buffer, int proc_dointvec(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, write, buffer, lenp, ppos, NULL, NULL); + return do_proc_dointvec(table, write, buffer, lenp, ppos, NULL); } /** @@ -692,42 +687,28 @@ int proc_douintvec(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_douintvec(table, write, buffer, lenp, ppos, - do_proc_douintvec_conv, NULL); + do_proc_douintvec_conv); } -/** - * struct do_proc_dointvec_minmax_conv_param - proc_dointvec_minmax() range checking structure - * @min: pointer to minimum allowable value - * @max: pointer to maximum allowable value - * - * The do_proc_dointvec_minmax_conv_param structure provides the - * minimum and maximum values for doing range checking for those sysctl - * parameters that use the proc_dointvec_minmax() handler. - */ -struct do_proc_dointvec_minmax_conv_param { - int *min; - int *max; -}; - static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, - int *valp, - int write, void *data) + int *valp, int write, + const struct ctl_table *table) { - int tmp, ret; - struct do_proc_dointvec_minmax_conv_param *param = data; + int tmp, ret, *min, *max; /* * If writing, first do so via a temporary local int so we can * bounds-check it before touching *valp. */ int *ip = write ? &tmp : valp; - ret = do_proc_dointvec_conv(negp, lvalp, ip, write, data); + ret = do_proc_dointvec_conv(negp, lvalp, ip, write, table); if (ret) return ret; if (write) { - if ((param->min && *param->min > tmp) || - (param->max && *param->max < tmp)) + min = (int *) table->extra1; + max = (int *) table->extra2; + if ((min && *min > tmp) || (max && *max < tmp)) return -EINVAL; WRITE_ONCE(*valp, tmp); } @@ -754,45 +735,27 @@ static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, int proc_dointvec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - struct do_proc_dointvec_minmax_conv_param param = { - .min = (int *) table->extra1, - .max = (int *) table->extra2, - }; return do_proc_dointvec(table, write, buffer, lenp, ppos, - do_proc_dointvec_minmax_conv, ¶m); + do_proc_dointvec_minmax_conv); } -/** - * struct do_proc_douintvec_minmax_conv_param - proc_douintvec_minmax() range checking structure - * @min: pointer to minimum allowable value - * @max: pointer to maximum allowable value - * - * The do_proc_douintvec_minmax_conv_param structure provides the - * minimum and maximum values for doing range checking for those sysctl - * parameters that use the proc_douintvec_minmax() handler. - */ -struct do_proc_douintvec_minmax_conv_param { - unsigned int *min; - unsigned int *max; -}; - static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, - unsigned int *valp, - int write, void *data) + unsigned int *valp, int write, + const struct ctl_table *table) { int ret; - unsigned int tmp; - struct do_proc_douintvec_minmax_conv_param *param = data; + unsigned int tmp, *min, *max; /* write via temporary local uint for bounds-checking */ unsigned int *up = write ? &tmp : valp; - ret = do_proc_douintvec_conv(lvalp, up, write, data); + ret = do_proc_douintvec_conv(lvalp, up, write, table); if (ret) return ret; if (write) { - if ((param->min && *param->min > tmp) || - (param->max && *param->max < tmp)) + min = (unsigned int *) table->extra1; + max = (unsigned int *) table->extra2; + if ((min && *min > tmp) || (max && *max < tmp)) return -ERANGE; WRITE_ONCE(*valp, tmp); @@ -823,12 +786,8 @@ static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - struct do_proc_douintvec_minmax_conv_param param = { - .min = (unsigned int *) table->extra1, - .max = (unsigned int *) table->extra2, - }; return do_proc_douintvec(table, write, buffer, lenp, ppos, - do_proc_douintvec_minmax_conv, ¶m); + do_proc_douintvec_minmax_conv); } /** @@ -854,28 +813,24 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int write, struct ctl_table tmp; unsigned int min = 0, max = 255U, val; u8 *data = table->data; - struct do_proc_douintvec_minmax_conv_param param = { - .min = &min, - .max = &max, - }; int res; /* Do not support arrays yet. */ if (table->maxlen != sizeof(u8)) return -EINVAL; - if (table->extra1) - min = *(unsigned int *) table->extra1; - if (table->extra2) - max = *(unsigned int *) table->extra2; - tmp = *table; tmp.maxlen = sizeof(val); tmp.data = &val; + if (!tmp.extra1) + tmp.extra1 = (unsigned int *) &min; + if (!tmp.extra2) + tmp.extra2 = (unsigned int *) &max; + val = READ_ONCE(*data); res = do_proc_douintvec(&tmp, write, buffer, lenp, ppos, - do_proc_douintvec_minmax_conv, ¶m); + do_proc_douintvec_minmax_conv); if (res) return res; if (write) @@ -1014,8 +969,8 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int write, static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, - int write, void *data) + int *valp, int write, + const struct ctl_table *table) { if (write) { if (*lvalp > INT_MAX / HZ) @@ -1040,8 +995,8 @@ static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp, } static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, - int write, void *data) + int *valp, int write, + const struct ctl_table *table) { if (write) { if (USER_HZ < HZ && *lvalp > (LONG_MAX / HZ) * USER_HZ) @@ -1063,8 +1018,8 @@ static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp } static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, - int write, void *data) + int *valp, int write, + const struct ctl_table *table) { if (write) { unsigned long jif = msecs_to_jiffies(*negp ? -*lvalp : *lvalp); @@ -1088,23 +1043,24 @@ static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, } static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, void *data) + int *valp, int write, + const struct ctl_table *table) { - int tmp, ret; - struct do_proc_dointvec_minmax_conv_param *param = data; + int tmp, ret, *min, *max; /* * If writing, first do so via a temporary local int so we can * bounds-check it before touching *valp. */ int *ip = write ? &tmp : valp; - ret = do_proc_dointvec_ms_jiffies_conv(negp, lvalp, ip, write, data); + ret = do_proc_dointvec_ms_jiffies_conv(negp, lvalp, ip, write, table); if (ret) return ret; if (write) { - if ((param->min && *param->min > tmp) || - (param->max && *param->max < tmp)) + min = (int *) table->extra1; + max = (int *) table->extra2; + if ((min && *min > tmp) || (max && *max < tmp)) return -EINVAL; *valp = tmp; } @@ -1130,18 +1086,14 @@ int proc_dointvec_jiffies(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table,write,buffer,lenp,ppos, - do_proc_dointvec_jiffies_conv,NULL); + do_proc_dointvec_jiffies_conv); } int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - struct do_proc_dointvec_minmax_conv_param param = { - .min = (int *) table->extra1, - .max = (int *) table->extra2, - }; return do_proc_dointvec(table, write, buffer, lenp, ppos, - do_proc_dointvec_ms_jiffies_minmax_conv, ¶m); + do_proc_dointvec_ms_jiffies_minmax_conv); } /** @@ -1163,7 +1115,7 @@ int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, write, buffer, lenp, ppos, - do_proc_dointvec_userhz_jiffies_conv, NULL); + do_proc_dointvec_userhz_jiffies_conv); } /** @@ -1185,7 +1137,7 @@ int proc_dointvec_ms_jiffies(const struct ctl_table *table, int write, void *buf size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, write, buffer, lenp, ppos, - do_proc_dointvec_ms_jiffies_conv, NULL); + do_proc_dointvec_ms_jiffies_conv); } /** From ee581c0e3acd6b59e36c1d990090c699c5ba6735 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 2 Oct 2025 09:03:31 +0200 Subject: [PATCH 04/21] sysctl: Remove superfluous tbl_data param from "dovec" functions Remove superfluous tbl_data param from do_proc_douintvec{,_r,_w} and __do_proc_do{intvec,uintvec,ulongvec_minmax}. There is no need to pass it as it is always contained within the ctl_table struct. Signed-off-by: Joel Granados --- kernel/sysctl.c | 61 +++++++++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index f0a691ffb290..0e249a1f99fe 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -398,22 +398,22 @@ static int do_proc_douintvec_conv(unsigned long *lvalp, static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; -static int __do_proc_dointvec(void *tbl_data, const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *lvalp, int *valp, - int write, const struct ctl_table *table)) +static int __do_proc_dointvec(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos, + int (*conv)(bool *negp, unsigned long *lvalp, + int *valp, int write, + const struct ctl_table *table)) { int *i, vleft, first = 1, err = 0; size_t left; char *p; - if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) { + if (!table->data || !table->maxlen || !*lenp || (*ppos && !write)) { *lenp = 0; return 0; } - i = (int *) tbl_data; + i = (int *) table->data; vleft = table->maxlen / sizeof(*i); left = *lenp; @@ -475,13 +475,10 @@ static int do_proc_dointvec(const struct ctl_table *table, int write, int (*conv)(bool *negp, unsigned long *lvalp, int *valp, int write, const struct ctl_table *table)) { - return __do_proc_dointvec(table->data, table, write, - buffer, lenp, ppos, conv); + return __do_proc_dointvec(table, write, buffer, lenp, ppos, conv); } -static int do_proc_douintvec_w(unsigned int *tbl_data, - const struct ctl_table *table, - void *buffer, +static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, unsigned int *valp, int write, @@ -515,7 +512,7 @@ static int do_proc_douintvec_w(unsigned int *tbl_data, goto out_free; } - if (conv(&lval, tbl_data, 1, table)) { + if (conv(&lval, (unsigned int *) table->data, 1, table)) { err = -EINVAL; goto out_free; } @@ -535,8 +532,7 @@ static int do_proc_douintvec_w(unsigned int *tbl_data, return err; } -static int do_proc_douintvec_r(unsigned int *tbl_data, - const struct ctl_table *table, void *buffer, +static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, unsigned int *valp, int write, @@ -548,7 +544,7 @@ static int do_proc_douintvec_r(unsigned int *tbl_data, left = *lenp; - if (conv(&lval, tbl_data, 0, table)) { + if (conv(&lval, (unsigned int *) table->data, 0, table)) { err = -EINVAL; goto out; } @@ -566,22 +562,20 @@ static int do_proc_douintvec_r(unsigned int *tbl_data, return err; } -static int __do_proc_douintvec(void *tbl_data, const struct ctl_table *table, - int write, void *buffer, - size_t *lenp, loff_t *ppos, +static int __do_proc_douintvec(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)) { - unsigned int *i, vleft; + unsigned int vleft; - if (!tbl_data || !table->maxlen || !*lenp || (*ppos && !write)) { + if (!table->data || !table->maxlen || !*lenp || (*ppos && !write)) { *lenp = 0; return 0; } - i = (unsigned int *) tbl_data; - vleft = table->maxlen / sizeof(*i); + vleft = table->maxlen / sizeof(unsigned int); /* * Arrays are not supported, keep this simple. *Do not* add @@ -596,9 +590,8 @@ static int __do_proc_douintvec(void *tbl_data, const struct ctl_table *table, conv = do_proc_douintvec_conv; if (write) - return do_proc_douintvec_w(i, table, buffer, lenp, ppos, - conv); - return do_proc_douintvec_r(i, table, buffer, lenp, ppos, conv); + return do_proc_douintvec_w(table, buffer, lenp, ppos, conv); + return do_proc_douintvec_r(table, buffer, lenp, ppos, conv); } int do_proc_douintvec(const struct ctl_table *table, int write, @@ -607,8 +600,7 @@ int do_proc_douintvec(const struct ctl_table *table, int write, unsigned int *valp, int write, const struct ctl_table *table)) { - return __do_proc_douintvec(table->data, table, write, buffer, lenp, - ppos, conv); + return __do_proc_douintvec(table, write, buffer, lenp, ppos, conv); } /** @@ -839,9 +831,8 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int write, } EXPORT_SYMBOL_GPL(proc_dou8vec_minmax); -static int __do_proc_doulongvec_minmax(void *data, - const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos, +static int __do_proc_doulongvec_minmax(const struct ctl_table *table, + int write, void *buffer, size_t *lenp, loff_t *ppos, unsigned long convmul, unsigned long convdiv) { unsigned long *i, *min, *max; @@ -849,12 +840,12 @@ static int __do_proc_doulongvec_minmax(void *data, size_t left; char *p; - if (!data || !table->maxlen || !*lenp || (*ppos && !write)) { + if (!table->data || !table->maxlen || !*lenp || (*ppos && !write)) { *lenp = 0; return 0; } - i = data; + i = table->data; min = table->extra1; max = table->extra2; vleft = table->maxlen / sizeof(unsigned long); @@ -917,8 +908,8 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos, unsigned long convmul, unsigned long convdiv) { - return __do_proc_doulongvec_minmax(table->data, table, write, - buffer, lenp, ppos, convmul, convdiv); + return __do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, + convmul, convdiv); } /** From 610c9b6efb701e559f5b5d449e69e76e91ea4401 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 2 Oct 2025 10:50:03 +0200 Subject: [PATCH 05/21] sysctl: Remove superfluous __do_proc_* indirection Remove "__" from __do_proc_do{intvec,uintvec,ulongvec_minmax} internal functions and delete their corresponding do_proc_do* wrappers. These indirections are unnecessary as they do not add extra logic nor do they indicate a layer separation. Signed-off-by: Joel Granados --- kernel/sysctl.c | 52 +++++++++++++------------------------------------ 1 file changed, 13 insertions(+), 39 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 0e249a1f99fe..9b042d81fd1c 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -398,11 +398,11 @@ static int do_proc_douintvec_conv(unsigned long *lvalp, static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; -static int __do_proc_dointvec(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *lvalp, - int *valp, int write, - const struct ctl_table *table)) + +static int do_proc_dointvec(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos, + int (*conv)(bool *negp, unsigned long *lvalp, int *valp, + int write, const struct ctl_table *table)) { int *i, vleft, first = 1, err = 0; size_t left; @@ -470,14 +470,6 @@ static int __do_proc_dointvec(const struct ctl_table *table, int write, return err; } -static int do_proc_dointvec(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *lvalp, int *valp, - int write, const struct ctl_table *table)) -{ - return __do_proc_dointvec(table, write, buffer, lenp, ppos, conv); -} - static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, @@ -526,7 +518,6 @@ static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, return 0; - /* This is in keeping with old __do_proc_dointvec() */ bail_early: *ppos += *lenp; return err; @@ -562,11 +553,10 @@ static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, return err; } -static int __do_proc_douintvec(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)) +int do_proc_douintvec(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)) { unsigned int vleft; @@ -594,15 +584,6 @@ static int __do_proc_douintvec(const struct ctl_table *table, int write, return do_proc_douintvec_r(table, buffer, lenp, ppos, conv); } -int do_proc_douintvec(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 __do_proc_douintvec(table, write, buffer, lenp, ppos, conv); -} - /** * proc_dobool - read/write a bool * @table: the sysctl table @@ -831,9 +812,10 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int write, } EXPORT_SYMBOL_GPL(proc_dou8vec_minmax); -static int __do_proc_doulongvec_minmax(const struct ctl_table *table, - int write, void *buffer, size_t *lenp, loff_t *ppos, - unsigned long convmul, unsigned long convdiv) +static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, + void *buffer, size_t *lenp, loff_t *ppos, + unsigned long convmul, + unsigned long convdiv) { unsigned long *i, *min, *max; int vleft, first = 1, err = 0; @@ -904,14 +886,6 @@ static int __do_proc_doulongvec_minmax(const struct ctl_table *table, return err; } -static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos, unsigned long convmul, - unsigned long convdiv) -{ - return __do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, - convmul, convdiv); -} - /** * proc_doulongvec_minmax - read a vector of long integers with min/max values * @table: the sysctl table From 5412f5b13d290d22cf910a0e8227737aaa2cc44e Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Fri, 3 Oct 2025 14:07:18 +0200 Subject: [PATCH 06/21] sysctl: Indicate the direction of operation with macro names Replace the "write" integer parameter with SYSCTL_USER_TO_KERN() and SYSCTL_KERN_TO_USER() that clearly indicate data flow direction in sysctl operations. "write" originates in proc_sysctl.c (proc_sys_{read,write}) and can take one of two values: "0" or "1" when called from proc_sys_read and proc_sys_write respectively. When write has a value of zero, data is "written" to a user space buffer from a kernel variable (usually ctl_table->data). Whereas when write has a value greater than zero, data is "written" to an internal kernel variable from a user space buffer. Remove this ambiguity by introducing macros that clearly indicate the direction of the "write". The write mode names in sysctl_writes_mode are left unchanged as these directly relate to the sysctl_write_strict file in /proc/sys where the word "write" unambiguously refers to writing to a file. Signed-off-by: Joel Granados --- kernel/sysctl.c | 252 ++++++++++++++++++++++++++---------------------- 1 file changed, 136 insertions(+), 116 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 9b042d81fd1c..69148fe73599 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -30,6 +30,19 @@ EXPORT_SYMBOL(sysctl_vals); const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX }; EXPORT_SYMBOL_GPL(sysctl_long_vals); +/** + * + * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1) + * in the file_operations struct at proc/proc_sysctl.c. Its value means + * one of two things for sysctl: + * 1. SYSCTL_USER_TO_KERN(dir) Writing to an internal kernel variable from user + * space (dir > 0) + * 2. SYSCTL_KERN_TO_USER(dir) Writing to a user space buffer from a kernel + * variable (dir == 0). + */ +#define SYSCTL_USER_TO_KERN(dir) (!!(dir)) +#define SYSCTL_KERN_TO_USER(dir) (!dir) + #if defined(CONFIG_SYSCTL) /* Constants used for minimum and maximum */ @@ -55,7 +68,8 @@ static const int cap_last_cap = CAP_LAST_CAP; * to the buffer. * * These write modes control how current file position affects the behavior of - * updating sysctl values through the proc interface on each write. + * updating internal kernel (SYSCTL_USER_TO_KERN) sysctl values through the proc + * interface on each write. */ enum sysctl_writes_mode { SYSCTL_WRITES_LEGACY = -1, @@ -73,7 +87,7 @@ static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT; #ifdef CONFIG_PROC_SYSCTL -static int _proc_do_string(char *data, int maxlen, int write, +static int _proc_do_string(char *data, int maxlen, int dir, char *buffer, size_t *lenp, loff_t *ppos) { size_t len; @@ -84,7 +98,7 @@ static int _proc_do_string(char *data, int maxlen, int write, return 0; } - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (sysctl_writes_strict == SYSCTL_WRITES_STRICT) { /* Only continue writes not past the end of buffer. */ len = strlen(data); @@ -172,7 +186,7 @@ static bool proc_first_pos_non_zero_ignore(loff_t *ppos, /** * proc_dostring - read a string sysctl * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -186,13 +200,13 @@ static bool proc_first_pos_non_zero_ignore(loff_t *ppos, * * Returns 0 on success. */ -int proc_dostring(const struct ctl_table *table, int write, +int proc_dostring(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - if (write) + if (SYSCTL_USER_TO_KERN(dir)) proc_first_pos_non_zero_ignore(ppos, table); - return _proc_do_string(table->data, table->maxlen, write, buffer, lenp, + return _proc_do_string(table->data, table->maxlen, dir, buffer, lenp, ppos); } @@ -355,10 +369,10 @@ static void proc_put_char(void **buf, size_t *size, char c) } static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, + int *valp, int dir, const struct ctl_table *table) { - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (*negp) { if (*lvalp > (unsigned long) INT_MAX + 1) return -EINVAL; @@ -382,10 +396,10 @@ static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, } static int do_proc_douintvec_conv(unsigned long *lvalp, - unsigned int *valp, int write, + unsigned int *valp, int dir, const struct ctl_table *table) { - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (*lvalp > UINT_MAX) return -EINVAL; WRITE_ONCE(*valp, *lvalp); @@ -399,16 +413,17 @@ static int do_proc_douintvec_conv(unsigned long *lvalp, static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; -static int do_proc_dointvec(const struct ctl_table *table, int write, +static int do_proc_dointvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(bool *negp, unsigned long *lvalp, int *valp, - int write, const struct ctl_table *table)) + int dir, const struct ctl_table *table)) { int *i, vleft, first = 1, err = 0; size_t left; char *p; - if (!table->data || !table->maxlen || !*lenp || (*ppos && !write)) { + if (!table->data || !table->maxlen || !*lenp || + (*ppos && SYSCTL_KERN_TO_USER(dir))) { *lenp = 0; return 0; } @@ -420,7 +435,7 @@ static int do_proc_dointvec(const struct ctl_table *table, int write, if (!conv) conv = do_proc_dointvec_conv; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (proc_first_pos_non_zero_ignore(ppos, table)) goto out; @@ -433,7 +448,7 @@ static int do_proc_dointvec(const struct ctl_table *table, int write, unsigned long lval; bool neg; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { proc_skip_spaces(&p, &left); if (!left) @@ -458,11 +473,11 @@ static int do_proc_dointvec(const struct ctl_table *table, int write, } } - if (!write && !first && left && !err) + if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err) proc_put_char(&buffer, &left, '\n'); - if (write && !err && left) + if (SYSCTL_USER_TO_KERN(dir) && !err && left) proc_skip_spaces(&p, &left); - if (write && first) + if (SYSCTL_USER_TO_KERN(dir) && first) return err ? : -EINVAL; *lenp -= left; out: @@ -473,7 +488,7 @@ static int do_proc_dointvec(const struct ctl_table *table, int write, static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, - unsigned int *valp, int write, + unsigned int *valp, int dir, const struct ctl_table *table)) { unsigned long lval; @@ -526,7 +541,7 @@ static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, - unsigned int *valp, int write, + unsigned int *valp, int dir, const struct ctl_table *table)) { unsigned long lval; @@ -553,14 +568,15 @@ static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, return err; } -int do_proc_douintvec(const struct ctl_table *table, int write, void *buffer, +int do_proc_douintvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, int (*conv)(unsigned long *lvalp, unsigned int *valp, - int write, const struct ctl_table *table)) + int dir, const struct ctl_table *table)) { unsigned int vleft; - if (!table->data || !table->maxlen || !*lenp || (*ppos && !write)) { + if (!table->data || !table->maxlen || !*lenp || + (*ppos && SYSCTL_KERN_TO_USER(dir))) { *lenp = 0; return 0; } @@ -579,7 +595,7 @@ int do_proc_douintvec(const struct ctl_table *table, int write, void *buffer, if (!conv) conv = do_proc_douintvec_conv; - if (write) + if (SYSCTL_USER_TO_KERN(dir)) return do_proc_douintvec_w(table, buffer, lenp, ppos, conv); return do_proc_douintvec_r(table, buffer, lenp, ppos, conv); } @@ -587,7 +603,7 @@ int do_proc_douintvec(const struct ctl_table *table, int write, void *buffer, /** * proc_dobool - read/write a bool * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -600,7 +616,7 @@ int do_proc_douintvec(const struct ctl_table *table, int write, void *buffer, * * Returns 0 on success. */ -int proc_dobool(const struct ctl_table *table, int write, void *buffer, +int proc_dobool(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { struct ctl_table tmp; @@ -616,10 +632,10 @@ int proc_dobool(const struct ctl_table *table, int write, void *buffer, tmp.data = &val; val = READ_ONCE(*data); - res = proc_dointvec(&tmp, write, buffer, lenp, ppos); + res = proc_dointvec(&tmp, dir, buffer, lenp, ppos); if (res) return res; - if (write) + if (SYSCTL_USER_TO_KERN(dir)) WRITE_ONCE(*data, val); return 0; } @@ -627,7 +643,7 @@ int proc_dobool(const struct ctl_table *table, int write, void *buffer, /** * proc_dointvec - read a vector of integers * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -637,16 +653,16 @@ int proc_dobool(const struct ctl_table *table, int write, void *buffer, * * Returns 0 on success. */ -int proc_dointvec(const struct ctl_table *table, int write, void *buffer, +int proc_dointvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, write, buffer, lenp, ppos, NULL); + return do_proc_dointvec(table, dir, buffer, lenp, ppos, NULL); } /** * proc_douintvec - read a vector of unsigned integers * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -656,15 +672,15 @@ int proc_dointvec(const struct ctl_table *table, int write, void *buffer, * * Returns 0 on success. */ -int proc_douintvec(const struct ctl_table *table, int write, void *buffer, +int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_douintvec(table, write, buffer, lenp, ppos, + return do_proc_douintvec(table, dir, buffer, lenp, ppos, do_proc_douintvec_conv); } static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, + int *valp, int dir, const struct ctl_table *table) { int tmp, ret, *min, *max; @@ -672,13 +688,13 @@ static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, * If writing, first do so via a temporary local int so we can * bounds-check it before touching *valp. */ - int *ip = write ? &tmp : valp; + int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : valp; - ret = do_proc_dointvec_conv(negp, lvalp, ip, write, table); + ret = do_proc_dointvec_conv(negp, lvalp, ip, dir, table); if (ret) return ret; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { min = (int *) table->extra1; max = (int *) table->extra2; if ((min && *min > tmp) || (max && *max < tmp)) @@ -692,7 +708,7 @@ static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, /** * proc_dointvec_minmax - read a vector of integers with min/max values * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -703,29 +719,30 @@ static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, * This routine will ensure the values are within the range specified by * table->extra1 (min) and table->extra2 (max). * - * Returns 0 on success or -EINVAL on write when the range check fails. + * Returns 0 on success or -EINVAL when the range check fails and + * SYSCTL_USER_TO_KERN(dir) == true */ -int proc_dointvec_minmax(const struct ctl_table *table, int write, +int proc_dointvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, write, buffer, lenp, ppos, + return do_proc_dointvec(table, dir, buffer, lenp, ppos, do_proc_dointvec_minmax_conv); } static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, - unsigned int *valp, int write, + unsigned int *valp, int dir, const struct ctl_table *table) { int ret; unsigned int tmp, *min, *max; - /* write via temporary local uint for bounds-checking */ - unsigned int *up = write ? &tmp : valp; + /* When writing to the kernel use a temp local uint for bounds-checking */ + unsigned int *up = SYSCTL_USER_TO_KERN(dir) ? &tmp : valp; - ret = do_proc_douintvec_conv(lvalp, up, write, table); + ret = do_proc_douintvec_conv(lvalp, up, dir, table); if (ret) return ret; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { min = (unsigned int *) table->extra1; max = (unsigned int *) table->extra2; if ((min && *min > tmp) || (max && *max < tmp)) @@ -740,7 +757,7 @@ static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, /** * proc_douintvec_minmax - read a vector of unsigned ints with min/max values * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -754,19 +771,20 @@ static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, * check for UINT_MAX to avoid having to support wrap around uses from * userspace. * - * Returns 0 on success or -ERANGE on write when the range check fails. + * Returns 0 on success or -ERANGE when range check failes and + * SYSCTL_USER_TO_KERN(dir) == true */ -int proc_douintvec_minmax(const struct ctl_table *table, int write, +int proc_douintvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_douintvec(table, write, buffer, lenp, ppos, + return do_proc_douintvec(table, dir, buffer, lenp, ppos, do_proc_douintvec_minmax_conv); } /** * proc_dou8vec_minmax - read a vector of unsigned chars with min/max values * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -778,9 +796,10 @@ int proc_douintvec_minmax(const struct ctl_table *table, int write, * This routine will ensure the values are within the range specified by * table->extra1 (min) and table->extra2 (max). * - * Returns 0 on success or an error on write when the range check fails. + * Returns 0 on success or an error on SYSCTL_USER_TO_KERN(dir) == true + * and the range check fails. */ -int proc_dou8vec_minmax(const struct ctl_table *table, int write, +int proc_dou8vec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { struct ctl_table tmp; @@ -802,17 +821,17 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int write, tmp.extra2 = (unsigned int *) &max; val = READ_ONCE(*data); - res = do_proc_douintvec(&tmp, write, buffer, lenp, ppos, + res = do_proc_douintvec(&tmp, dir, buffer, lenp, ppos, do_proc_douintvec_minmax_conv); if (res) return res; - if (write) + if (SYSCTL_USER_TO_KERN(dir)) WRITE_ONCE(*data, val); return 0; } EXPORT_SYMBOL_GPL(proc_dou8vec_minmax); -static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, +static int do_proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, unsigned long convmul, unsigned long convdiv) @@ -822,7 +841,8 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, size_t left; char *p; - if (!table->data || !table->maxlen || !*lenp || (*ppos && !write)) { + if (!table->data || !table->maxlen || !*lenp || + (*ppos && SYSCTL_KERN_TO_USER(dir))) { *lenp = 0; return 0; } @@ -833,7 +853,7 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, vleft = table->maxlen / sizeof(unsigned long); left = *lenp; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (proc_first_pos_non_zero_ignore(ppos, table)) goto out; @@ -845,7 +865,7 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, for (; left && vleft--; i++, first = 0) { unsigned long val; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { bool neg; proc_skip_spaces(&p, &left); @@ -874,11 +894,11 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, } } - if (!write && !first && left && !err) + if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err) proc_put_char(&buffer, &left, '\n'); - if (write && !err) + if (SYSCTL_USER_TO_KERN(dir) && !err) proc_skip_spaces(&p, &left); - if (write && first) + if (SYSCTL_USER_TO_KERN(dir) && first) return err ? : -EINVAL; *lenp -= left; out: @@ -889,7 +909,7 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, /** * proc_doulongvec_minmax - read a vector of long integers with min/max values * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -902,16 +922,16 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int write, * * Returns 0 on success. */ -int proc_doulongvec_minmax(const struct ctl_table *table, int write, +int proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_doulongvec_minmax(table, write, buffer, lenp, ppos, 1l, 1l); + return do_proc_doulongvec_minmax(table, dir, buffer, lenp, ppos, 1l, 1l); } /** * proc_doulongvec_ms_jiffies_minmax - read a vector of millisecond values with min/max values * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -925,19 +945,19 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int write, * * Returns 0 on success. */ -int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int write, +int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_doulongvec_minmax(table, write, buffer, - lenp, ppos, HZ, 1000l); + return do_proc_doulongvec_minmax(table, dir, buffer, + lenp, ppos, HZ, 1000l); } static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, + int *valp, int dir, const struct ctl_table *table) { - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (*lvalp > INT_MAX / HZ) return 1; if (*negp) @@ -960,10 +980,10 @@ static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp, } static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, + int *valp, int dir, const struct ctl_table *table) { - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (USER_HZ < HZ && *lvalp > (LONG_MAX / HZ) * USER_HZ) return 1; *valp = clock_t_to_jiffies(*negp ? -*lvalp : *lvalp); @@ -983,10 +1003,10 @@ static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp } static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, + int *valp, int dir, const struct ctl_table *table) { - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { unsigned long jif = msecs_to_jiffies(*negp ? -*lvalp : *lvalp); if (jif > INT_MAX) @@ -1008,7 +1028,7 @@ static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, } static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lvalp, - int *valp, int write, + int *valp, int dir, const struct ctl_table *table) { int tmp, ret, *min, *max; @@ -1016,13 +1036,13 @@ static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lv * If writing, first do so via a temporary local int so we can * bounds-check it before touching *valp. */ - int *ip = write ? &tmp : valp; + int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : valp; - ret = do_proc_dointvec_ms_jiffies_conv(negp, lvalp, ip, write, table); + ret = do_proc_dointvec_ms_jiffies_conv(negp, lvalp, ip, dir, table); if (ret) return ret; - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { min = (int *) table->extra1; max = (int *) table->extra2; if ((min && *min > tmp) || (max && *max < tmp)) @@ -1035,7 +1055,7 @@ static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lv /** * proc_dointvec_jiffies - read a vector of integers as seconds * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -1047,24 +1067,24 @@ static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lv * * Returns 0 on success. */ -int proc_dointvec_jiffies(const struct ctl_table *table, int write, +int proc_dointvec_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table,write,buffer,lenp,ppos, - do_proc_dointvec_jiffies_conv); + return do_proc_dointvec(table, dir, buffer, lenp, ppos, + do_proc_dointvec_jiffies_conv); } -int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int write, +int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, write, buffer, lenp, ppos, + return do_proc_dointvec(table, dir, buffer, lenp, ppos, do_proc_dointvec_ms_jiffies_minmax_conv); } /** * proc_dointvec_userhz_jiffies - read a vector of integers as 1/USER_HZ seconds * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @dir: %TRUE if this is a write to the sysctl file * @buffer: the user buffer * @lenp: the size of the user buffer * @ppos: pointer to the file position @@ -1076,17 +1096,17 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int write, * * Returns 0 on success. */ -int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int write, +int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, write, buffer, lenp, ppos, + return do_proc_dointvec(table, dir, buffer, lenp, ppos, do_proc_dointvec_userhz_jiffies_conv); } /** * proc_dointvec_ms_jiffies - read a vector of integers as 1 milliseconds * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @dir: %TRUE if this is a write to the sysctl file * @buffer: the user buffer * @lenp: the size of the user buffer * @ppos: the current position in the file @@ -1098,17 +1118,17 @@ int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int write, * * Returns 0 on success. */ -int proc_dointvec_ms_jiffies(const struct ctl_table *table, int write, void *buffer, +int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, write, buffer, lenp, ppos, + return do_proc_dointvec(table, dir, buffer, lenp, ppos, do_proc_dointvec_ms_jiffies_conv); } /** * proc_do_large_bitmap - read/write from/to a large bitmap * @table: the sysctl table - * @write: %TRUE if this is a write to the sysctl file + * @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 @@ -1122,7 +1142,7 @@ int proc_dointvec_ms_jiffies(const struct ctl_table *table, int write, void *buf * * Returns 0 on success. */ -int proc_do_large_bitmap(const struct ctl_table *table, int write, +int proc_do_large_bitmap(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { int err = 0; @@ -1132,12 +1152,12 @@ int proc_do_large_bitmap(const struct ctl_table *table, int write, unsigned long *tmp_bitmap = NULL; char tr_a[] = { '-', ',', '\n' }, tr_b[] = { ',', '\n', 0 }, c; - if (!bitmap || !bitmap_len || !left || (*ppos && !write)) { + if (!bitmap || !bitmap_len || !left || (*ppos && SYSCTL_KERN_TO_USER(dir))) { *lenp = 0; return 0; } - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { char *p = buffer; size_t skipped = 0; @@ -1238,7 +1258,7 @@ int proc_do_large_bitmap(const struct ctl_table *table, int write, } if (!err) { - if (write) { + if (SYSCTL_USER_TO_KERN(dir)) { if (*ppos) bitmap_or(bitmap, bitmap, tmp_bitmap, bitmap_len); else @@ -1254,85 +1274,85 @@ int proc_do_large_bitmap(const struct ctl_table *table, int write, #else /* CONFIG_PROC_SYSCTL */ -int proc_dostring(const struct ctl_table *table, int write, +int proc_dostring(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dobool(const struct ctl_table *table, int write, +int proc_dobool(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dointvec(const struct ctl_table *table, int write, +int proc_dointvec(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 write, +int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dointvec_minmax(const struct ctl_table *table, int write, +int proc_dointvec_minmax(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 write, +int proc_douintvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dou8vec_minmax(const struct ctl_table *table, int write, +int proc_dou8vec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dointvec_jiffies(const struct ctl_table *table, int write, +int proc_dointvec_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int write, +int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int write, +int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_dointvec_ms_jiffies(const struct ctl_table *table, int write, +int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_doulongvec_minmax(const struct ctl_table *table, int write, +int proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int write, +int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; } -int proc_do_large_bitmap(const struct ctl_table *table, int write, +int proc_do_large_bitmap(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return -ENOSYS; @@ -1341,7 +1361,7 @@ int proc_do_large_bitmap(const struct ctl_table *table, int write, #endif /* CONFIG_PROC_SYSCTL */ #if defined(CONFIG_SYSCTL) -int proc_do_static_key(const struct ctl_table *table, int write, +int proc_do_static_key(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { struct static_key *key = (struct static_key *)table->data; @@ -1355,13 +1375,13 @@ int proc_do_static_key(const struct ctl_table *table, int write, .extra2 = SYSCTL_ONE, }; - if (write && !capable(CAP_SYS_ADMIN)) + if (SYSCTL_USER_TO_KERN(dir) && !capable(CAP_SYS_ADMIN)) return -EPERM; mutex_lock(&static_key_mutex); val = static_key_enabled(key); - ret = proc_dointvec_minmax(&tmp, write, buffer, lenp, ppos); - if (write && !ret) { + ret = proc_dointvec_minmax(&tmp, dir, buffer, lenp, ppos); + if (SYSCTL_USER_TO_KERN(dir) && !ret) { if (val) static_key_enable(key); else From 551bf1845027650ff6c9da598fb270ba362e9218 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Fri, 3 Oct 2025 21:36:34 +0200 Subject: [PATCH 07/21] sysctl: Discriminate between kernel and user converter params Rename converter parameter to indicate data flow direction: "lvalp" to "u_ptr" indicating a user space parsed value pointer. "valp" to "k_ptr" indicating a kernel storage value pointer. This facilitates the identification of discrepancies between direction (copy to kernel or copy to user space) and the modified variable. This is a preparation commit for when the converter functions are exposed to the rest of the kernel. Signed-off-by: Joel Granados --- kernel/sysctl.c | 118 ++++++++++++++++++++++++------------------------ 1 file changed, 59 insertions(+), 59 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 69148fe73599..2091d2396c83 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -368,44 +368,44 @@ static void proc_put_char(void **buf, size_t *size, char c) } } -static int do_proc_dointvec_conv(bool *negp, unsigned long *lvalp, - int *valp, int dir, +static int do_proc_dointvec_conv(bool *negp, unsigned long *u_ptr, + int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { if (*negp) { - if (*lvalp > (unsigned long) INT_MAX + 1) + if (*u_ptr > (unsigned long) INT_MAX + 1) return -EINVAL; - WRITE_ONCE(*valp, -*lvalp); + WRITE_ONCE(*k_ptr, -*u_ptr); } else { - if (*lvalp > (unsigned long) INT_MAX) + if (*u_ptr > (unsigned long) INT_MAX) return -EINVAL; - WRITE_ONCE(*valp, *lvalp); + WRITE_ONCE(*k_ptr, *u_ptr); } } else { - int val = READ_ONCE(*valp); + int val = READ_ONCE(*k_ptr); if (val < 0) { *negp = true; - *lvalp = -(unsigned long)val; + *u_ptr = -(unsigned long)val; } else { *negp = false; - *lvalp = (unsigned long)val; + *u_ptr = (unsigned long)val; } } return 0; } -static int do_proc_douintvec_conv(unsigned long *lvalp, - unsigned int *valp, int dir, +static int do_proc_douintvec_conv(unsigned long *u_ptr, + unsigned int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - if (*lvalp > UINT_MAX) + if (*u_ptr > UINT_MAX) return -EINVAL; - WRITE_ONCE(*valp, *lvalp); + WRITE_ONCE(*k_ptr, *u_ptr); } else { - unsigned int val = READ_ONCE(*valp); - *lvalp = (unsigned long)val; + unsigned int val = READ_ONCE(*k_ptr); + *u_ptr = (unsigned long)val; } return 0; } @@ -415,7 +415,7 @@ static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; static int do_proc_dointvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(bool *negp, unsigned long *lvalp, int *valp, + int (*conv)(bool *negp, unsigned long *u_ptr, int *k_ptr, int dir, const struct ctl_table *table)) { int *i, vleft, first = 1, err = 0; @@ -487,8 +487,8 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(unsigned long *lvalp, - unsigned int *valp, int dir, + int (*conv)(unsigned long *u_ptr, + unsigned int *k_ptr, int dir, const struct ctl_table *table)) { unsigned long lval; @@ -540,8 +540,8 @@ static int do_proc_douintvec_w(const struct ctl_table *table, void *buffer, static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(unsigned long *lvalp, - unsigned int *valp, int dir, + int (*conv)(unsigned long *u_ptr, + unsigned int *k_ptr, int dir, const struct ctl_table *table)) { unsigned long lval; @@ -570,7 +570,7 @@ static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, int do_proc_douintvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, - int (*conv)(unsigned long *lvalp, unsigned int *valp, + int (*conv)(unsigned long *u_ptr, unsigned int *k_ptr, int dir, const struct ctl_table *table)) { unsigned int vleft; @@ -679,18 +679,18 @@ int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, do_proc_douintvec_conv); } -static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, - int *valp, int dir, +static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *u_ptr, + int *k_ptr, int dir, const struct ctl_table *table) { int tmp, ret, *min, *max; /* - * If writing, first do so via a temporary local int so we can - * bounds-check it before touching *valp. + * If writing to a kernel variable, first do so via a temporary + * local int so we can bounds-check it before touching *k_ptr. */ - int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : valp; + int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - ret = do_proc_dointvec_conv(negp, lvalp, ip, dir, table); + ret = do_proc_dointvec_conv(negp, u_ptr, ip, dir, table); if (ret) return ret; @@ -699,7 +699,7 @@ static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *lvalp, max = (int *) table->extra2; if ((min && *min > tmp) || (max && *max < tmp)) return -EINVAL; - WRITE_ONCE(*valp, tmp); + WRITE_ONCE(*k_ptr, tmp); } return 0; @@ -729,16 +729,16 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir, do_proc_dointvec_minmax_conv); } -static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, - unsigned int *valp, int dir, +static int do_proc_douintvec_minmax_conv(unsigned long *u_ptr, + unsigned int *k_ptr, int dir, const struct ctl_table *table) { int ret; unsigned int tmp, *min, *max; /* When writing to the kernel use a temp local uint for bounds-checking */ - unsigned int *up = SYSCTL_USER_TO_KERN(dir) ? &tmp : valp; + unsigned int *up = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - ret = do_proc_douintvec_conv(lvalp, up, dir, table); + ret = do_proc_douintvec_conv(u_ptr, up, dir, table); if (ret) return ret; @@ -748,7 +748,7 @@ static int do_proc_douintvec_minmax_conv(unsigned long *lvalp, if ((min && *min > tmp) || (max && *max < tmp)) return -ERANGE; - WRITE_ONCE(*valp, tmp); + WRITE_ONCE(*k_ptr, tmp); } return 0; @@ -953,19 +953,19 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, } -static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, int dir, +static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *u_ptr, + int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - if (*lvalp > INT_MAX / HZ) + if (*u_ptr > INT_MAX / HZ) return 1; if (*negp) - WRITE_ONCE(*valp, -*lvalp * HZ); + WRITE_ONCE(*k_ptr, -*u_ptr * HZ); else - WRITE_ONCE(*valp, *lvalp * HZ); + WRITE_ONCE(*k_ptr, *u_ptr * HZ); } else { - int val = READ_ONCE(*valp); + int val = READ_ONCE(*k_ptr); unsigned long lval; if (val < 0) { *negp = true; @@ -974,21 +974,21 @@ static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *lvalp, *negp = false; lval = (unsigned long)val; } - *lvalp = lval / HZ; + *u_ptr = lval / HZ; } return 0; } -static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, int dir, +static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *u_ptr, + int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - if (USER_HZ < HZ && *lvalp > (LONG_MAX / HZ) * USER_HZ) + if (USER_HZ < HZ && (LONG_MAX / HZ) * USER_HZ < *u_ptr) return 1; - *valp = clock_t_to_jiffies(*negp ? -*lvalp : *lvalp); + *k_ptr = clock_t_to_jiffies(*negp ? -*u_ptr : *u_ptr); } else { - int val = *valp; + int val = *k_ptr; unsigned long lval; if (val < 0) { *negp = true; @@ -997,23 +997,23 @@ static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *lvalp *negp = false; lval = (unsigned long)val; } - *lvalp = jiffies_to_clock_t(lval); + *u_ptr = jiffies_to_clock_t(lval); } return 0; } -static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, - int *valp, int dir, +static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *u_ptr, + int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - unsigned long jif = msecs_to_jiffies(*negp ? -*lvalp : *lvalp); + unsigned long jif = msecs_to_jiffies(*negp ? -*u_ptr : *u_ptr); if (jif > INT_MAX) return 1; - WRITE_ONCE(*valp, (int)jif); + WRITE_ONCE(*k_ptr, (int)jif); } else { - int val = READ_ONCE(*valp); + int val = READ_ONCE(*k_ptr); unsigned long lval; if (val < 0) { *negp = true; @@ -1022,23 +1022,23 @@ static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *lvalp, *negp = false; lval = (unsigned long)val; } - *lvalp = jiffies_to_msecs(lval); + *u_ptr = jiffies_to_msecs(lval); } return 0; } -static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lvalp, - int *valp, int dir, +static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *u_ptr, + int *k_ptr, int dir, const struct ctl_table *table) { int tmp, ret, *min, *max; /* - * If writing, first do so via a temporary local int so we can - * bounds-check it before touching *valp. + * If writing to a kernel var, first do so via a temporary local + * int so we can bounds-check it before touching *k_ptr. */ - int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : valp; + int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - ret = do_proc_dointvec_ms_jiffies_conv(negp, lvalp, ip, dir, table); + ret = do_proc_dointvec_ms_jiffies_conv(negp, u_ptr, ip, dir, table); if (ret) return ret; @@ -1047,7 +1047,7 @@ static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *lv max = (int *) table->extra2; if ((min && *min > tmp) || (max && *max < tmp)) return -EINVAL; - *valp = tmp; + *k_ptr = tmp; } return 0; } From 2dc164a48e6fdb03066614e2b82464b9b7c790ca Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Mon, 6 Oct 2025 14:40:01 +0200 Subject: [PATCH 08/21] sysctl: Create converter functions with two new macros Eight converter functions are created using two new macros (SYSCTL_USER_TO_KERN_INT_CONV & SYSCTL_KERN_TO_USER_INT_CONV); they are called from four pre-existing converter functions: do_proc_dointvec_conv and do_proc_dointvec{,_userhz,_ms}_jiffies_conv. The function names generated by the macros are differentiated by a string suffix passed as the first macro argument. The SYSCTL_USER_TO_KERN_INT_CONV macro first executes the u_ptr_op operation, then checks for overflow, assigns sign (-, +) and finally writes to the kernel var with WRITE_ONCE; it always returns an -EINVAL when an overflow is detected. The SYSCTL_KERN_TO_USER_INT_CONV uses READ_ONCE, casts to unsigned long, then executes the k_ptr_op before assigning the value to the user space buffer. The overflow check is always done against MAX_INT after applying {k,u}_ptr_op. This approach avoids rounding or precision errors that might occur when using the inverse operations. Signed-off-by: Joel Granados --- kernel/sysctl.c | 131 ++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 70 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 2091d2396c83..f47bb8af33fb 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -368,31 +368,65 @@ static void proc_put_char(void **buf, size_t *size, char c) } } +#define SYSCTL_USER_TO_KERN_INT_CONV(name, u_ptr_op) \ +int sysctl_user_to_kern_int_conv##name(const bool *negp, \ + const unsigned long *u_ptr,\ + int *k_ptr) \ +{ \ + unsigned long u = u_ptr_op(*u_ptr); \ + if (*negp) { \ + if (u > (unsigned long) INT_MAX + 1) \ + return -EINVAL; \ + WRITE_ONCE(*k_ptr, -u); \ + } else { \ + if (u > (unsigned long) INT_MAX) \ + return -EINVAL; \ + WRITE_ONCE(*k_ptr, u); \ + } \ + return 0; \ +} + +#define SYSCTL_KERN_TO_USER_INT_CONV(name, k_ptr_op) \ +int sysctl_kern_to_user_int_conv##name(bool *negp, \ + unsigned long *u_ptr, \ + const int *k_ptr) \ +{ \ + int val = READ_ONCE(*k_ptr); \ + if (val < 0) { \ + *negp = true; \ + *u_ptr = -k_ptr_op((unsigned long)val); \ + } else { \ + *negp = false; \ + *u_ptr = k_ptr_op((unsigned long)val); \ + } \ + return 0; \ +} + +#define SYSCTL_CONV_IDENTITY(val) val +#define SYSCTL_CONV_MULT_HZ(val) ((val) * HZ) +#define SYSCTL_CONV_DIV_HZ(val) ((val) / HZ) + +static SYSCTL_USER_TO_KERN_INT_CONV(, SYSCTL_CONV_IDENTITY) +static SYSCTL_KERN_TO_USER_INT_CONV(, SYSCTL_CONV_IDENTITY) + +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 int do_proc_dointvec_conv(bool *negp, unsigned long *u_ptr, int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - if (*negp) { - if (*u_ptr > (unsigned long) INT_MAX + 1) - return -EINVAL; - WRITE_ONCE(*k_ptr, -*u_ptr); - } else { - if (*u_ptr > (unsigned long) INT_MAX) - return -EINVAL; - WRITE_ONCE(*k_ptr, *u_ptr); - } - } else { - int val = READ_ONCE(*k_ptr); - if (val < 0) { - *negp = true; - *u_ptr = -(unsigned long)val; - } else { - *negp = false; - *u_ptr = (unsigned long)val; - } + return sysctl_user_to_kern_int_conv(negp, u_ptr, k_ptr); } - return 0; + + return sysctl_kern_to_user_int_conv(negp, u_ptr, k_ptr); } static int do_proc_douintvec_conv(unsigned long *u_ptr, @@ -952,31 +986,14 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, lenp, ppos, HZ, 1000l); } - static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *u_ptr, int *k_ptr, int dir, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - if (*u_ptr > INT_MAX / HZ) - return 1; - if (*negp) - WRITE_ONCE(*k_ptr, -*u_ptr * HZ); - else - WRITE_ONCE(*k_ptr, *u_ptr * HZ); - } else { - int val = READ_ONCE(*k_ptr); - unsigned long lval; - if (val < 0) { - *negp = true; - lval = -(unsigned long)val; - } else { - *negp = false; - lval = (unsigned long)val; - } - *u_ptr = lval / HZ; + return sysctl_user_to_kern_int_conv_hz(negp, u_ptr, k_ptr); } - return 0; + return sysctl_kern_to_user_int_conv_hz(negp, u_ptr, k_ptr); } static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *u_ptr, @@ -984,22 +1001,11 @@ static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *u_ptr const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - if (USER_HZ < HZ && (LONG_MAX / HZ) * USER_HZ < *u_ptr) - return 1; - *k_ptr = clock_t_to_jiffies(*negp ? -*u_ptr : *u_ptr); - } else { - int val = *k_ptr; - unsigned long lval; - if (val < 0) { - *negp = true; - lval = -(unsigned long)val; - } else { - *negp = false; - lval = (unsigned long)val; - } - *u_ptr = jiffies_to_clock_t(lval); + if (USER_HZ < HZ) + return -EINVAL; + return sysctl_user_to_kern_int_conv_userhz(negp, u_ptr, k_ptr); } - return 0; + return sysctl_kern_to_user_int_conv_userhz(negp, u_ptr, k_ptr); } static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *u_ptr, @@ -1007,24 +1013,9 @@ static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *u_ptr, const struct ctl_table *table) { if (SYSCTL_USER_TO_KERN(dir)) { - unsigned long jif = msecs_to_jiffies(*negp ? -*u_ptr : *u_ptr); - - if (jif > INT_MAX) - return 1; - WRITE_ONCE(*k_ptr, (int)jif); - } else { - int val = READ_ONCE(*k_ptr); - unsigned long lval; - if (val < 0) { - *negp = true; - lval = -(unsigned long)val; - } else { - *negp = false; - lval = (unsigned long)val; - } - *u_ptr = jiffies_to_msecs(lval); + return sysctl_user_to_kern_int_conv_ms(negp, u_ptr, k_ptr); } - return 0; + return sysctl_kern_to_user_int_conv_ms(negp, u_ptr, k_ptr); } static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *u_ptr, From 796c481a4b7026a93e4800bff8d99923630014e7 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Wed, 8 Oct 2025 09:58:46 +0200 Subject: [PATCH 09/21] sysctl: Create integer converters with one macro New SYSCTL_INT_CONV_CUSTOM macro creates "bi-directional" converters from a user-to-kernel and a kernel-to-user functions. Replace integer versions of do_proc_*_conv functions with the ones from the new macro. Rename "_dointvec_" to just "_int_" as these converters are not applied to vectors and the "do" is already in the name. Move the USER_HZ validation directly into proc_dointvec_userhz_jiffies() Signed-off-by: Joel Granados --- kernel/sysctl.c | 72 +++++++++++++++++-------------------------------- 1 file changed, 25 insertions(+), 47 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index f47bb8af33fb..d81ae3a64efc 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -418,17 +418,25 @@ 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 int do_proc_dointvec_conv(bool *negp, unsigned long *u_ptr, - int *k_ptr, int dir, - const struct ctl_table *table) -{ - if (SYSCTL_USER_TO_KERN(dir)) { - return sysctl_user_to_kern_int_conv(negp, u_ptr, k_ptr); - } - - return sysctl_kern_to_user_int_conv(negp, u_ptr, k_ptr); +#define SYSCTL_INT_CONV_CUSTOM(name, user_to_kern, kern_to_user) \ +int do_proc_int_conv##name(bool *negp, unsigned long *u_ptr, int *k_ptr,\ + int dir, const struct ctl_table *table) \ +{ \ + if (SYSCTL_USER_TO_KERN(dir)) \ + return user_to_kern(negp, u_ptr, k_ptr); \ + return kern_to_user(negp, u_ptr, k_ptr); \ } +static SYSCTL_INT_CONV_CUSTOM(, sysctl_user_to_kern_int_conv, + sysctl_kern_to_user_int_conv) +static SYSCTL_INT_CONV_CUSTOM(_jiffies, sysctl_user_to_kern_int_conv_hz, + sysctl_kern_to_user_int_conv_hz) +static SYSCTL_INT_CONV_CUSTOM(_userhz_jiffies, + sysctl_user_to_kern_int_conv_userhz, + sysctl_kern_to_user_int_conv_userhz) +static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies, sysctl_user_to_kern_int_conv_ms, + sysctl_kern_to_user_int_conv_ms) + static int do_proc_douintvec_conv(unsigned long *u_ptr, unsigned int *k_ptr, int dir, const struct ctl_table *table) @@ -467,7 +475,7 @@ static int do_proc_dointvec(const struct ctl_table *table, int dir, left = *lenp; if (!conv) - conv = do_proc_dointvec_conv; + conv = do_proc_int_conv; if (SYSCTL_USER_TO_KERN(dir)) { if (proc_first_pos_non_zero_ignore(ppos, table)) @@ -724,7 +732,7 @@ static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *u_ptr, */ int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - ret = do_proc_dointvec_conv(negp, u_ptr, ip, dir, table); + ret = do_proc_int_conv(negp, u_ptr, ip, dir, table); if (ret) return ret; @@ -986,38 +994,6 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, lenp, ppos, HZ, 1000l); } -static int do_proc_dointvec_jiffies_conv(bool *negp, unsigned long *u_ptr, - int *k_ptr, int dir, - const struct ctl_table *table) -{ - if (SYSCTL_USER_TO_KERN(dir)) { - return sysctl_user_to_kern_int_conv_hz(negp, u_ptr, k_ptr); - } - return sysctl_kern_to_user_int_conv_hz(negp, u_ptr, k_ptr); -} - -static int do_proc_dointvec_userhz_jiffies_conv(bool *negp, unsigned long *u_ptr, - int *k_ptr, int dir, - const struct ctl_table *table) -{ - if (SYSCTL_USER_TO_KERN(dir)) { - if (USER_HZ < HZ) - return -EINVAL; - return sysctl_user_to_kern_int_conv_userhz(negp, u_ptr, k_ptr); - } - return sysctl_kern_to_user_int_conv_userhz(negp, u_ptr, k_ptr); -} - -static int do_proc_dointvec_ms_jiffies_conv(bool *negp, unsigned long *u_ptr, - int *k_ptr, int dir, - const struct ctl_table *table) -{ - if (SYSCTL_USER_TO_KERN(dir)) { - return sysctl_user_to_kern_int_conv_ms(negp, u_ptr, k_ptr); - } - return sysctl_kern_to_user_int_conv_ms(negp, u_ptr, k_ptr); -} - static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *u_ptr, int *k_ptr, int dir, const struct ctl_table *table) @@ -1029,7 +1005,7 @@ static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *u_ */ int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - ret = do_proc_dointvec_ms_jiffies_conv(negp, u_ptr, ip, dir, table); + ret = do_proc_int_conv_ms_jiffies(negp, u_ptr, ip, dir, table); if (ret) return ret; @@ -1062,7 +1038,7 @@ int proc_dointvec_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_dointvec_jiffies_conv); + do_proc_int_conv_jiffies); } int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, @@ -1090,8 +1066,10 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { + if (SYSCTL_USER_TO_KERN(dir) && USER_HZ < HZ) + return -EINVAL; return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_dointvec_userhz_jiffies_conv); + do_proc_int_conv_userhz_jiffies); } /** @@ -1113,7 +1091,7 @@ int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffe size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_dointvec_ms_jiffies_conv); + do_proc_int_conv_ms_jiffies); } /** From 54e77495a7c5f64bc2d35583a4f59c8dbee579ab Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Sun, 12 Oct 2025 11:24:41 +0200 Subject: [PATCH 10/21] sysctl: Add optional range checking to SYSCTL_INT_CONV_CUSTOM Extend the SYSCTL_INT_CONV_CUSTOM macro with a k_ptr_range_check parameter to conditionally generate range validation code. When enabled, validation is done against table->extra1 (min) and table->extra2 (max) bounds before assignment. Add base minmax and ms_jiffies_minmax converter instances that utilize the range checking functionality. Signed-off-by: Joel Granados --- kernel/sysctl.c | 106 ++++++++++++++++++------------------------------ 1 file changed, 40 insertions(+), 66 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index d81ae3a64efc..df96d3355dfc 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -402,6 +402,34 @@ int sysctl_kern_to_user_int_conv##name(bool *negp, \ return 0; \ } +/** + * To range check on a converted value, use a temp k_ptr + * When checking range, value should be within (tbl->extra1, tbl->extra2) + */ +#define SYSCTL_INT_CONV_CUSTOM(name, user_to_kern, kern_to_user, \ + k_ptr_range_check) \ +int do_proc_int_conv##name(bool *negp, unsigned long *u_ptr, int *k_ptr,\ + int dir, const struct ctl_table *tbl) \ +{ \ + 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; \ +} + #define SYSCTL_CONV_IDENTITY(val) val #define SYSCTL_CONV_MULT_HZ(val) ((val) * HZ) #define SYSCTL_CONV_DIV_HZ(val) ((val) / HZ) @@ -418,24 +446,21 @@ 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) -#define SYSCTL_INT_CONV_CUSTOM(name, user_to_kern, kern_to_user) \ -int do_proc_int_conv##name(bool *negp, unsigned long *u_ptr, int *k_ptr,\ - int dir, const struct ctl_table *table) \ -{ \ - if (SYSCTL_USER_TO_KERN(dir)) \ - return user_to_kern(negp, u_ptr, k_ptr); \ - return kern_to_user(negp, u_ptr, k_ptr); \ -} - static SYSCTL_INT_CONV_CUSTOM(, sysctl_user_to_kern_int_conv, - sysctl_kern_to_user_int_conv) + sysctl_kern_to_user_int_conv, false) static SYSCTL_INT_CONV_CUSTOM(_jiffies, sysctl_user_to_kern_int_conv_hz, - sysctl_kern_to_user_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) + 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) + sysctl_kern_to_user_int_conv_ms, false) + +static SYSCTL_INT_CONV_CUSTOM(_minmax, sysctl_user_to_kern_int_conv, + sysctl_kern_to_user_int_conv, true) +static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax, + sysctl_user_to_kern_int_conv_ms, + sysctl_kern_to_user_int_conv_ms, true) static int do_proc_douintvec_conv(unsigned long *u_ptr, unsigned int *k_ptr, int dir, @@ -721,32 +746,6 @@ int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, do_proc_douintvec_conv); } -static int do_proc_dointvec_minmax_conv(bool *negp, unsigned long *u_ptr, - int *k_ptr, int dir, - const struct ctl_table *table) -{ - int tmp, ret, *min, *max; - /* - * If writing to a kernel variable, first do so via a temporary - * local int so we can bounds-check it before touching *k_ptr. - */ - int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - - ret = do_proc_int_conv(negp, u_ptr, ip, dir, table); - if (ret) - return ret; - - if (SYSCTL_USER_TO_KERN(dir)) { - min = (int *) table->extra1; - max = (int *) table->extra2; - if ((min && *min > tmp) || (max && *max < tmp)) - return -EINVAL; - WRITE_ONCE(*k_ptr, tmp); - } - - return 0; -} - /** * proc_dointvec_minmax - read a vector of integers with min/max values * @table: the sysctl table @@ -768,7 +767,7 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_dointvec_minmax_conv); + do_proc_int_conv_minmax); } static int do_proc_douintvec_minmax_conv(unsigned long *u_ptr, @@ -994,31 +993,6 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, lenp, ppos, HZ, 1000l); } -static int do_proc_dointvec_ms_jiffies_minmax_conv(bool *negp, unsigned long *u_ptr, - int *k_ptr, int dir, - const struct ctl_table *table) -{ - int tmp, ret, *min, *max; - /* - * If writing to a kernel var, first do so via a temporary local - * int so we can bounds-check it before touching *k_ptr. - */ - int *ip = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - - ret = do_proc_int_conv_ms_jiffies(negp, u_ptr, ip, dir, table); - if (ret) - return ret; - - if (SYSCTL_USER_TO_KERN(dir)) { - min = (int *) table->extra1; - max = (int *) table->extra2; - if ((min && *min > tmp) || (max && *max < tmp)) - return -EINVAL; - *k_ptr = tmp; - } - return 0; -} - /** * proc_dointvec_jiffies - read a vector of integers as seconds * @table: the sysctl table @@ -1045,7 +1019,7 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_dointvec_ms_jiffies_minmax_conv); + do_proc_int_conv_ms_jiffies_minmax); } /** From 49d3288c1d83178d25e2a8cc6c978de35a616d42 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 16 Oct 2025 09:06:21 +0200 Subject: [PATCH 11/21] sysctl: Create unsigned int converter using new macro Pass sysctl_{user_to_kern,kern_to_user}_uint_conv (unsigned integer uni-directional converters) to the new SYSCTL_UINT_CONV_CUSTOM macro to create do_proc_douintvec_conv's replacement (do_proc_uint_conv). This is a preparation commit to use the unsigned integer converter from outside sysctl. No functional change is intended. Signed-off-by: Joel Granados --- kernel/sysctl.c | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index df96d3355dfc..fb4f4ec99811 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -462,23 +462,36 @@ static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax, sysctl_user_to_kern_int_conv_ms, sysctl_kern_to_user_int_conv_ms, true) -static int do_proc_douintvec_conv(unsigned long *u_ptr, - unsigned int *k_ptr, int dir, - const struct ctl_table *table) +#define SYSCTL_UINT_CONV_CUSTOM(name, user_to_kern, kern_to_user) \ +int do_proc_uint_conv##name(unsigned long *u_ptr, unsigned int *k_ptr, \ + int dir, const struct ctl_table *tbl) \ +{ \ + if (SYSCTL_USER_TO_KERN(dir)) \ + return user_to_kern(u_ptr, k_ptr); \ + return kern_to_user(u_ptr, k_ptr); \ +} + +static int sysctl_user_to_kern_uint_conv(const unsigned long *u_ptr, + unsigned int *k_ptr) { - if (SYSCTL_USER_TO_KERN(dir)) { - if (*u_ptr > UINT_MAX) - return -EINVAL; - WRITE_ONCE(*k_ptr, *u_ptr); - } else { - unsigned int val = READ_ONCE(*k_ptr); - *u_ptr = (unsigned long)val; - } + if (*u_ptr > UINT_MAX) + return -EINVAL; + WRITE_ONCE(*k_ptr, *u_ptr); return 0; } -static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; +static int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr, + const unsigned int *k_ptr) +{ + unsigned int val = READ_ONCE(*k_ptr); + *u_ptr = (unsigned long)val; + return 0; +} +static SYSCTL_UINT_CONV_CUSTOM(, sysctl_user_to_kern_uint_conv, + sysctl_kern_to_user_uint_conv) + +static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; static int do_proc_dointvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos, @@ -660,7 +673,7 @@ int do_proc_douintvec(const struct ctl_table *table, int dir, void *buffer, } if (!conv) - conv = do_proc_douintvec_conv; + conv = do_proc_uint_conv; if (SYSCTL_USER_TO_KERN(dir)) return do_proc_douintvec_w(table, buffer, lenp, ppos, conv); @@ -743,7 +756,7 @@ int proc_douintvec(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_douintvec(table, dir, buffer, lenp, ppos, - do_proc_douintvec_conv); + do_proc_uint_conv); } /** @@ -779,7 +792,7 @@ static int do_proc_douintvec_minmax_conv(unsigned long *u_ptr, /* When writing to the kernel use a temp local uint for bounds-checking */ unsigned int *up = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - ret = do_proc_douintvec_conv(u_ptr, up, dir, table); + ret = do_proc_uint_conv(u_ptr, up, dir, table); if (ret) return ret; From 0c1d2dc7cce78445f723d32b57f430c42316cab0 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 16 Oct 2025 09:46:14 +0200 Subject: [PATCH 12/21] sysctl: Add optional range checking to SYSCTL_UINT_CONV_CUSTOM Add k_ptr_range_check parameter to SYSCTL_UINT_CONV_CUSTOM macro to enable range validation using table->extra1/extra2. Replace do_proc_douintvec_minmax_conv with do_proc_uint_conv_minmax generated by the updated macro. Signed-off-by: Joel Granados --- kernel/sysctl.c | 69 +++++++++++++++++++++++-------------------------- 1 file changed, 32 insertions(+), 37 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index fb4f4ec99811..1e8d330a773d 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -462,15 +462,6 @@ static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax, sysctl_user_to_kern_int_conv_ms, sysctl_kern_to_user_int_conv_ms, true) -#define SYSCTL_UINT_CONV_CUSTOM(name, user_to_kern, kern_to_user) \ -int do_proc_uint_conv##name(unsigned long *u_ptr, unsigned int *k_ptr, \ - int dir, const struct ctl_table *tbl) \ -{ \ - if (SYSCTL_USER_TO_KERN(dir)) \ - return user_to_kern(u_ptr, k_ptr); \ - return kern_to_user(u_ptr, k_ptr); \ -} - static int sysctl_user_to_kern_uint_conv(const unsigned long *u_ptr, unsigned int *k_ptr) { @@ -488,8 +479,37 @@ static int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr, return 0; } +#define SYSCTL_UINT_CONV_CUSTOM(name, user_to_kern, kern_to_user, \ + k_ptr_range_check) \ +int do_proc_uint_conv##name(unsigned long *u_ptr, unsigned int *k_ptr, \ + int dir, const struct ctl_table *tbl) \ +{ \ + if (SYSCTL_KERN_TO_USER(dir)) \ + return kern_to_user(u_ptr, k_ptr); \ + \ + if (k_ptr_range_check) { \ + unsigned int tmp_k; \ + int ret; \ + if (!tbl) \ + return -EINVAL; \ + ret = user_to_kern(u_ptr, &tmp_k); \ + if (ret) \ + return ret; \ + if ((tbl->extra1 && \ + *(unsigned int *)tbl->extra1 > tmp_k) || \ + (tbl->extra2 && \ + *(unsigned int *)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_UINT_CONV_CUSTOM(, sysctl_user_to_kern_uint_conv, - sysctl_kern_to_user_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 const char proc_wspace_sep[] = { ' ', '\t', '\n' }; @@ -783,31 +803,6 @@ int proc_dointvec_minmax(const struct ctl_table *table, int dir, do_proc_int_conv_minmax); } -static int do_proc_douintvec_minmax_conv(unsigned long *u_ptr, - unsigned int *k_ptr, int dir, - const struct ctl_table *table) -{ - int ret; - unsigned int tmp, *min, *max; - /* When writing to the kernel use a temp local uint for bounds-checking */ - unsigned int *up = SYSCTL_USER_TO_KERN(dir) ? &tmp : k_ptr; - - ret = do_proc_uint_conv(u_ptr, up, dir, table); - if (ret) - return ret; - - if (SYSCTL_USER_TO_KERN(dir)) { - min = (unsigned int *) table->extra1; - max = (unsigned int *) table->extra2; - if ((min && *min > tmp) || (max && *max < tmp)) - return -ERANGE; - - WRITE_ONCE(*k_ptr, tmp); - } - - return 0; -} - /** * proc_douintvec_minmax - read a vector of unsigned ints with min/max values * @table: the sysctl table @@ -832,7 +827,7 @@ int proc_douintvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_douintvec(table, dir, buffer, lenp, ppos, - do_proc_douintvec_minmax_conv); + do_proc_uint_conv_minmax); } /** @@ -876,7 +871,7 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir, val = READ_ONCE(*data); res = do_proc_douintvec(&tmp, dir, buffer, lenp, ppos, - do_proc_douintvec_minmax_conv); + do_proc_uint_conv_minmax); if (res) return res; if (SYSCTL_USER_TO_KERN(dir)) From c3102febf43ba8a9ce5512a49103f42661659a16 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 16 Oct 2025 10:22:16 +0200 Subject: [PATCH 13/21] sysctl: Create macro for user-to-kernel uint converter Replace sysctl_user_to_kern_uint_conv function with SYSCTL_USER_TO_KERN_UINT_CONV macro that accepts u_ptr_op parameter for value transformation. Replacing sysctl_kern_to_user_uint_conv is not needed as it will only be used from within sysctl.c. This is a preparation commit for creating a custom converter in fs/pipe.c. No Functional changes are intended. Signed-off-by: Joel Granados --- kernel/sysctl.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 1e8d330a773d..00595b84beac 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -462,15 +462,19 @@ static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax, sysctl_user_to_kern_int_conv_ms, sysctl_kern_to_user_int_conv_ms, true) -static int sysctl_user_to_kern_uint_conv(const unsigned long *u_ptr, - unsigned int *k_ptr) -{ - if (*u_ptr > UINT_MAX) - return -EINVAL; - WRITE_ONCE(*k_ptr, *u_ptr); - return 0; +#define SYSCTL_USER_TO_KERN_UINT_CONV(name, u_ptr_op) \ +int sysctl_user_to_kern_uint_conv##name(const unsigned long *u_ptr,\ + unsigned int *k_ptr) \ +{ \ + unsigned long u = u_ptr_op(*u_ptr); \ + if (u > UINT_MAX) \ + return -EINVAL; \ + WRITE_ONCE(*k_ptr, u); \ + return 0; \ } +static SYSCTL_USER_TO_KERN_UINT_CONV(, SYSCTL_CONV_IDENTITY) + static int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr, const unsigned int *k_ptr) { From 1aa53326e1df68f8e7191053a3fd712449d444cb Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Mon, 27 Oct 2025 11:24:17 +0100 Subject: [PATCH 14/21] sysctl: remove __user qualifier from stack_erasing_sysctl buffer argument The buffer arg in proc handler functions have been void* (no __user qualifier) since commit 32927393dc1c ("sysctl: pass kernel pointers to ->proc_handler"). The __user qualifier was erroneously brought back in commit 0df8bdd5e3b3 ("stackleak: move stack_erasing sysctl to stackleak.c"). This fixes the error by removing the __user qualifier. Reported-by: kernel test robot Closes: https://lore.kernel.org/oe-kbuild-all/202510221719.3ggn070M-lkp@intel.com/ Signed-off-by: Joel Granados --- kernel/kstack_erase.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/kstack_erase.c b/kernel/kstack_erase.c index e49bb88b4f0a..d4449884084c 100644 --- a/kernel/kstack_erase.c +++ b/kernel/kstack_erase.c @@ -23,7 +23,7 @@ static DEFINE_STATIC_KEY_FALSE(stack_erasing_bypass); #ifdef CONFIG_SYSCTL static int stack_erasing_sysctl(const struct ctl_table *table, int write, - void __user *buffer, size_t *lenp, loff_t *ppos) + void *buffer, size_t *lenp, loff_t *ppos) { int ret = 0; int state = !static_branch_unlikely(&stack_erasing_bypass); From c5b4c183f7aeb46cd27ddea9dab776655b8d7034 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Wed, 8 Oct 2025 16:12:37 +0200 Subject: [PATCH 15/21] sysctl: Allow custom converters from outside sysctl The new non-static proc_dointvec_conv forwards a custom converter function to do_proc_dointvec from outside the sysctl scope. Rename the do_proc_dointvec call points so any future changes to proc_dointvec_conv are propagated in sysctl.c This is a preparation commit that allows the integer jiffie converter functions to move out of kernel/sysctl.c. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 4 ++++ kernel/sysctl.c | 32 ++++++++++++++++++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 436191e569da..a48273757c99 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -68,6 +68,10 @@ int proc_dostring(const struct ctl_table *, int, void *, size_t *, loff_t *); 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_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, + int dir, const struct ctl_table *table)); int proc_douintvec(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_dointvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer, diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 00595b84beac..833ed04f52dc 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -1005,6 +1005,14 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, lenp, ppos, HZ, 1000l); } +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, + int dir, const struct ctl_table *table)) +{ + return do_proc_dointvec(table, dir, buffer, lenp, ppos, conv); +} + /** * proc_dointvec_jiffies - read a vector of integers as seconds * @table: the sysctl table @@ -1023,15 +1031,15 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, int proc_dointvec_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_int_conv_jiffies); + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_jiffies); } int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_int_conv_ms_jiffies_minmax); + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_ms_jiffies_minmax); } /** @@ -1054,8 +1062,8 @@ int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, { if (SYSCTL_USER_TO_KERN(dir) && USER_HZ < HZ) return -EINVAL; - return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_int_conv_userhz_jiffies); + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_userhz_jiffies); } /** @@ -1076,8 +1084,8 @@ int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_dointvec(table, dir, buffer, lenp, ppos, - do_proc_int_conv_ms_jiffies); + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_ms_jiffies); } /** @@ -1307,6 +1315,14 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, return -ENOSYS; } +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, + int dir, const struct ctl_table *table)) +{ + return -ENOSYS; +} + int proc_do_large_bitmap(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { From e2e5dac304fdf991fb974510db4565db04ef1335 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Tue, 14 Oct 2025 12:42:01 +0200 Subject: [PATCH 16/21] sysctl: Move INT converter macros to sysctl header Move direction macros (SYSCTL_{USER_TO_KERN,KERN_TO_USER}) and the integer converter macros (SYSCTL_{USER_TO_KERN,KERN_TO_USER}_INT_CONV, SYSCTL_INT_CONV_CUSTOM) into include/linux/sysctl.h. This is a preparation commit to enable jiffies converter creation outside kernel/sysctl.c. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 75 ++++++++++++++++++++++++++++++++++++++++++ kernel/sysctl.c | 75 ------------------------------------------ 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index a48273757c99..a0ca9496119a 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -59,6 +59,81 @@ extern const int sysctl_vals[]; #define SYSCTL_LONG_ONE ((void *)&sysctl_long_vals[1]) #define SYSCTL_LONG_MAX ((void *)&sysctl_long_vals[2]) +/** + * + * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1) + * in the file_operations struct at proc/proc_sysctl.c. Its value means + * one of two things for sysctl: + * 1. SYSCTL_USER_TO_KERN(dir) Writing to an internal kernel variable from user + * space (dir > 0) + * 2. SYSCTL_KERN_TO_USER(dir) Writing to a user space buffer from a kernel + * variable (dir == 0). + */ +#define SYSCTL_USER_TO_KERN(dir) (!!(dir)) +#define SYSCTL_KERN_TO_USER(dir) (!dir) + +#define SYSCTL_USER_TO_KERN_INT_CONV(name, u_ptr_op) \ +int sysctl_user_to_kern_int_conv##name(const bool *negp, \ + const unsigned long *u_ptr,\ + int *k_ptr) \ +{ \ + unsigned long u = u_ptr_op(*u_ptr); \ + if (*negp) { \ + if (u > (unsigned long) INT_MAX + 1) \ + return -EINVAL; \ + WRITE_ONCE(*k_ptr, -u); \ + } else { \ + if (u > (unsigned long) INT_MAX) \ + return -EINVAL; \ + WRITE_ONCE(*k_ptr, u); \ + } \ + return 0; \ +} + +#define SYSCTL_KERN_TO_USER_INT_CONV(name, k_ptr_op) \ +int sysctl_kern_to_user_int_conv##name(bool *negp, \ + unsigned long *u_ptr, \ + const int *k_ptr) \ +{ \ + int val = READ_ONCE(*k_ptr); \ + if (val < 0) { \ + *negp = true; \ + *u_ptr = -k_ptr_op((unsigned long)val); \ + } else { \ + *negp = false; \ + *u_ptr = k_ptr_op((unsigned long)val); \ + } \ + return 0; \ +} + +/** + * To range check on a converted value, use a temp k_ptr + * When checking range, value should be within (tbl->extra1, tbl->extra2) + */ +#define SYSCTL_INT_CONV_CUSTOM(name, user_to_kern, kern_to_user, \ + k_ptr_range_check) \ +int do_proc_int_conv##name(bool *negp, unsigned long *u_ptr, int *k_ptr,\ + int dir, const struct ctl_table *tbl) \ +{ \ + 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; \ +} + extern const unsigned long sysctl_long_vals[]; typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer, diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 833ed04f52dc..0a33d92904de 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -30,19 +30,6 @@ EXPORT_SYMBOL(sysctl_vals); const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX }; EXPORT_SYMBOL_GPL(sysctl_long_vals); -/** - * - * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1) - * in the file_operations struct at proc/proc_sysctl.c. Its value means - * one of two things for sysctl: - * 1. SYSCTL_USER_TO_KERN(dir) Writing to an internal kernel variable from user - * space (dir > 0) - * 2. SYSCTL_KERN_TO_USER(dir) Writing to a user space buffer from a kernel - * variable (dir == 0). - */ -#define SYSCTL_USER_TO_KERN(dir) (!!(dir)) -#define SYSCTL_KERN_TO_USER(dir) (!dir) - #if defined(CONFIG_SYSCTL) /* Constants used for minimum and maximum */ @@ -368,68 +355,6 @@ static void proc_put_char(void **buf, size_t *size, char c) } } -#define SYSCTL_USER_TO_KERN_INT_CONV(name, u_ptr_op) \ -int sysctl_user_to_kern_int_conv##name(const bool *negp, \ - const unsigned long *u_ptr,\ - int *k_ptr) \ -{ \ - unsigned long u = u_ptr_op(*u_ptr); \ - if (*negp) { \ - if (u > (unsigned long) INT_MAX + 1) \ - return -EINVAL; \ - WRITE_ONCE(*k_ptr, -u); \ - } else { \ - if (u > (unsigned long) INT_MAX) \ - return -EINVAL; \ - WRITE_ONCE(*k_ptr, u); \ - } \ - return 0; \ -} - -#define SYSCTL_KERN_TO_USER_INT_CONV(name, k_ptr_op) \ -int sysctl_kern_to_user_int_conv##name(bool *negp, \ - unsigned long *u_ptr, \ - const int *k_ptr) \ -{ \ - int val = READ_ONCE(*k_ptr); \ - if (val < 0) { \ - *negp = true; \ - *u_ptr = -k_ptr_op((unsigned long)val); \ - } else { \ - *negp = false; \ - *u_ptr = k_ptr_op((unsigned long)val); \ - } \ - return 0; \ -} - -/** - * To range check on a converted value, use a temp k_ptr - * When checking range, value should be within (tbl->extra1, tbl->extra2) - */ -#define SYSCTL_INT_CONV_CUSTOM(name, user_to_kern, kern_to_user, \ - k_ptr_range_check) \ -int do_proc_int_conv##name(bool *negp, unsigned long *u_ptr, int *k_ptr,\ - int dir, const struct ctl_table *tbl) \ -{ \ - 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; \ -} - #define SYSCTL_CONV_IDENTITY(val) val #define SYSCTL_CONV_MULT_HZ(val) ((val) * HZ) #define SYSCTL_CONV_DIV_HZ(val) ((val) / HZ) From 24a08eefddb33c7a259975e932c434b85f70d684 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 16 Oct 2025 10:38:45 +0200 Subject: [PATCH 17/21] sysctl: Move UINT converter macros to sysctl header Move SYSCTL_USER_TO_KERN_UINT_CONV and SYSCTL_UINT_CONV_CUSTOM macros to include/linux/sysctl.h. No need to embed sysctl_kern_to_user_uint_conv in a macro as it will not need a custom kernel pointer operation. This is a preparation commit to enable jiffies converter creation outside kernel/sysctl.c. Signed-off-by: Joel Granados --- include/linux/sysctl.h | 40 ++++++++++++++++++++++++++++++++++++++++ kernel/sysctl.c | 41 ++--------------------------------------- 2 files changed, 42 insertions(+), 39 deletions(-) diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index a0ca9496119a..fa78136617ad 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -134,6 +134,45 @@ int do_proc_int_conv##name(bool *negp, unsigned long *u_ptr, int *k_ptr,\ return 0; \ } +#define SYSCTL_USER_TO_KERN_UINT_CONV(name, u_ptr_op) \ +int sysctl_user_to_kern_uint_conv##name(const unsigned long *u_ptr,\ + unsigned int *k_ptr) \ +{ \ + unsigned long u = u_ptr_op(*u_ptr); \ + if (u > UINT_MAX) \ + return -EINVAL; \ + WRITE_ONCE(*k_ptr, u); \ + return 0; \ +} + +#define SYSCTL_UINT_CONV_CUSTOM(name, user_to_kern, kern_to_user, \ + k_ptr_range_check) \ +int do_proc_uint_conv##name(unsigned long *u_ptr, unsigned int *k_ptr, \ + int dir, const struct ctl_table *tbl) \ +{ \ + if (SYSCTL_KERN_TO_USER(dir)) \ + return kern_to_user(u_ptr, k_ptr); \ + \ + if (k_ptr_range_check) { \ + unsigned int tmp_k; \ + int ret; \ + if (!tbl) \ + return -EINVAL; \ + ret = user_to_kern(u_ptr, &tmp_k); \ + if (ret) \ + return ret; \ + if ((tbl->extra1 && \ + *(unsigned int *)tbl->extra1 > tmp_k) || \ + (tbl->extra2 && \ + *(unsigned int *)tbl->extra2 < tmp_k)) \ + return -ERANGE; \ + WRITE_ONCE(*k_ptr, tmp_k); \ + } else \ + return user_to_kern(u_ptr, k_ptr); \ + return 0; \ +} + + extern const unsigned long sysctl_long_vals[]; typedef int proc_handler(const struct ctl_table *ctl, int write, void *buffer, @@ -166,6 +205,7 @@ int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int, void * int proc_do_large_bitmap(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_do_static_key(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); +int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr, const unsigned int *k_ptr); /* * Register a set of sysctl names by calling register_sysctl diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 0a33d92904de..5a79622ad1cd 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -387,54 +387,17 @@ static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax, sysctl_user_to_kern_int_conv_ms, sysctl_kern_to_user_int_conv_ms, true) -#define SYSCTL_USER_TO_KERN_UINT_CONV(name, u_ptr_op) \ -int sysctl_user_to_kern_uint_conv##name(const unsigned long *u_ptr,\ - unsigned int *k_ptr) \ -{ \ - unsigned long u = u_ptr_op(*u_ptr); \ - if (u > UINT_MAX) \ - return -EINVAL; \ - WRITE_ONCE(*k_ptr, u); \ - return 0; \ -} static SYSCTL_USER_TO_KERN_UINT_CONV(, SYSCTL_CONV_IDENTITY) -static int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr, - const unsigned int *k_ptr) +int sysctl_kern_to_user_uint_conv(unsigned long *u_ptr, + const unsigned int *k_ptr) { unsigned int val = READ_ONCE(*k_ptr); *u_ptr = (unsigned long)val; return 0; } -#define SYSCTL_UINT_CONV_CUSTOM(name, user_to_kern, kern_to_user, \ - k_ptr_range_check) \ -int do_proc_uint_conv##name(unsigned long *u_ptr, unsigned int *k_ptr, \ - int dir, const struct ctl_table *tbl) \ -{ \ - if (SYSCTL_KERN_TO_USER(dir)) \ - return kern_to_user(u_ptr, k_ptr); \ - \ - if (k_ptr_range_check) { \ - unsigned int tmp_k; \ - int ret; \ - if (!tbl) \ - return -EINVAL; \ - ret = user_to_kern(u_ptr, &tmp_k); \ - if (ret) \ - return ret; \ - if ((tbl->extra1 && \ - *(unsigned int *)tbl->extra1 > tmp_k) || \ - (tbl->extra2 && \ - *(unsigned int *)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_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, From 54932988c4230925d2bf0023509ac2fee59a089a Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Tue, 14 Oct 2025 13:04:16 +0200 Subject: [PATCH 18/21] sysctl: Move jiffies converters to kernel/time/jiffies.c Move integer jiffies converters (proc_dointvec{_,_ms_,_userhz_}jiffies and proc_dointvec_ms_jiffies_minmax) to kernel/time/jiffies.c. Error stubs for when CONFIG_PRCO_SYSCTL is not defined are not reproduced because all the jiffies converters go through proc_dointvec_conv which is already stubbed. This is part of the greater effort to move sysctl logic out of kernel/sysctl.c thereby reducing merge conflicts in kernel/sysctl.c. Signed-off-by: Joel Granados --- include/linux/jiffies.h | 10 ++++ include/linux/sysctl.h | 7 --- kernel/sysctl.c | 124 ---------------------------------------- kernel/time/jiffies.c | 100 ++++++++++++++++++++++++++++++++ 4 files changed, 110 insertions(+), 131 deletions(-) diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h index 0d1927da8055..72d589a8a0d6 100644 --- a/include/linux/jiffies.h +++ b/include/linux/jiffies.h @@ -611,4 +611,14 @@ extern unsigned long nsecs_to_jiffies(u64 n); #define TIMESTAMP_SIZE 30 +struct ctl_table; +int proc_dointvec_jiffies(const struct ctl_table *table, int dir, void *buffer, + size_t *lenp, loff_t *ppos); +int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos); +int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos); +int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, + size_t *lenp, loff_t *ppos); + #endif diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index fa78136617ad..db4020f6933b 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -192,13 +192,6 @@ int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer size_t *lenp, loff_t *ppos); int proc_dou8vec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); -int proc_dointvec_jiffies(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int write, - void *buffer, size_t *lenp, loff_t *ppos); -int proc_dointvec_userhz_jiffies(const struct ctl_table *, int, void *, size_t *, - loff_t *); -int proc_dointvec_ms_jiffies(const struct ctl_table *, int, void *, size_t *, - loff_t *); int proc_doulongvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int, void *, size_t *, loff_t *); diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 5a79622ad1cd..bcbf69c10426 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -356,36 +356,14 @@ static void proc_put_char(void **buf, size_t *size, char c) } #define SYSCTL_CONV_IDENTITY(val) val -#define SYSCTL_CONV_MULT_HZ(val) ((val) * HZ) -#define SYSCTL_CONV_DIV_HZ(val) ((val) / HZ) static SYSCTL_USER_TO_KERN_INT_CONV(, SYSCTL_CONV_IDENTITY) static SYSCTL_KERN_TO_USER_INT_CONV(, SYSCTL_CONV_IDENTITY) -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(, sysctl_user_to_kern_int_conv, sysctl_kern_to_user_int_conv, false) -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(_minmax, sysctl_user_to_kern_int_conv, sysctl_kern_to_user_int_conv, true) -static SYSCTL_INT_CONV_CUSTOM(_ms_jiffies_minmax, - sysctl_user_to_kern_int_conv_ms, - sysctl_kern_to_user_int_conv_ms, true) static SYSCTL_USER_TO_KERN_UINT_CONV(, SYSCTL_CONV_IDENTITY) @@ -901,81 +879,6 @@ int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer, return do_proc_dointvec(table, dir, buffer, lenp, ppos, conv); } -/** - * proc_dointvec_jiffies - read a vector of integers as seconds - * @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 - * - * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. - * The values read are assumed to be in seconds, and are converted into - * jiffies. - * - * Returns 0 on success. - */ -int proc_dointvec_jiffies(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return proc_dointvec_conv(table, dir, buffer, lenp, ppos, - do_proc_int_conv_jiffies); -} - -int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return proc_dointvec_conv(table, dir, buffer, lenp, ppos, - do_proc_int_conv_ms_jiffies_minmax); -} - -/** - * proc_dointvec_userhz_jiffies - read a vector of integers as 1/USER_HZ seconds - * @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: pointer to the file position - * - * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. - * The values read are assumed to be in 1/USER_HZ seconds, and - * are converted into jiffies. - * - * Returns 0 on success. - */ -int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - if (SYSCTL_USER_TO_KERN(dir) && USER_HZ < HZ) - return -EINVAL; - return proc_dointvec_conv(table, dir, buffer, lenp, ppos, - do_proc_int_conv_userhz_jiffies); -} - -/** - * proc_dointvec_ms_jiffies - read a vector of integers as 1 milliseconds - * @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: the current position in the file - * - * Reads/writes up to table->maxlen/sizeof(unsigned int) integer - * values from/to the user buffer, treated as an ASCII string. - * The values read are assumed to be in 1/1000 seconds, and - * are converted into jiffies. - * - * Returns 0 on success. - */ -int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, - size_t *lenp, loff_t *ppos) -{ - return proc_dointvec_conv(table, dir, buffer, lenp, ppos, - do_proc_int_conv_ms_jiffies); -} - /** * proc_do_large_bitmap - read/write from/to a large bitmap * @table: the sysctl table @@ -1167,30 +1070,6 @@ int proc_dou8vec_minmax(const struct ctl_table *table, int dir, return -ENOSYS; } -int proc_dointvec_jiffies(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return -ENOSYS; -} - -int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return -ENOSYS; -} - -int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return -ENOSYS; -} - -int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return -ENOSYS; -} - int proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { @@ -1310,11 +1189,8 @@ int __init sysctl_init_bases(void) EXPORT_SYMBOL(proc_dobool); EXPORT_SYMBOL(proc_dointvec); EXPORT_SYMBOL(proc_douintvec); -EXPORT_SYMBOL(proc_dointvec_jiffies); EXPORT_SYMBOL(proc_dointvec_minmax); EXPORT_SYMBOL_GPL(proc_douintvec_minmax); -EXPORT_SYMBOL(proc_dointvec_userhz_jiffies); -EXPORT_SYMBOL(proc_dointvec_ms_jiffies); EXPORT_SYMBOL(proc_dostring); EXPORT_SYMBOL(proc_doulongvec_minmax); EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 34eeacac2253..8e845a68382c 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -99,3 +99,103 @@ 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) + +/** + * proc_dointvec_jiffies - read a vector of integers as seconds + * @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 + * + * Reads/writes up to table->maxlen/sizeof(unsigned int) integer + * values from/to the user buffer, treated as an ASCII string. + * The values read are assumed to be in seconds, and are converted into + * jiffies. + * + * Returns 0 on success. + */ +int proc_dointvec_jiffies(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos) +{ + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_jiffies); +} +EXPORT_SYMBOL(proc_dointvec_jiffies); + +/** + * proc_dointvec_userhz_jiffies - read a vector of integers as 1/USER_HZ seconds + * @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: pointer to the file position + * + * Reads/writes up to table->maxlen/sizeof(unsigned int) integer + * values from/to the user buffer, treated as an ASCII string. + * The values read are assumed to be in 1/USER_HZ seconds, and + * are converted into jiffies. + * + * Returns 0 on success. + */ +int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos) +{ + if (SYSCTL_USER_TO_KERN(dir) && USER_HZ < HZ) + return -EINVAL; + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_userhz_jiffies); +} +EXPORT_SYMBOL(proc_dointvec_userhz_jiffies); + +/** + * proc_dointvec_ms_jiffies - read a vector of integers as 1 milliseconds + * @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: the current position in the file + * + * Reads/writes up to table->maxlen/sizeof(unsigned int) integer + * values from/to the user buffer, treated as an ASCII string. + * The values read are assumed to be in 1/1000 seconds, and + * are converted into jiffies. + * + * Returns 0 on success. + */ +int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, + size_t *lenp, loff_t *ppos) +{ + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_ms_jiffies); +} + +int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos) +{ + return proc_dointvec_conv(table, dir, buffer, lenp, ppos, + do_proc_int_conv_ms_jiffies_minmax); +} +EXPORT_SYMBOL(proc_dointvec_ms_jiffies); + From 4639faaa607f3bed85f2cdde686a88453c99ef06 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Tue, 14 Oct 2025 13:35:42 +0200 Subject: [PATCH 19/21] sysctl: Move proc_doulongvec_ms_jiffies_minmax to kernel/time/jiffies.c Move proc_doulongvec_ms_jiffies_minmax to kernel/time/jiffies.c. Create a non static wrapper function proc_doulongvec_minmax_conv that forwards the custom convmul and convdiv argument values to the internal do_proc_doulongvec_minmax. Remove unused linux/times.h include from kernel/sysctl.c. Signed-off-by: Joel Granados --- include/linux/jiffies.h | 2 ++ include/linux/sysctl.h | 5 +++-- kernel/sysctl.c | 41 ++++++++++++----------------------------- kernel/time/jiffies.c | 27 ++++++++++++++++++++++++++- 4 files changed, 43 insertions(+), 32 deletions(-) diff --git a/include/linux/jiffies.h b/include/linux/jiffies.h index 72d589a8a0d6..fdef2c155c27 100644 --- a/include/linux/jiffies.h +++ b/include/linux/jiffies.h @@ -620,5 +620,7 @@ int proc_dointvec_userhz_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos); int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos); +int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos); #endif diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index db4020f6933b..30f6a184d3f4 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -193,8 +193,9 @@ int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer int proc_dou8vec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); int proc_doulongvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int, void *, - size_t *, loff_t *); +int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos, + unsigned long convmul, unsigned long convdiv); int proc_do_large_bitmap(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_do_static_key(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); diff --git a/kernel/sysctl.c b/kernel/sysctl.c index bcbf69c10426..998400323ae9 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -825,6 +824,14 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int dir, return err; } +int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos, + unsigned long convmul, unsigned long convdiv) +{ + return do_proc_doulongvec_minmax(table, dir, buffer, lenp, ppos, + convmul, convdiv); +} + /** * proc_doulongvec_minmax - read a vector of long integers with min/max values * @table: the sysctl table @@ -844,31 +851,7 @@ static int do_proc_doulongvec_minmax(const struct ctl_table *table, int dir, int proc_doulongvec_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_doulongvec_minmax(table, dir, buffer, lenp, ppos, 1l, 1l); -} - -/** - * proc_doulongvec_ms_jiffies_minmax - read a vector of millisecond values with min/max values - * @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 - * - * Reads/writes up to table->maxlen/sizeof(unsigned long) unsigned long - * values from/to the user buffer, treated as an ASCII string. The values - * are treated as milliseconds, and converted to jiffies when they are stored. - * - * This routine will ensure the values are within the range specified by - * table->extra1 (min) and table->extra2 (max). - * - * Returns 0 on success. - */ -int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) -{ - return do_proc_doulongvec_minmax(table, dir, buffer, - lenp, ppos, HZ, 1000l); + return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, 1l, 1l); } int proc_dointvec_conv(const struct ctl_table *table, int dir, void *buffer, @@ -1076,8 +1059,9 @@ int proc_doulongvec_minmax(const struct ctl_table *table, int dir, return -ENOSYS; } -int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, - void *buffer, size_t *lenp, loff_t *ppos) +int proc_doulongvec_minmax_conv(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos, + unsigned long convmul, unsigned long convdiv) { return -ENOSYS; } @@ -1193,5 +1177,4 @@ EXPORT_SYMBOL(proc_dointvec_minmax); EXPORT_SYMBOL_GPL(proc_douintvec_minmax); EXPORT_SYMBOL(proc_dostring); EXPORT_SYMBOL(proc_doulongvec_minmax); -EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); EXPORT_SYMBOL(proc_do_large_bitmap); diff --git a/kernel/time/jiffies.c b/kernel/time/jiffies.c index 8e845a68382c..d31a6d40d38d 100644 --- a/kernel/time/jiffies.c +++ b/kernel/time/jiffies.c @@ -190,6 +190,7 @@ int proc_dointvec_ms_jiffies(const struct ctl_table *table, int dir, void *buffe return proc_dointvec_conv(table, dir, buffer, lenp, ppos, do_proc_int_conv_ms_jiffies); } +EXPORT_SYMBOL(proc_dointvec_ms_jiffies); int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) @@ -197,5 +198,29 @@ int proc_dointvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, return proc_dointvec_conv(table, dir, buffer, lenp, ppos, do_proc_int_conv_ms_jiffies_minmax); } -EXPORT_SYMBOL(proc_dointvec_ms_jiffies); + +/** + * proc_doulongvec_ms_jiffies_minmax - read a vector of millisecond values with min/max values + * @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 + * + * Reads/writes up to table->maxlen/sizeof(unsigned long) unsigned long + * values from/to the user buffer, treated as an ASCII string. The values + * are treated as milliseconds, and converted to jiffies when they are stored. + * + * This routine will ensure the values are within the range specified by + * table->extra1 (min) and table->extra2 (max). + * + * Returns 0 on success. + */ +int proc_doulongvec_ms_jiffies_minmax(const struct ctl_table *table, int dir, + void *buffer, size_t *lenp, loff_t *ppos) +{ + return proc_doulongvec_minmax_conv(table, dir, buffer, lenp, ppos, + HZ, 1000l); +} +EXPORT_SYMBOL(proc_doulongvec_ms_jiffies_minmax); From 30baaeb685bce0b7dfd3c5a55f22b1076c21f7b2 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Tue, 14 Oct 2025 14:21:03 +0200 Subject: [PATCH 20/21] sysctl: Create pipe-max-size converter using sysctl UINT macros Create a converter for the pipe-max-size proc_handler using the SYSCTL_UINT_CONV_CUSTOM. Move SYSCTL_CONV_IDENTITY macro to the sysctl header to make it available for pipe size validation. Keep returning -EINVAL when (val == 0) by using a range checking converter and setting the minimal valid value (extern1) to SYSCTL_ONE. Keep round_pipe_size by passing it as the operation for SYSCTL_USER_TO_KERN_INT_CONV. Signed-off-by: Joel Granados --- fs/pipe.c | 26 ++++++-------------------- include/linux/sysctl.h | 1 + kernel/sysctl.c | 2 -- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/fs/pipe.c b/fs/pipe.c index 9411d4fc2f43..f1b3d1154ad2 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -1481,31 +1481,16 @@ static struct file_system_type pipe_fs_type = { }; #ifdef CONFIG_SYSCTL -static int do_proc_dopipe_max_size_conv(unsigned long *lvalp, - unsigned int *valp, int write, - const struct ctl_table *table) -{ - if (write) { - unsigned int val; - - val = round_pipe_size(*lvalp); - if (val == 0) - return -EINVAL; - - *valp = val; - } else { - unsigned int val = *valp; - *lvalp = (unsigned long) val; - } - - return 0; -} +static SYSCTL_USER_TO_KERN_UINT_CONV(_pipe_maxsz, round_pipe_size) +static SYSCTL_UINT_CONV_CUSTOM(_pipe_maxsz, + sysctl_user_to_kern_uint_conv_pipe_maxsz, + sysctl_kern_to_user_uint_conv, true) static int proc_dopipe_max_size(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { return do_proc_douintvec(table, write, buffer, lenp, ppos, - do_proc_dopipe_max_size_conv); + do_proc_uint_conv_pipe_maxsz); } static const struct ctl_table fs_pipe_sysctls[] = { @@ -1515,6 +1500,7 @@ static const struct ctl_table fs_pipe_sysctls[] = { .maxlen = sizeof(pipe_max_size), .mode = 0644, .proc_handler = proc_dopipe_max_size, + .extra1 = SYSCTL_ONE, }, { .procname = "pipe-user-pages-hard", diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 30f6a184d3f4..4c88514a7d1a 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -59,6 +59,7 @@ extern const int sysctl_vals[]; #define SYSCTL_LONG_ONE ((void *)&sysctl_long_vals[1]) #define SYSCTL_LONG_MAX ((void *)&sysctl_long_vals[2]) +#define SYSCTL_CONV_IDENTITY(val) (val) /** * * "dir" originates from read_iter (dir = 0) or write_iter (dir = 1) diff --git a/kernel/sysctl.c b/kernel/sysctl.c index 998400323ae9..d09c6602a115 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -354,8 +354,6 @@ static void proc_put_char(void **buf, size_t *size, char c) } } -#define SYSCTL_CONV_IDENTITY(val) val - static SYSCTL_USER_TO_KERN_INT_CONV(, SYSCTL_CONV_IDENTITY) static SYSCTL_KERN_TO_USER_INT_CONV(, SYSCTL_CONV_IDENTITY) From 564195c1a33c8fc631cd3d306e350b0e3d3e9555 Mon Sep 17 00:00:00 2001 From: Joel Granados Date: Thu, 16 Oct 2025 11:04:23 +0200 Subject: [PATCH 21/21] sysctl: Wrap do_proc_douintvec with the public function proc_douintvec_conv Make do_proc_douintvec static and export proc_douintvec_conv wrapper function for external use. This is to keep with the design in sysctl.c. Update fs/pipe.c to use the new public API. Signed-off-by: Joel Granados --- fs/pipe.c | 4 ++-- include/linux/sysctl.h | 13 +++++++------ kernel/sysctl.c | 18 ++++++++++++++---- 3 files changed, 23 insertions(+), 12 deletions(-) diff --git a/fs/pipe.c b/fs/pipe.c index f1b3d1154ad2..0acca73617e9 100644 --- a/fs/pipe.c +++ b/fs/pipe.c @@ -1489,8 +1489,8 @@ static SYSCTL_UINT_CONV_CUSTOM(_pipe_maxsz, static int proc_dopipe_max_size(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos) { - return do_proc_douintvec(table, write, buffer, lenp, ppos, - do_proc_uint_conv_pipe_maxsz); + return proc_douintvec_conv(table, write, buffer, lenp, ppos, + do_proc_uint_conv_pipe_maxsz); } static const struct ctl_table fs_pipe_sysctls[] = { diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 4c88514a7d1a..288fe0055cd5 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -183,14 +183,20 @@ int proc_dostring(const struct ctl_table *, int, void *, size_t *, loff_t *); 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_minmax(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, int dir, const struct ctl_table *table)); int proc_douintvec(const struct ctl_table *, int, void *, size_t *, loff_t *); -int proc_dointvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); int proc_douintvec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); +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)); + int proc_dou8vec_minmax(const struct ctl_table *table, int write, void *buffer, size_t *lenp, loff_t *ppos); int proc_doulongvec_minmax(const struct ctl_table *, int, void *, size_t *, loff_t *); @@ -346,11 +352,6 @@ extern struct ctl_table_header *register_sysctl_mount_point(const char *path); void do_sysctl_args(void); bool sysctl_is_alias(char *param); -int do_proc_douintvec(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)); extern int unaligned_enabled; extern int no_unaligned_warning; diff --git a/kernel/sysctl.c b/kernel/sysctl.c index d09c6602a115..2cd767b9680e 100644 --- a/kernel/sysctl.c +++ b/kernel/sysctl.c @@ -535,10 +535,11 @@ static int do_proc_douintvec_r(const struct ctl_table *table, void *buffer, return err; } -int do_proc_douintvec(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, - int dir, const struct ctl_table *table)) +static int do_proc_douintvec(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, int dir, + const struct ctl_table *table)) { unsigned int vleft; @@ -567,6 +568,15 @@ int do_proc_douintvec(const struct ctl_table *table, int dir, void *buffer, return do_proc_douintvec_r(table, buffer, lenp, ppos, conv); } +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, + int dir, const struct ctl_table *table)) +{ + return do_proc_douintvec(table, dir, buffer, lenp, ppos, conv); +} + + /** * proc_dobool - read/write a bool * @table: the sysctl table