sysctl check complains with a WARN() when proc_doulongvec_minmax() or proc_doulongvec_ms_jiffies_minmax() are used by a vector of longs (with more than one element), with no min or max value specified. This is unexpected, given we had a bug on this min/max handling :) Reported-by: Jiri Slaby <jirislaby@gmail.com> Signed-off-by: Eric Dumazet <eric.dumazet@gmail.com> Cc: "Eric W. Biederman" <ebiederm@xmission.com> Cc: David Miller <davem@davemloft.net> Acked-by: WANG Cong <xiyou.wangcong@gmail.com> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
		
			
				
	
	
		
			166 lines
		
	
	
	
		
			4.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			166 lines
		
	
	
	
		
			4.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
#include <linux/stat.h>
 | 
						|
#include <linux/sysctl.h>
 | 
						|
#include "../fs/xfs/linux-2.6/xfs_sysctl.h"
 | 
						|
#include <linux/sunrpc/debug.h>
 | 
						|
#include <linux/string.h>
 | 
						|
#include <net/ip_vs.h>
 | 
						|
 | 
						|
 | 
						|
static int sysctl_depth(struct ctl_table *table)
 | 
						|
{
 | 
						|
	struct ctl_table *tmp;
 | 
						|
	int depth;
 | 
						|
 | 
						|
	depth = 0;
 | 
						|
	for (tmp = table; tmp->parent; tmp = tmp->parent)
 | 
						|
		depth++;
 | 
						|
 | 
						|
	return depth;
 | 
						|
}
 | 
						|
 | 
						|
static struct ctl_table *sysctl_parent(struct ctl_table *table, int n)
 | 
						|
{
 | 
						|
	int i;
 | 
						|
 | 
						|
	for (i = 0; table && i < n; i++)
 | 
						|
		table = table->parent;
 | 
						|
 | 
						|
	return table;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void sysctl_print_path(struct ctl_table *table)
 | 
						|
{
 | 
						|
	struct ctl_table *tmp;
 | 
						|
	int depth, i;
 | 
						|
	depth = sysctl_depth(table);
 | 
						|
	if (table->procname) {
 | 
						|
		for (i = depth; i >= 0; i--) {
 | 
						|
			tmp = sysctl_parent(table, i);
 | 
						|
			printk("/%s", tmp->procname?tmp->procname:"");
 | 
						|
		}
 | 
						|
	}
 | 
						|
	printk(" ");
 | 
						|
}
 | 
						|
 | 
						|
static struct ctl_table *sysctl_check_lookup(struct nsproxy *namespaces,
 | 
						|
						struct ctl_table *table)
 | 
						|
{
 | 
						|
	struct ctl_table_header *head;
 | 
						|
	struct ctl_table *ref, *test;
 | 
						|
	int depth, cur_depth;
 | 
						|
 | 
						|
	depth = sysctl_depth(table);
 | 
						|
 | 
						|
	for (head = __sysctl_head_next(namespaces, NULL); head;
 | 
						|
	     head = __sysctl_head_next(namespaces, head)) {
 | 
						|
		cur_depth = depth;
 | 
						|
		ref = head->ctl_table;
 | 
						|
repeat:
 | 
						|
		test = sysctl_parent(table, cur_depth);
 | 
						|
		for (; ref->procname; ref++) {
 | 
						|
			int match = 0;
 | 
						|
			if (cur_depth && !ref->child)
 | 
						|
				continue;
 | 
						|
 | 
						|
			if (test->procname && ref->procname &&
 | 
						|
			    (strcmp(test->procname, ref->procname) == 0))
 | 
						|
					match++;
 | 
						|
 | 
						|
			if (match) {
 | 
						|
				if (cur_depth != 0) {
 | 
						|
					cur_depth--;
 | 
						|
					ref = ref->child;
 | 
						|
					goto repeat;
 | 
						|
				}
 | 
						|
				goto out;
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	ref = NULL;
 | 
						|
out:
 | 
						|
	sysctl_head_finish(head);
 | 
						|
	return ref;
 | 
						|
}
 | 
						|
 | 
						|
static void set_fail(const char **fail, struct ctl_table *table, const char *str)
 | 
						|
{
 | 
						|
	if (*fail) {
 | 
						|
		printk(KERN_ERR "sysctl table check failed: ");
 | 
						|
		sysctl_print_path(table);
 | 
						|
		printk(" %s\n", *fail);
 | 
						|
		dump_stack();
 | 
						|
	}
 | 
						|
	*fail = str;
 | 
						|
}
 | 
						|
 | 
						|
static void sysctl_check_leaf(struct nsproxy *namespaces,
 | 
						|
				struct ctl_table *table, const char **fail)
 | 
						|
{
 | 
						|
	struct ctl_table *ref;
 | 
						|
 | 
						|
	ref = sysctl_check_lookup(namespaces, table);
 | 
						|
	if (ref && (ref != table))
 | 
						|
		set_fail(fail, table, "Sysctl already exists");
 | 
						|
}
 | 
						|
 | 
						|
int sysctl_check_table(struct nsproxy *namespaces, struct ctl_table *table)
 | 
						|
{
 | 
						|
	int error = 0;
 | 
						|
	for (; table->procname; table++) {
 | 
						|
		const char *fail = NULL;
 | 
						|
 | 
						|
		if (table->parent) {
 | 
						|
			if (table->procname && !table->parent->procname)
 | 
						|
				set_fail(&fail, table, "Parent without procname");
 | 
						|
		}
 | 
						|
		if (!table->procname)
 | 
						|
			set_fail(&fail, table, "No procname");
 | 
						|
		if (table->child) {
 | 
						|
			if (table->data)
 | 
						|
				set_fail(&fail, table, "Directory with data?");
 | 
						|
			if (table->maxlen)
 | 
						|
				set_fail(&fail, table, "Directory with maxlen?");
 | 
						|
			if ((table->mode & (S_IRUGO|S_IXUGO)) != table->mode)
 | 
						|
				set_fail(&fail, table, "Writable sysctl directory");
 | 
						|
			if (table->proc_handler)
 | 
						|
				set_fail(&fail, table, "Directory with proc_handler");
 | 
						|
			if (table->extra1)
 | 
						|
				set_fail(&fail, table, "Directory with extra1");
 | 
						|
			if (table->extra2)
 | 
						|
				set_fail(&fail, table, "Directory with extra2");
 | 
						|
		} else {
 | 
						|
			if ((table->proc_handler == proc_dostring) ||
 | 
						|
			    (table->proc_handler == proc_dointvec) ||
 | 
						|
			    (table->proc_handler == proc_dointvec_minmax) ||
 | 
						|
			    (table->proc_handler == proc_dointvec_jiffies) ||
 | 
						|
			    (table->proc_handler == proc_dointvec_userhz_jiffies) ||
 | 
						|
			    (table->proc_handler == proc_dointvec_ms_jiffies) ||
 | 
						|
			    (table->proc_handler == proc_doulongvec_minmax) ||
 | 
						|
			    (table->proc_handler == proc_doulongvec_ms_jiffies_minmax)) {
 | 
						|
				if (!table->data)
 | 
						|
					set_fail(&fail, table, "No data");
 | 
						|
				if (!table->maxlen)
 | 
						|
					set_fail(&fail, table, "No maxlen");
 | 
						|
			}
 | 
						|
#ifdef CONFIG_PROC_SYSCTL
 | 
						|
			if (table->procname && !table->proc_handler)
 | 
						|
				set_fail(&fail, table, "No proc_handler");
 | 
						|
#endif
 | 
						|
#if 0
 | 
						|
			if (!table->procname && table->proc_handler)
 | 
						|
				set_fail(&fail, table, "proc_handler without procname");
 | 
						|
#endif
 | 
						|
			sysctl_check_leaf(namespaces, table, &fail);
 | 
						|
		}
 | 
						|
		if (table->mode > 0777)
 | 
						|
			set_fail(&fail, table, "bogus .mode");
 | 
						|
		if (fail) {
 | 
						|
			set_fail(&fail, table, NULL);
 | 
						|
			error = -EINVAL;
 | 
						|
		}
 | 
						|
		if (table->child)
 | 
						|
			error |= sysctl_check_table(namespaces, table->child);
 | 
						|
	}
 | 
						|
	return error;
 | 
						|
}
 |