// SPDX-License-Identifier: GPL-2.0-only/* * sysctl.c: General linux system control interface */ #include <linux/sysctl.h>#include <linux/bitmap.h>#include <linux/proc_fs.h>#include <linux/ctype.h>#include <linux/init.h>#include <linux/kernel.h>#include <linux/kobject.h>#include <linux/highuid.h>#include <linux/writeback.h>#include <linux/initrd.h>#include <linux/limits.h>#include <linux/syscalls.h>#include <linux/capability.h> #include "../lib/kstrtox.h" #include <linux/uaccess.h>#include <asm/processor.h> /* shared constants to be used in various sysctls */const int sysctl_vals[] = { 0, 1, 2, 3, 4, 100, 200, 1000, 3000, INT_MAX, 65535, -1 };EXPORT_SYMBOL(sysctl_vals); const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX };EXPORT_SYMBOL_GPL(sysctl_long_vals); #if defined(CONFIG_SYSCTL) /* Constants used for minimum and maximum */static const int ngroups_max = NGROUPS_MAX;static const int cap_last_cap = CAP_LAST_CAP; #ifdef CONFIG_PROC_SYSCTL /** * enum sysctl_writes_mode - supported sysctl write modes * * @SYSCTL_WRITES_LEGACY: each write syscall must fully contain the sysctl value * to be written, and multiple writes on the same sysctl file descriptor * will rewrite the sysctl value, regardless of file position. No warning * is issued when the initial position is not 0. * @SYSCTL_WRITES_WARN: same as above but warn when the initial file position is * not 0. * @SYSCTL_WRITES_STRICT: writes to numeric sysctl entries must always be at * file position 0 and the value must be fully contained in the buffer * sent to the write syscall. If dealing with strings respect the file * position, but restrict this to the max length of the buffer, anything * passed the max length will be ignored. Multiple writes will append * to the buffer. * * These write modes control how current file position affects the behavior of * updating internal kernel (SYSCTL_USER_TO_KERN) sysctl values through the proc * interface on each write. */enum sysctl_writes_mode { SYSCTL_WRITES_LEGACY = -1, SYSCTL_WRITES_WARN = 0, SYSCTL_WRITES_STRICT = 1,}; static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT;#endif /* CONFIG_PROC_SYSCTL */#endif /* CONFIG_SYSCTL */ /* * /proc/sys support */ #ifdef CONFIG_PROC_SYSCTL static int _proc_do_string(char *data, int maxlen, int dir, char *buffer, size_t *lenp, loff_t *ppos){ size_t len; char c, *p; if (!data || !maxlen || !*lenp) { *lenp = 0; return 0; } 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); if (len > maxlen - 1) len = maxlen - 1; if (*ppos > len) return 0; len = *ppos; } else { /* Start writing from beginning of buffer. */ len = 0; } *ppos += *lenp; p = buffer; while ((p - buffer) < *lenp && len < maxlen - 1) { c = *(p++); if (c == 0 || c == '\n') break; data[len++] = c; } data[len] = 0; } else { len = strlen(data); if (len > maxlen) len = maxlen; if (*ppos > len) { *lenp = 0; return 0; } data += *ppos; len -= *ppos; if (len > *lenp) len = *lenp; if (len) memcpy(buffer, data, len); if (len < *lenp) { buffer[len] = '\n'; len++; } *lenp = len; *ppos += len; } return 0;} static void warn_sysctl_write(const struct ctl_table *table){ pr_warn_once("%s wrote to %s when file position was not 0!\n" "This will not be supported in the future. To silence this\n" "warning, set kernel.sysctl_writes_strict = -1\n", current->comm, table->procname);} /** * proc_first_pos_non_zero_ignore - check if first position is allowed * @ppos: file position * @table: the sysctl table * * Returns true if the first position is non-zero and the sysctl_writes_strict * mode indicates this is not allowed for numeric input types. String proc * handlers can ignore the return value. */static bool proc_first_pos_non_zero_ignore(loff_t *ppos, const struct ctl_table *table){ if (!*ppos) return false; switch (sysctl_writes_strict) { case SYSCTL_WRITES_STRICT: return true; case SYSCTL_WRITES_WARN: warn_sysctl_write(table); return false; default: return false; }} /** * proc_dostring - read a string sysctl * @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 a string from/to the user buffer. If the kernel * buffer provided is not large enough to hold the string, the * string is truncated. The copied string is %NULL-terminated. * If the string is being read by the user process, it is copied * and a newline '\n' is added. It is truncated if the buffer is * not large enough. * * Returns 0 on success. */int proc_dostring(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos){ if (SYSCTL_USER_TO_KERN(dir)) proc_first_pos_non_zero_ignore(ppos, table); return _proc_do_string(table->data, table->maxlen, dir, buffer, lenp, ppos);} static void proc_skip_spaces(char **buf, size_t *size){ while (*size) { if (!isspace(**buf)) break; (*size)--; (*buf)++; }} static void proc_skip_char(char **buf, size_t *size, const char v){ while (*size) { if (**buf != v) break; (*size)--; (*buf)++; }} /** * strtoul_lenient - parse an ASCII formatted integer from a buffer and only * fail on overflow * * @cp: kernel buffer containing the string to parse * @endp: pointer to store the trailing characters * @base: the base to use * @res: where the parsed integer will be stored * * In case of success 0 is returned and @res will contain the parsed integer, * @endp will hold any trailing characters. * This function will fail the parse on overflow. If there wasn't an overflow * the function will defer the decision what characters count as invalid to the * caller. */static int strtoul_lenient(const char *cp, char **endp, unsigned int base, unsigned long *res){ unsigned long long result; unsigned int rv; cp = _parse_integer_fixup_radix(cp, &base); rv = _parse_integer(cp, base, &result); if ((rv & KSTRTOX_OVERFLOW) || (result != (unsigned long)result)) return -ERANGE; cp += rv; if (endp) *endp = (char *)cp; *res = (unsigned long)result; return 0;} #define TMPBUFLEN 22/** * proc_get_long - reads an ASCII formatted integer from a user buffer * * @buf: a kernel buffer * @size: size of the kernel buffer * @val: this is where the number will be stored * @neg: set to %TRUE if number is negative * @perm_tr: a vector which contains the allowed trailers * @perm_tr_len: size of the perm_tr vector * @tr: pointer to store the trailer character * * In case of success %0 is returned and @buf and @size are updated with * the amount of bytes read. If @tr is non-NULL and a trailing * character exists (size is non-zero after returning from this * function), @tr is updated with the trailing character. */static int proc_get_long(char **buf, size_t *size, unsigned long *val, bool *neg, const char *perm_tr, unsigned perm_tr_len, char *tr){ char *p, tmp[TMPBUFLEN]; ssize_t len = *size; if (len <= 0) return -EINVAL; if (len > TMPBUFLEN - 1) len = TMPBUFLEN - 1; memcpy(tmp, *buf, len); tmp[len] = 0; p = tmp; if (*p == '-' && *size > 1) { *neg = true; p++; } else *neg = false; if (!isdigit(*p)) return -EINVAL; if (strtoul_lenient(p, &p, 0, val)) return -EINVAL; len = p - tmp; /* We don't know if the next char is whitespace thus we may accept * invalid integers (e.g. 1234...a) or two integers instead of one * (e.g. 123...1). So lets not allow such large numbers. */ if (len == TMPBUFLEN - 1) return -EINVAL; if (len < *size && perm_tr_len && !memchr(perm_tr, *p, perm_tr_len)) return -EINVAL; if (tr && (len < *size)) *tr = *p; *buf += len; *size -= len; return 0;} /** * proc_put_long - converts an integer to a decimal ASCII formatted string * * @buf: the user buffer * @size: the size of the user buffer * @val: the integer to be converted * @neg: sign of the number, %TRUE for negative * * In case of success @buf and @size are updated with the amount of bytes * written. */static void proc_put_long(void **buf, size_t *size, unsigned long val, bool neg){ int len; char tmp[TMPBUFLEN], *p = tmp; sprintf(p, "%s%lu", neg ? "-" : "", val); len = strlen(tmp); if (len > *size) len = *size; memcpy(*buf, tmp, len); *size -= len; *buf += len;}#undef TMPBUFLEN static void proc_put_char(void **buf, size_t *size, char c){ if (*size) { char **buffer = (char **)buf; **buffer = c; (*size)--; (*buffer)++; *buf = *buffer; }} /** * proc_uint_u2k_conv_uop - Assign user value to a kernel pointer * * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * @u_ptr_op: execute this function before assigning to k_ptr * * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if * not NULL. Check that the values are less than UINT_MAX to avoid * having to support wrap around from userspace. * * returns 0 on success. */int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr, ulong (*u_ptr_op)(const ulong)){ ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr; if (u > UINT_MAX) return -EINVAL; WRITE_ONCE(*k_ptr, u); return 0;} /** * proc_uint_k2u_conv - Assign kernel value to a user space pointer * * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * * Uses READ_ONCE to assign value to u_ptr. * * returns 0 on success. */int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr){ uint val = READ_ONCE(*k_ptr); *u_ptr = (ulong)val; return 0;} /** * proc_uint_conv - Change user or kernel pointer based on direction * * @u_ptr: pointer to user variable * @k_ptr: pointer to kernel variable * @dir: %TRUE if this is a write to the sysctl file * @tbl: the sysctl table * @k_ptr_range_check: Check range for k_ptr when %TRUE * @user_to_kern: Callback used to assign value from user to kernel var * @kern_to_user: Callback used to assign value from kernel to user var * * When direction is kernel to user, then the u_ptr is modified. * When direction is user to kernel, then the k_ptr is modified. * * Returns 0 on success */int proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl, bool k_ptr_range_check, int (*user_to_kern)(const ulong *u_ptr, uint *k_ptr), int (*kern_to_user)(ulong *u_ptr, const uint *k_ptr)){ if (SYSCTL_KERN_TO_USER(dir)) return kern_to_user(u_ptr, k_ptr); if (k_ptr_range_check) { uint tmp_k; int ret; if (!tbl) return -EINVAL; ret = user_to_kern(u_ptr, &tmp_k); if (ret) return ret; if ((tbl->extra1 && *(uint *)tbl->extra1 > tmp_k) || (tbl->extra2 && *(uint *)tbl->extra2 < tmp_k)) return -ERANGE; WRITE_ONCE(*k_ptr, tmp_k); } else return user_to_kern(u_ptr, k_ptr); return 0;} static int proc_uint_u2k_conv(const ulong *u_ptr, uint *k_ptr){ return proc_uint_u2k_conv_uop(u_ptr, k_ptr, NULL);} static int do_proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl){ return proc_uint_conv(u_ptr, k_ptr, dir, tbl, false, proc_uint_u2k_conv, proc_uint_k2u_conv);} static int do_proc_uint_conv_minmax(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl){ return proc_uint_conv(u_ptr, k_ptr, dir, tbl, true, proc_uint_u2k_conv, proc_uint_k2u_conv);} /** * proc_int_k2u_conv_kop - Assign kernel value to a user space pointer * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * @negp: assigned %TRUE if the converted kernel value is negative; * %FALSE otherweise * @k_ptr_op: execute this function before assigning to u_ptr * * Uses READ_ONCE to get value from k_ptr. Executes k_ptr_op before assigning * to u_ptr if not NULL. Does **not** check for overflow. * * Returns: 0 on success. */int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp, ulong (*k_ptr_op)(const ulong)){ int val = READ_ONCE(*k_ptr); if (val < 0) { *negp = true; *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val; } else { *negp = false; *u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val; } return 0;} /** * proc_int_u2k_conv_uop - Assign user value to a kernel pointer * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * @negp: If %TRUE, the converted user value is made negative. * @u_ptr_op: execute this function before assigning to k_ptr * * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if * not NULL. Check for overflow with UINT_MAX. * * Returns: 0 on success. */Lines 1–500 of 1438. Load more
// SPDX-License-Identifier: GPL-2.0-only /* * sysctl.c: General linux system control interface */ #include <linux/sysctl.h> #include <linux/bitmap.h> #include <linux/proc_fs.h> #include <linux/ctype.h> #include <linux/init.h> #include <linux/kernel.h> #include <linux/kobject.h> #include <linux/highuid.h> #include <linux/writeback.h> #include <linux/initrd.h> #include <linux/limits.h> #include <linux/syscalls.h> #include <linux/capability.h> #include "../lib/kstrtox.h" #include <linux/uaccess.h> #include <asm/processor.h> /* shared constants to be used in various sysctls */ const int sysctl_vals[] = { 0, 1, 2, 3, 4, 100, 200, 1000, 3000, INT_MAX, 65535, -1 }; EXPORT_SYMBOL(sysctl_vals); const unsigned long sysctl_long_vals[] = { 0, 1, LONG_MAX }; EXPORT_SYMBOL_GPL(sysctl_long_vals); #if defined(CONFIG_SYSCTL) /* Constants used for minimum and maximum */ static const int ngroups_max = NGROUPS_MAX; static const int cap_last_cap = CAP_LAST_CAP; #ifdef CONFIG_PROC_SYSCTL /** * enum sysctl_writes_mode - supported sysctl write modes * * @SYSCTL_WRITES_LEGACY: each write syscall must fully contain the sysctl value * to be written, and multiple writes on the same sysctl file descriptor * will rewrite the sysctl value, regardless of file position. No warning * is issued when the initial position is not 0. * @SYSCTL_WRITES_WARN: same as above but warn when the initial file position is * not 0. * @SYSCTL_WRITES_STRICT: writes to numeric sysctl entries must always be at * file position 0 and the value must be fully contained in the buffer * sent to the write syscall. If dealing with strings respect the file * position, but restrict this to the max length of the buffer, anything * passed the max length will be ignored. Multiple writes will append * to the buffer. * * These write modes control how current file position affects the behavior of * updating internal kernel (SYSCTL_USER_TO_KERN) sysctl values through the proc * interface on each write. */ enum sysctl_writes_mode { SYSCTL_WRITES_LEGACY = -1, SYSCTL_WRITES_WARN = 0, SYSCTL_WRITES_STRICT = 1, }; static enum sysctl_writes_mode sysctl_writes_strict = SYSCTL_WRITES_STRICT; #endif /* CONFIG_PROC_SYSCTL */ #endif /* CONFIG_SYSCTL */ /* * /proc/sys support */ #ifdef CONFIG_PROC_SYSCTL static int _proc_do_string(char *data, int maxlen, int dir, char *buffer, size_t *lenp, loff_t *ppos) { size_t len; char c, *p; if (!data || !maxlen || !*lenp) { *lenp = 0; return 0; } 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); if (len > maxlen - 1) len = maxlen - 1; if (*ppos > len) return 0; len = *ppos; } else { /* Start writing from beginning of buffer. */ len = 0; } *ppos += *lenp; p = buffer; while ((p - buffer) < *lenp && len < maxlen - 1) { c = *(p++); if (c == 0 || c == '\n') break; data[len++] = c; } data[len] = 0; } else { len = strlen(data); if (len > maxlen) len = maxlen; if (*ppos > len) { *lenp = 0; return 0; } data += *ppos; len -= *ppos; if (len > *lenp) len = *lenp; if (len) memcpy(buffer, data, len); if (len < *lenp) { buffer[len] = '\n'; len++; } *lenp = len; *ppos += len; } return 0; } static void warn_sysctl_write(const struct ctl_table *table) { pr_warn_once("%s wrote to %s when file position was not 0!\n" "This will not be supported in the future. To silence this\n" "warning, set kernel.sysctl_writes_strict = -1\n", current->comm, table->procname); } /** * proc_first_pos_non_zero_ignore - check if first position is allowed * @ppos: file position * @table: the sysctl table * * Returns true if the first position is non-zero and the sysctl_writes_strict * mode indicates this is not allowed for numeric input types. String proc * handlers can ignore the return value. */ static bool proc_first_pos_non_zero_ignore(loff_t *ppos, const struct ctl_table *table) { if (!*ppos) return false; switch (sysctl_writes_strict) { case SYSCTL_WRITES_STRICT: return true; case SYSCTL_WRITES_WARN: warn_sysctl_write(table); return false; default: return false; } } /** * proc_dostring - read a string sysctl * @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 a string from/to the user buffer. If the kernel * buffer provided is not large enough to hold the string, the * string is truncated. The copied string is %NULL-terminated. * If the string is being read by the user process, it is copied * and a newline '\n' is added. It is truncated if the buffer is * not large enough. * * Returns 0 on success. */ int proc_dostring(const struct ctl_table *table, int dir, void *buffer, size_t *lenp, loff_t *ppos) { if (SYSCTL_USER_TO_KERN(dir)) proc_first_pos_non_zero_ignore(ppos, table); return _proc_do_string(table->data, table->maxlen, dir, buffer, lenp, ppos); } static void proc_skip_spaces(char **buf, size_t *size) { while (*size) { if (!isspace(**buf)) break; (*size)--; (*buf)++; } } static void proc_skip_char(char **buf, size_t *size, const char v) { while (*size) { if (**buf != v) break; (*size)--; (*buf)++; } } /** * strtoul_lenient - parse an ASCII formatted integer from a buffer and only * fail on overflow * * @cp: kernel buffer containing the string to parse * @endp: pointer to store the trailing characters * @base: the base to use * @res: where the parsed integer will be stored * * In case of success 0 is returned and @res will contain the parsed integer, * @endp will hold any trailing characters. * This function will fail the parse on overflow. If there wasn't an overflow * the function will defer the decision what characters count as invalid to the * caller. */ static int strtoul_lenient(const char *cp, char **endp, unsigned int base, unsigned long *res) { unsigned long long result; unsigned int rv; cp = _parse_integer_fixup_radix(cp, &base); rv = _parse_integer(cp, base, &result); if ((rv & KSTRTOX_OVERFLOW) || (result != (unsigned long)result)) return -ERANGE; cp += rv; if (endp) *endp = (char *)cp; *res = (unsigned long)result; return 0; } #define TMPBUFLEN 22 /** * proc_get_long - reads an ASCII formatted integer from a user buffer * * @buf: a kernel buffer * @size: size of the kernel buffer * @val: this is where the number will be stored * @neg: set to %TRUE if number is negative * @perm_tr: a vector which contains the allowed trailers * @perm_tr_len: size of the perm_tr vector * @tr: pointer to store the trailer character * * In case of success %0 is returned and @buf and @size are updated with * the amount of bytes read. If @tr is non-NULL and a trailing * character exists (size is non-zero after returning from this * function), @tr is updated with the trailing character. */ static int proc_get_long(char **buf, size_t *size, unsigned long *val, bool *neg, const char *perm_tr, unsigned perm_tr_len, char *tr) { char *p, tmp[TMPBUFLEN]; ssize_t len = *size; if (len <= 0) return -EINVAL; if (len > TMPBUFLEN - 1) len = TMPBUFLEN - 1; memcpy(tmp, *buf, len); tmp[len] = 0; p = tmp; if (*p == '-' && *size > 1) { *neg = true; p++; } else *neg = false; if (!isdigit(*p)) return -EINVAL; if (strtoul_lenient(p, &p, 0, val)) return -EINVAL; len = p - tmp; /* We don't know if the next char is whitespace thus we may accept * invalid integers (e.g. 1234...a) or two integers instead of one * (e.g. 123...1). So lets not allow such large numbers. */ if (len == TMPBUFLEN - 1) return -EINVAL; if (len < *size && perm_tr_len && !memchr(perm_tr, *p, perm_tr_len)) return -EINVAL; if (tr && (len < *size)) *tr = *p; *buf += len; *size -= len; return 0; } /** * proc_put_long - converts an integer to a decimal ASCII formatted string * * @buf: the user buffer * @size: the size of the user buffer * @val: the integer to be converted * @neg: sign of the number, %TRUE for negative * * In case of success @buf and @size are updated with the amount of bytes * written. */ static void proc_put_long(void **buf, size_t *size, unsigned long val, bool neg) { int len; char tmp[TMPBUFLEN], *p = tmp; sprintf(p, "%s%lu", neg ? "-" : "", val); len = strlen(tmp); if (len > *size) len = *size; memcpy(*buf, tmp, len); *size -= len; *buf += len; } #undef TMPBUFLEN static void proc_put_char(void **buf, size_t *size, char c) { if (*size) { char **buffer = (char **)buf; **buffer = c; (*size)--; (*buffer)++; *buf = *buffer; } } /** * proc_uint_u2k_conv_uop - Assign user value to a kernel pointer * * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * @u_ptr_op: execute this function before assigning to k_ptr * * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if * not NULL. Check that the values are less than UINT_MAX to avoid * having to support wrap around from userspace. * * returns 0 on success. */ int proc_uint_u2k_conv_uop(const ulong *u_ptr, uint *k_ptr, ulong (*u_ptr_op)(const ulong)) { ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr; if (u > UINT_MAX) return -EINVAL; WRITE_ONCE(*k_ptr, u); return 0; } /** * proc_uint_k2u_conv - Assign kernel value to a user space pointer * * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * * Uses READ_ONCE to assign value to u_ptr. * * returns 0 on success. */ int proc_uint_k2u_conv(ulong *u_ptr, const uint *k_ptr) { uint val = READ_ONCE(*k_ptr); *u_ptr = (ulong)val; return 0; } /** * proc_uint_conv - Change user or kernel pointer based on direction * * @u_ptr: pointer to user variable * @k_ptr: pointer to kernel variable * @dir: %TRUE if this is a write to the sysctl file * @tbl: the sysctl table * @k_ptr_range_check: Check range for k_ptr when %TRUE * @user_to_kern: Callback used to assign value from user to kernel var * @kern_to_user: Callback used to assign value from kernel to user var * * When direction is kernel to user, then the u_ptr is modified. * When direction is user to kernel, then the k_ptr is modified. * * Returns 0 on success */ int proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl, bool k_ptr_range_check, int (*user_to_kern)(const ulong *u_ptr, uint *k_ptr), int (*kern_to_user)(ulong *u_ptr, const uint *k_ptr)) { if (SYSCTL_KERN_TO_USER(dir)) return kern_to_user(u_ptr, k_ptr); if (k_ptr_range_check) { uint tmp_k; int ret; if (!tbl) return -EINVAL; ret = user_to_kern(u_ptr, &tmp_k); if (ret) return ret; if ((tbl->extra1 && *(uint *)tbl->extra1 > tmp_k) || (tbl->extra2 && *(uint *)tbl->extra2 < tmp_k)) return -ERANGE; WRITE_ONCE(*k_ptr, tmp_k); } else return user_to_kern(u_ptr, k_ptr); return 0; } static int proc_uint_u2k_conv(const ulong *u_ptr, uint *k_ptr) { return proc_uint_u2k_conv_uop(u_ptr, k_ptr, NULL); } static int do_proc_uint_conv(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl) { return proc_uint_conv(u_ptr, k_ptr, dir, tbl, false, proc_uint_u2k_conv, proc_uint_k2u_conv); } static int do_proc_uint_conv_minmax(ulong *u_ptr, uint *k_ptr, int dir, const struct ctl_table *tbl) { return proc_uint_conv(u_ptr, k_ptr, dir, tbl, true, proc_uint_u2k_conv, proc_uint_k2u_conv); } /** * proc_int_k2u_conv_kop - Assign kernel value to a user space pointer * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * @negp: assigned %TRUE if the converted kernel value is negative; * %FALSE otherweise * @k_ptr_op: execute this function before assigning to u_ptr * * Uses READ_ONCE to get value from k_ptr. Executes k_ptr_op before assigning * to u_ptr if not NULL. Does **not** check for overflow. * * Returns: 0 on success. */ int proc_int_k2u_conv_kop(ulong *u_ptr, const int *k_ptr, bool *negp, ulong (*k_ptr_op)(const ulong)) { int val = READ_ONCE(*k_ptr); if (val < 0) { *negp = true; *u_ptr = k_ptr_op ? -k_ptr_op((ulong)val) : -(ulong)val; } else { *negp = false; *u_ptr = k_ptr_op ? k_ptr_op((ulong)val) : (ulong) val; } return 0; } /** * proc_int_u2k_conv_uop - Assign user value to a kernel pointer * @u_ptr: pointer to user space variable * @k_ptr: pointer to kernel variable * @negp: If %TRUE, the converted user value is made negative. * @u_ptr_op: execute this function before assigning to k_ptr * * Uses WRITE_ONCE to assign value to k_ptr. Executes u_ptr_op if * not NULL. Check for overflow with UINT_MAX. * * Returns: 0 on success. */