Commit message: bridge: netfilter: fix information leak

Struct tmp is copied from userspace.  It is not checked whether the "name"
field is NULL terminated.  This may lead to buffer overflow and passing
contents of kernel stack as a module name to try_then_request_module() and,
consequently, to modprobe commandline.  It would be seen by all userspace
processes.

Signed-off-by: Vasiliy Kulikov <segoon@openwall.com>
Signed-off-by: Patrick McHardy <kaber@trash.net>
CVE: CVE-2011-1080
CWE: CWE-20
Vulnerability description: The do_replace function in net/bridge/netfilter/ebtables.c in the Linux kernel before 2.6.39 does not ensure that a certain name field ends with a '\0' character, which allows local users to obtain potentially sensitive information from kernel stack memory by leveraging the CAP_NET_ADMIN capability to replace a table, and then reading a modprobe command line.

Project: linux
File: ebtables.c

static int compat_calc_match(struct ebt_entry_match *m, int *off)
{
	*off += ebt_compat_match_offset(m->u.match, m->match_size);
	*off += ebt_compat_entry_padsize();
	return 0;
}

Project: linux
File: ebtables.c

static int compat_table_info(const struct ebt_table_info *info,
			     struct compat_ebt_replace *newinfo)
{
	unsigned int size = info->entries_size;
	const void *entries = info->entries;

	newinfo->entries_size = size;

	xt_compat_init_offsets(AF_INET, info->nentries);
	return EBT_ENTRY_ITERATE(entries, size, compat_calc_entry, info,
							entries, newinfo);
}

Project: linux
File: ebtables.c

static int compat_update_counters(struct net *net, void __user *user,
				  unsigned int len)
{
	struct compat_ebt_replace hlp;

	if (copy_from_user(&hlp, user, sizeof(hlp)))
		return -EFAULT;

	/* try real handler in case userland supplied needed padding */
	if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
		return update_counters(net, user, len);

	return do_update_counters(net, hlp.name, compat_ptr(hlp.counters),
					hlp.num_counters, user, len);
}

Project: linux
File: ebtables.c

ebt_check_entry(struct ebt_entry *e, struct net *net,
   const struct ebt_table_info *newinfo,
   const char *name, unsigned int *cnt,
   struct ebt_cl_stack *cl_s, unsigned int udc_cnt)
{
	struct ebt_entry_target *t;
	struct xt_target *target;
	unsigned int i, j, hook = 0, hookmask = 0;
	size_t gap;
	int ret;
	struct xt_mtchk_param mtpar;
	struct xt_tgchk_param tgpar;

	/* don't mess with the struct ebt_entries */
	if (e->bitmask == 0)
		return 0;

	if (e->bitmask & ~EBT_F_MASK) {
		BUGPRINT("Unknown flag for bitmask\n");
		return -EINVAL;
	}
	if (e->invflags & ~EBT_INV_MASK) {
		BUGPRINT("Unknown flag for inv bitmask\n");
		return -EINVAL;
	}
	if ( (e->bitmask & EBT_NOPROTO) && (e->bitmask & EBT_802_3) ) {
		BUGPRINT("NOPROTO & 802_3 not allowed\n");
		return -EINVAL;
	}
	/* what hook do we belong to? */
	for (i = 0; i < NF_BR_NUMHOOKS; i++) {
		if (!newinfo->hook_entry[i])
			continue;
		if ((char *)newinfo->hook_entry[i] < (char *)e)
			hook = i;
		else
			break;
	}
	/* (1 << NF_BR_NUMHOOKS) tells the check functions the rule is on
	   a base chain */
	if (i < NF_BR_NUMHOOKS)
		hookmask = (1 << hook) | (1 << NF_BR_NUMHOOKS);
	else {
		for (i = 0; i < udc_cnt; i++)
			if ((char *)(cl_s[i].cs.chaininfo) > (char *)e)
				break;
		if (i == 0)
			hookmask = (1 << hook) | (1 << NF_BR_NUMHOOKS);
		else
			hookmask = cl_s[i - 1].hookmask;
	}
	i = 0;

	mtpar.net	= tgpar.net       = net;
	mtpar.table     = tgpar.table     = name;
	mtpar.entryinfo = tgpar.entryinfo = e;
	mtpar.hook_mask = tgpar.hook_mask = hookmask;
	mtpar.family    = tgpar.family    = NFPROTO_BRIDGE;
	ret = EBT_MATCH_ITERATE(e, ebt_check_match, &mtpar, &i);
	if (ret != 0)
		goto cleanup_matches;
	j = 0;
	ret = EBT_WATCHER_ITERATE(e, ebt_check_watcher, &tgpar, &j);
	if (ret != 0)
		goto cleanup_watchers;
	t = (struct ebt_entry_target *)(((char *)e) + e->target_offset);
	gap = e->next_offset - e->target_offset;

	target = xt_request_find_target(NFPROTO_BRIDGE, t->u.name, 0);
	if (IS_ERR(target)) {
		ret = PTR_ERR(target);
		goto cleanup_watchers;
	}

	t->u.target = target;
	if (t->u.target == &ebt_standard_target) {
		if (gap < sizeof(struct ebt_standard_target)) {
			BUGPRINT("Standard target size too big\n");
			ret = -EFAULT;
			goto cleanup_watchers;
		}
		if (((struct ebt_standard_target *)t)->verdict <
		   -NUM_STANDARD_TARGETS) {
			BUGPRINT("Invalid standard target\n");
			ret = -EFAULT;
			goto cleanup_watchers;
		}
	} else if (t->target_size > gap - sizeof(struct ebt_entry_target)) {
		module_put(t->u.target->me);
		ret = -EFAULT;
		goto cleanup_watchers;
	}

	tgpar.target   = target;
	tgpar.targinfo = t->data;
	ret = xt_check_target(&tgpar, t->target_size,
	      e->ethproto, e->invflags & EBT_IPROTO);
	if (ret < 0) {
		module_put(target->me);
		goto cleanup_watchers;
	}
	(*cnt)++;
	return 0;
cleanup_watchers:
	EBT_WATCHER_ITERATE(e, ebt_cleanup_watcher, net, &j);
cleanup_matches:
	EBT_MATCH_ITERATE(e, ebt_cleanup_match, net, &i);
	return ret;
}

Project: linux
File: ebtables.c

ebt_do_match(struct ebt_entry_match *m, const struct sk_buff *skb,
	     struct xt_action_param *par)
{
	par->match     = m->u.match;
	par->matchinfo = m->data;
	return m->u.match->match(skb, par) ? EBT_MATCH : EBT_NOMATCH;
}

Project: linux
File: ebtables.c

ebt_do_watcher(const struct ebt_entry_watcher *w, struct sk_buff *skb,
	       struct xt_action_param *par)
{
	par->target   = w->u.watcher;
	par->targinfo = w->data;
	w->u.watcher->target(skb, par);
	/* watchers don't give a verdict */
	return 0;
}

Project: linux
File: ebtables.c

static int ebt_standard_compat_to_user(void __user *dst, const void *src)
{
	compat_int_t cv = *(int *)src;

	if (cv >= 0)
		cv -= xt_compat_calc_jump(NFPROTO_BRIDGE, cv);
	return copy_to_user(dst, &cv, sizeof(cv)) ? -EFAULT : 0;
}

Project: linux
File: ebtables.c

static int update_counters(struct net *net, const void __user *user,
			    unsigned int len)
{
	struct ebt_replace hlp;

	if (copy_from_user(&hlp, user, sizeof(hlp)))
		return -EFAULT;

	if (len != sizeof(hlp) + hlp.num_counters * sizeof(struct ebt_counter))
		return -EINVAL;

	return do_update_counters(net, hlp.name, hlp.counters,
				hlp.num_counters, user, len);
}