1438 lines · 35.4 KB · C
RawDownload
// 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.