Commit message: igmp: Avoid zero delay when receiving odd mixture of IGMP queries

Commit 5b7c84066733c5dfb0e4016d939757b38de189e4 ('ipv4: correct IGMP
behavior on v3 query during v2-compatibility mode') added yet another
case for query parsing, which can result in max_delay = 0.  Substitute
a value of 1, as in the usual v3 case.

Reported-by: Simon McVittie <smcv@debian.org>
References: http://bugs.debian.org/654876
Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Signed-off-by: David S. Miller <davem@davemloft.net>
CVE: CVE-2012-0207
CWE: CWE-399
Vulnerability description: The igmp_heard_query function in net/ipv4/igmp.c in the Linux kernel before 3.2.1 allows remote attackers to cause a denial of service (divide-by-zero error and panic) via IGMP packets.

Project: linux
File: igmp.c

	__releases(rcu)
{
	struct igmp_mc_iter_state *state = igmp_mc_seq_private(seq);

	state->in_dev = NULL;
	state->dev = NULL;
	rcu_read_unlock();
}

Project: linux
File: igmp.c

static void __net_exit igmp_net_exit(struct net *net)
{
	proc_net_remove(net, "mcfilter");
	proc_net_remove(net, "igmp");
}

Project: linux
File: igmp.c

static int igmp_xmarksources(struct ip_mc_list *pmc, int nsrcs, __be32 *srcs)
{
	struct ip_sf_list *psf;
	int i, scount;

	scount = 0;
	for (psf=pmc->sources; psf; psf=psf->sf_next) {
		if (scount == nsrcs)
			break;
		for (i=0; i<nsrcs; i++) {
			/* skip inactive filters */
			if (psf->sf_count[MCAST_INCLUDE] ||
			    pmc->sfcount[MCAST_EXCLUDE] !=
			    psf->sf_count[MCAST_EXCLUDE])
				continue;
			if (srcs[i] == psf->sf_inaddr) {
				scount++;
				break;
			}
		}
	}
	pmc->gsquery = 0;
	if (scount == nsrcs)	/* all sources excluded */
		return 0;
	return 1;
}

Project: linux
File: igmp.c

int ip_mc_msfget(struct sock *sk, struct ip_msfilter *msf,
	struct ip_msfilter __user *optval, int __user *optlen)
{
	int err, len, count, copycount;
	struct ip_mreqn	imr;
	__be32 addr = msf->imsf_multiaddr;
	struct ip_mc_socklist *pmc;
	struct in_device *in_dev;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_sf_socklist *psl;
	struct net *net = sock_net(sk);

	if (!ipv4_is_multicast(addr))
		return -EINVAL;

	rtnl_lock();

	imr.imr_multiaddr.s_addr = msf->imsf_multiaddr;
	imr.imr_address.s_addr = msf->imsf_interface;
	imr.imr_ifindex = 0;
	in_dev = ip_mc_find_dev(net, &imr);

	if (!in_dev) {
		err = -ENODEV;
		goto done;
	}
	err = -EADDRNOTAVAIL;

	for_each_pmc_rtnl(inet, pmc) {
		if (pmc->multi.imr_multiaddr.s_addr == msf->imsf_multiaddr &&
		    pmc->multi.imr_ifindex == imr.imr_ifindex)
			break;
	}
	if (!pmc)		/* must have a prior join */
		goto done;
	msf->imsf_fmode = pmc->sfmode;
	psl = rtnl_dereference(pmc->sflist);
	rtnl_unlock();
	if (!psl) {
		len = 0;
		count = 0;
	} else {
		count = psl->sl_count;
	}
	copycount = count < msf->imsf_numsrc ? count : msf->imsf_numsrc;
	len = copycount * sizeof(psl->sl_addr[0]);
	msf->imsf_numsrc = count;
	if (put_user(IP_MSFILTER_SIZE(copycount), optlen) ||
	    copy_to_user(optval, msf, IP_MSFILTER_SIZE(0))) {
		return -EFAULT;
	}
	if (len &&
	    copy_to_user(&optval->imsf_slist[0], psl->sl_addr, len))
		return -EFAULT;
	return 0;
done:
	rtnl_unlock();
	return err;
}

Project: linux
File: igmp.c

int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf, int ifindex)
{
	int err = 0;
	struct ip_mreqn	imr;
	__be32 addr = msf->imsf_multiaddr;
	struct ip_mc_socklist *pmc;
	struct in_device *in_dev;
	struct inet_sock *inet = inet_sk(sk);
	struct ip_sf_socklist *newpsl, *psl;
	struct net *net = sock_net(sk);
	int leavegroup = 0;

	if (!ipv4_is_multicast(addr))
		return -EINVAL;
	if (msf->imsf_fmode != MCAST_INCLUDE &&
	    msf->imsf_fmode != MCAST_EXCLUDE)
		return -EINVAL;

	rtnl_lock();

	imr.imr_multiaddr.s_addr = msf->imsf_multiaddr;
	imr.imr_address.s_addr = msf->imsf_interface;
	imr.imr_ifindex = ifindex;
	in_dev = ip_mc_find_dev(net, &imr);

	if (!in_dev) {
		err = -ENODEV;
		goto done;
	}

	/* special case - (INCLUDE, empty) == LEAVE_GROUP */
	if (msf->imsf_fmode == MCAST_INCLUDE && msf->imsf_numsrc == 0) {
		leavegroup = 1;
		goto done;
	}

	for_each_pmc_rtnl(inet, pmc) {
		if (pmc->multi.imr_multiaddr.s_addr == msf->imsf_multiaddr &&
		    pmc->multi.imr_ifindex == imr.imr_ifindex)
			break;
	}
	if (!pmc) {		/* must have a prior join */
		err = -EINVAL;
		goto done;
	}
	if (msf->imsf_numsrc) {
		newpsl = sock_kmalloc(sk, IP_SFLSIZE(msf->imsf_numsrc),
							   GFP_KERNEL);
		if (!newpsl) {
			err = -ENOBUFS;
			goto done;
		}
		newpsl->sl_max = newpsl->sl_count = msf->imsf_numsrc;
		memcpy(newpsl->sl_addr, msf->imsf_slist,
			msf->imsf_numsrc * sizeof(msf->imsf_slist[0]));
		err = ip_mc_add_src(in_dev, &msf->imsf_multiaddr,
			msf->imsf_fmode, newpsl->sl_count, newpsl->sl_addr, 0);
		if (err) {
			sock_kfree_s(sk, newpsl, IP_SFLSIZE(newpsl->sl_max));
			goto done;
		}
	} else {
		newpsl = NULL;
		(void) ip_mc_add_src(in_dev, &msf->imsf_multiaddr,
				     msf->imsf_fmode, 0, NULL, 0);
	}
	psl = rtnl_dereference(pmc->sflist);
	if (psl) {
		(void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
			psl->sl_count, psl->sl_addr, 0);
		/* decrease mem now to avoid the memleak warning */
		atomic_sub(IP_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
		kfree_rcu(psl, rcu);
	} else
		(void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
			0, NULL, 0);
	RCU_INIT_POINTER(pmc->sflist, newpsl);
	pmc->sfmode = msf->imsf_fmode;
	err = 0;
done:
	rtnl_unlock();
	if (leavegroup)
		err = ip_mc_leave_group(sk, &imr);
	return err;
}

Project: linux
File: igmp.c

void ip_mc_remap(struct in_device *in_dev)
{
	struct ip_mc_list *pmc;

	ASSERT_RTNL();

	for_each_pmc_rtnl(in_dev, pmc)
		igmp_group_added(pmc);
}