1438 lines · 35.4 KB · C
RawDownload
int proc_int_u2k_conv_uop(const ulong *u_ptr, int *k_ptr, const bool *negp,			  ulong (*u_ptr_op)(const ulong)){	ulong u = u_ptr_op ? u_ptr_op(*u_ptr) : *u_ptr; 	if (*negp) {		if (u > (ulong) INT_MAX + 1)			return -EINVAL;		WRITE_ONCE(*k_ptr, -u);	} else {		if (u > (ulong) INT_MAX)			return -EINVAL;		WRITE_ONCE(*k_ptr, u);	}	return 0;} int proc_int_conv(bool *negp, ulong *u_ptr, int *k_ptr, int dir,		  const struct ctl_table *tbl, bool k_ptr_range_check,		  int (*user_to_kern)(const bool *negp, const ulong *u_ptr, int *k_ptr),		  int (*kern_to_user)(bool *negp, ulong *u_ptr, const int *k_ptr)){	if (SYSCTL_KERN_TO_USER(dir))		return kern_to_user(negp, u_ptr, k_ptr); 	if (k_ptr_range_check) {		int tmp_k, ret; 		if (!tbl)			return -EINVAL;		ret = user_to_kern(negp, u_ptr, &tmp_k);		if (ret)			return ret;		if ((tbl->extra1 && *(int *)tbl->extra1 > tmp_k) ||		    (tbl->extra2 && *(int *)tbl->extra2 < tmp_k))			return -EINVAL;		WRITE_ONCE(*k_ptr, tmp_k);	} else		return user_to_kern(negp, u_ptr, k_ptr);	return 0;}   static int sysctl_user_to_kern_int_conv(const bool *negp, const ulong *u_ptr,					int *k_ptr){	return proc_int_u2k_conv_uop(u_ptr, k_ptr, negp, NULL);} static int sysctl_kern_to_user_int_conv(bool *negp, ulong *u_ptr, const int *k_ptr){	return proc_int_k2u_conv_kop(u_ptr, k_ptr, negp, NULL);} static int do_proc_int_conv(bool *negp, unsigned long *u_ptr, int *k_ptr,			    int dir, const struct ctl_table *tbl){	return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, false,			     sysctl_user_to_kern_int_conv,			     sysctl_kern_to_user_int_conv); } static int do_proc_int_conv_minmax(bool *negp, unsigned long *u_ptr, int *k_ptr,				   int dir, const struct ctl_table *tbl){	return proc_int_conv(negp, u_ptr, k_ptr, dir, tbl, true,			     sysctl_user_to_kern_int_conv,			     sysctl_kern_to_user_int_conv);} static const char proc_wspace_sep[] = { ' ', '\t', '\n' }; 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 *u_ptr, int *k_ptr,			      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 && SYSCTL_KERN_TO_USER(dir))) {		*lenp = 0;		return 0;	} 	i = (int *) table->data;	vleft = table->maxlen / sizeof(*i);	left = *lenp; 	if (!conv)		conv = do_proc_int_conv; 	if (SYSCTL_USER_TO_KERN(dir)) {		if (proc_first_pos_non_zero_ignore(ppos, table))			goto out; 		if (left > PAGE_SIZE - 1)			left = PAGE_SIZE - 1;		p = buffer;	} 	for (; left && vleft--; i++, first=0) {		unsigned long lval;		bool neg; 		if (SYSCTL_USER_TO_KERN(dir)) {			proc_skip_spaces(&p, &left); 			if (!left)				break;			err = proc_get_long(&p, &left, &lval, &neg,					     proc_wspace_sep,					     sizeof(proc_wspace_sep), NULL);			if (err)				break;			if (conv(&neg, &lval, i, 1, table)) {				err = -EINVAL;				break;			}		} else {			if (conv(&neg, &lval, i, 0, table)) {				err = -EINVAL;				break;			}			if (!first)				proc_put_char(&buffer, &left, '\t');			proc_put_long(&buffer, &left, lval, neg);		}	} 	if (SYSCTL_KERN_TO_USER(dir) && !first && left && !err)		proc_put_char(&buffer, &left, '\n');	if (SYSCTL_USER_TO_KERN(dir) && !err && left)		proc_skip_spaces(&p, &left);	if (SYSCTL_USER_TO_KERN(dir) && first)		return err ? : -EINVAL;	*lenp -= left;out:	*ppos += *lenp;	return err;} static int do_proc_douintvec_w(const struct ctl_table *table, 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 long lval;	int err = 0;	size_t left;	bool neg;	char *p = buffer; 	left = *lenp; 	if (proc_first_pos_non_zero_ignore(ppos, table))		goto bail_early; 	if (left > PAGE_SIZE - 1)		left = PAGE_SIZE - 1; 	proc_skip_spaces(&p, &left);	if (!left) {		err = -EINVAL;		goto out_free;	} 	err = proc_get_long(&p, &left, &lval, &neg,			     proc_wspace_sep,			     sizeof(proc_wspace_sep), NULL);	if (err || neg) {		err = -EINVAL;		goto out_free;	} 	if (conv(&lval, (unsigned int *) table->data, 1, table)) {		err = -EINVAL;		goto out_free;	} 	if (!err && left)		proc_skip_spaces(&p, &left); out_free:	if (err)		return -EINVAL; 	return 0; bail_early:	*ppos += *lenp;	return err;} static int do_proc_douintvec_r(const struct ctl_table *table, 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 long lval;	int err = 0;	size_t left; 	left = *lenp; 	if (conv(&lval, (unsigned int *) table->data, 0, table)) {		err = -EINVAL;		goto out;	} 	proc_put_long(&buffer, &left, lval, false);	if (!left)		goto out; 	proc_put_char(&buffer, &left, '\n'); out:	*lenp -= left;	*ppos += *lenp; 	return err;} 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; 	if (!table->data || !table->maxlen || !*lenp ||	    (*ppos && SYSCTL_KERN_TO_USER(dir))) {		*lenp = 0;		return 0;	} 	vleft = table->maxlen / sizeof(unsigned int); 	/*	 * Arrays are not supported, keep this simple. *Do not* add	 * support for them.	 */	if (vleft != 1) {		*lenp = 0;		return -EINVAL;	} 	if (!conv)		conv = do_proc_uint_conv; 	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);} /** * proc_douintvec_conv - read a vector of unsigned ints with a custom converter * * @table: the sysctl table * @dir: %TRUE if this is a write to the sysctl file * @buffer: the user buffer * @lenp: the size of the user buffer * @ppos: file position * @conv: Custom converter call back * * Reads/writes up to table->maxlen/sizeof(unsigned int) unsigned integer * values from/to the user buffer, treated as an ASCII string. Negative * strings are not allowed. * * Returns 0 on success */int proc_douintvec_conv(const struct ctl_table *table, int dir, void *buffer,			size_t *lenp, loff_t *ppos,			int (*conv)(unsigned long *u_ptr, unsigned int *k_ptr,				    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 * @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 one integer value from/to the user buffer, * treated as an ASCII string. * * table->data must point to a bool variable and table->maxlen must * be sizeof(bool). * * Returns 0 on success. */int proc_dobool(const struct ctl_table *table, int dir, void *buffer,		size_t *lenp, loff_t *ppos){	struct ctl_table tmp;	bool *data = table->data;	int res, val; 	/* Do not support arrays yet. */	if (table->maxlen != sizeof(bool))		return -EINVAL; 	tmp = *table;	tmp.maxlen = sizeof(val);	tmp.data = &val; 	val = READ_ONCE(*data);	res = proc_dointvec(&tmp, dir, buffer, lenp, ppos);	if (res)		return res;	if (SYSCTL_USER_TO_KERN(dir))		WRITE_ONCE(*data, val);	return 0;} /** * proc_dointvec - read a vector of integers * @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. * * Returns 0 on success. */int proc_dointvec(const struct ctl_table *table, int dir, void *buffer,		  size_t *lenp, loff_t *ppos){	return do_proc_dointvec(table, dir, buffer, lenp, ppos, NULL);} /** * proc_douintvec - read a vector of unsigned integers * @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) unsigned integer * values from/to the user buffer, treated as an ASCII string. * * Returns 0 on success. */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_uint_conv);} /** * proc_dointvec_minmax - read a vector of integers 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 int) integer * values from/to the user buffer, treated as an ASCII string. * * 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 when the range check fails and * SYSCTL_USER_TO_KERN(dir) == true */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_int_conv_minmax);} /** * proc_douintvec_minmax - read a vector of unsigned ints 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 int) unsigned integer * values from/to the user buffer, treated as an ASCII string. Negative * strings are not allowed. * * When changing the kernel variable, this routine will ensure the values * are within the range specified by table->extra1 (min) and table->extra2 * (max). And Check that the values are less than UINT_MAX to avoid having to * support wrap around uses from userspace. * * Returns 0 on success or -ERANGE when range check failes and * SYSCTL_USER_TO_KERN(dir) == true */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_uint_conv_minmax);} /** * proc_dou8vec_minmax - read a vector of unsigned chars 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(u8) unsigned chars * values from/to the user buffer, treated as an ASCII string. Negative * strings are not allowed. * * This routine will ensure the values are within the range specified by * table->extra1 (min) and table->extra2 (max). * * 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 dir,			void *buffer, size_t *lenp, loff_t *ppos){	struct ctl_table tmp;	unsigned int min = 0, max = 255U, val;	u8 *data = table->data;	int res; 	/* Do not support arrays yet. */	if (table->maxlen != sizeof(u8))		return -EINVAL; 	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, dir, buffer, lenp, ppos,				do_proc_uint_conv_minmax);	if (res)		return res;	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 dir,				     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;	size_t left;	char *p; 	if (!table->data || !table->maxlen || !*lenp ||	    (*ppos && SYSCTL_KERN_TO_USER(dir))) {		*lenp = 0;		return 0;	} 	i = table->data;	min = table->extra1;	max = table->extra2;	vleft = table->maxlen / sizeof(unsigned long);	left = *lenp; 	if (SYSCTL_USER_TO_KERN(dir)) {		if (proc_first_pos_non_zero_ignore(ppos, table))			goto out; 		if (left > PAGE_SIZE - 1)			left = PAGE_SIZE - 1;		p = buffer;	} 	for (; left && vleft--; i++, first = 0) {		unsigned long val; 

Lines 501–1000 of 1438. Previous lines