Commit 5ab4a6c8 authored by David L Stevens's avatar David L Stevens Committed by David S. Miller

[IPV6] mcast: Fix multiple issues in MLDv2 reports.

The below "jumbo" patch fixes the following problems in MLDv2.

1) Add necessary "ntohs" to recent "pskb_may_pull" check [breaks
        all nonzero source queries on little-endian (!)]

2) Add locking to source filter list [resend of prior patch]

3) fix "mld_marksources()" to
        a) send nothing when all queried sources are excluded
        b) send full exclude report when source queried sources are
                not excluded
        c) don't schedule a timer when there's nothing to report

NOTE: RFC 3810 specifies the source list should be saved and each
  source reported individually as an IS_IN. This is an obvious DOS
  path, requiring the host to store and then multicast as many sources
  as are queried (e.g., millions...). This alternative sends a full, 
  relevant report that's limited to number of sources present on the
  machine.

4) fix "add_grec()" to send empty-source records when it should
        The original check doesn't account for a non-empty source
        list with all sources inactive; the new code keeps that
        short-circuit case, and also generates the group header
        with an empty list if needed.

5) fix mca_crcount decrement to be after add_grec(), which needs
        its original value

These issues (other than item #1 ;-) ) were all found by Yan Zheng,
much thanks!
Signed-off-by: default avatarDavid L Stevens <dlstevens@us.ibm.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 1b93ae64
...@@ -83,6 +83,7 @@ struct ipv6_mc_socklist ...@@ -83,6 +83,7 @@ struct ipv6_mc_socklist
struct in6_addr addr; struct in6_addr addr;
int ifindex; int ifindex;
struct ipv6_mc_socklist *next; struct ipv6_mc_socklist *next;
rwlock_t sflock;
unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */ unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */
struct ip6_sf_socklist *sflist; struct ip6_sf_socklist *sflist;
}; };
......
...@@ -224,6 +224,7 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr) ...@@ -224,6 +224,7 @@ int ipv6_sock_mc_join(struct sock *sk, int ifindex, struct in6_addr *addr)
mc_lst->ifindex = dev->ifindex; mc_lst->ifindex = dev->ifindex;
mc_lst->sfmode = MCAST_EXCLUDE; mc_lst->sfmode = MCAST_EXCLUDE;
mc_lst->sflock = RW_LOCK_UNLOCKED;
mc_lst->sflist = NULL; mc_lst->sflist = NULL;
/* /*
...@@ -360,6 +361,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk, ...@@ -360,6 +361,7 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
struct ip6_sf_socklist *psl; struct ip6_sf_socklist *psl;
int i, j, rv; int i, j, rv;
int leavegroup = 0; int leavegroup = 0;
int pmclocked = 0;
int err; int err;
if (pgsr->gsr_group.ss_family != AF_INET6 || if (pgsr->gsr_group.ss_family != AF_INET6 ||
...@@ -403,6 +405,9 @@ int ip6_mc_source(int add, int omode, struct sock *sk, ...@@ -403,6 +405,9 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
pmc->sfmode = omode; pmc->sfmode = omode;
} }
write_lock_bh(&pmc->sflock);
pmclocked = 1;
psl = pmc->sflist; psl = pmc->sflist;
if (!add) { if (!add) {
if (!psl) if (!psl)
...@@ -475,6 +480,8 @@ int ip6_mc_source(int add, int omode, struct sock *sk, ...@@ -475,6 +480,8 @@ int ip6_mc_source(int add, int omode, struct sock *sk,
/* update the interface list */ /* update the interface list */
ip6_mc_add_src(idev, group, omode, 1, source, 1); ip6_mc_add_src(idev, group, omode, 1, source, 1);
done: done:
if (pmclocked)
write_unlock_bh(&pmc->sflock);
read_unlock_bh(&ipv6_sk_mc_lock); read_unlock_bh(&ipv6_sk_mc_lock);
read_unlock_bh(&idev->lock); read_unlock_bh(&idev->lock);
in6_dev_put(idev); in6_dev_put(idev);
...@@ -510,6 +517,8 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) ...@@ -510,6 +517,8 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
dev = idev->dev; dev = idev->dev;
err = 0; err = 0;
read_lock_bh(&ipv6_sk_mc_lock);
if (gsf->gf_fmode == MCAST_INCLUDE && gsf->gf_numsrc == 0) { if (gsf->gf_fmode == MCAST_INCLUDE && gsf->gf_numsrc == 0) {
leavegroup = 1; leavegroup = 1;
goto done; goto done;
...@@ -549,6 +558,8 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) ...@@ -549,6 +558,8 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
newpsl = NULL; newpsl = NULL;
(void) ip6_mc_add_src(idev, group, gsf->gf_fmode, 0, NULL, 0); (void) ip6_mc_add_src(idev, group, gsf->gf_fmode, 0, NULL, 0);
} }
write_lock_bh(&pmc->sflock);
psl = pmc->sflist; psl = pmc->sflist;
if (psl) { if (psl) {
(void) ip6_mc_del_src(idev, group, pmc->sfmode, (void) ip6_mc_del_src(idev, group, pmc->sfmode,
...@@ -558,8 +569,10 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf) ...@@ -558,8 +569,10 @@ int ip6_mc_msfilter(struct sock *sk, struct group_filter *gsf)
(void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0); (void) ip6_mc_del_src(idev, group, pmc->sfmode, 0, NULL, 0);
pmc->sflist = newpsl; pmc->sflist = newpsl;
pmc->sfmode = gsf->gf_fmode; pmc->sfmode = gsf->gf_fmode;
write_unlock_bh(&pmc->sflock);
err = 0; err = 0;
done: done:
read_unlock_bh(&ipv6_sk_mc_lock);
read_unlock_bh(&idev->lock); read_unlock_bh(&idev->lock);
in6_dev_put(idev); in6_dev_put(idev);
dev_put(dev); dev_put(dev);
...@@ -592,6 +605,11 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf, ...@@ -592,6 +605,11 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
dev = idev->dev; dev = idev->dev;
err = -EADDRNOTAVAIL; err = -EADDRNOTAVAIL;
/*
* changes to the ipv6_mc_list require the socket lock and
* a read lock on ip6_sk_mc_lock. We have the socket lock,
* so reading the list is safe.
*/
for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) { for (pmc=inet6->ipv6_mc_list; pmc; pmc=pmc->next) {
if (pmc->ifindex != gsf->gf_interface) if (pmc->ifindex != gsf->gf_interface)
...@@ -614,6 +632,10 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf, ...@@ -614,6 +632,10 @@ int ip6_mc_msfget(struct sock *sk, struct group_filter *gsf,
copy_to_user(optval, gsf, GROUP_FILTER_SIZE(0))) { copy_to_user(optval, gsf, GROUP_FILTER_SIZE(0))) {
return -EFAULT; return -EFAULT;
} }
/* changes to psl require the socket lock, a read lock on
* on ipv6_sk_mc_lock and a write lock on pmc->sflock. We
* have the socket lock, so reading here is safe.
*/
for (i=0; i<copycount; i++) { for (i=0; i<copycount; i++) {
struct sockaddr_in6 *psin6; struct sockaddr_in6 *psin6;
struct sockaddr_storage ss; struct sockaddr_storage ss;
...@@ -650,6 +672,7 @@ int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr, ...@@ -650,6 +672,7 @@ int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr,
read_unlock(&ipv6_sk_mc_lock); read_unlock(&ipv6_sk_mc_lock);
return 1; return 1;
} }
read_lock(&mc->sflock);
psl = mc->sflist; psl = mc->sflist;
if (!psl) { if (!psl) {
rv = mc->sfmode == MCAST_EXCLUDE; rv = mc->sfmode == MCAST_EXCLUDE;
...@@ -665,6 +688,7 @@ int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr, ...@@ -665,6 +688,7 @@ int inet6_mc_check(struct sock *sk, struct in6_addr *mc_addr,
if (mc->sfmode == MCAST_EXCLUDE && i < psl->sl_count) if (mc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
rv = 0; rv = 0;
} }
read_unlock(&mc->sflock);
read_unlock(&ipv6_sk_mc_lock); read_unlock(&ipv6_sk_mc_lock);
return rv; return rv;
...@@ -1068,7 +1092,8 @@ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime) ...@@ -1068,7 +1092,8 @@ static void igmp6_group_queried(struct ifmcaddr6 *ma, unsigned long resptime)
ma->mca_flags |= MAF_TIMER_RUNNING; ma->mca_flags |= MAF_TIMER_RUNNING;
} }
static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, /* mark EXCLUDE-mode sources */
static int mld_xmarksources(struct ifmcaddr6 *pmc, int nsrcs,
struct in6_addr *srcs) struct in6_addr *srcs)
{ {
struct ip6_sf_list *psf; struct ip6_sf_list *psf;
...@@ -1078,13 +1103,53 @@ static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs, ...@@ -1078,13 +1103,53 @@ static void mld_marksources(struct ifmcaddr6 *pmc, int nsrcs,
for (psf=pmc->mca_sources; psf; psf=psf->sf_next) { for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
if (scount == nsrcs) if (scount == nsrcs)
break; break;
for (i=0; i<nsrcs; i++) for (i=0; i<nsrcs; i++) {
/* skip inactive filters */
if (pmc->mca_sfcount[MCAST_INCLUDE] ||
pmc->mca_sfcount[MCAST_EXCLUDE] !=
psf->sf_count[MCAST_EXCLUDE])
continue;
if (ipv6_addr_equal(&srcs[i], &psf->sf_addr)) {
scount++;
break;
}
}
}
pmc->mca_flags &= ~MAF_GSQUERY;
if (scount == nsrcs) /* all sources excluded */
return 0;
return 1;
}
static int mld_marksources(struct ifmcaddr6 *pmc, int nsrcs,
struct in6_addr *srcs)
{
struct ip6_sf_list *psf;
int i, scount;
if (pmc->mca_sfmode == MCAST_EXCLUDE)
return mld_xmarksources(pmc, nsrcs, srcs);
/* mark INCLUDE-mode sources */
scount = 0;
for (psf=pmc->mca_sources; psf; psf=psf->sf_next) {
if (scount == nsrcs)
break;
for (i=0; i<nsrcs; i++) {
if (ipv6_addr_equal(&srcs[i], &psf->sf_addr)) { if (ipv6_addr_equal(&srcs[i], &psf->sf_addr)) {
psf->sf_gsresp = 1; psf->sf_gsresp = 1;
scount++; scount++;
break; break;
} }
}
}
if (!scount) {
pmc->mca_flags &= ~MAF_GSQUERY;
return 0;
} }
pmc->mca_flags |= MAF_GSQUERY;
return 1;
} }
int igmp6_event_query(struct sk_buff *skb) int igmp6_event_query(struct sk_buff *skb)
...@@ -1167,7 +1232,7 @@ int igmp6_event_query(struct sk_buff *skb) ...@@ -1167,7 +1232,7 @@ int igmp6_event_query(struct sk_buff *skb)
/* mark sources to include, if group & source-specific */ /* mark sources to include, if group & source-specific */
if (mlh2->nsrcs != 0) { if (mlh2->nsrcs != 0) {
if (!pskb_may_pull(skb, srcs_offset + if (!pskb_may_pull(skb, srcs_offset +
mlh2->nsrcs * sizeof(struct in6_addr))) { ntohs(mlh2->nsrcs) * sizeof(struct in6_addr))) {
in6_dev_put(idev); in6_dev_put(idev);
return -EINVAL; return -EINVAL;
} }
...@@ -1203,10 +1268,9 @@ int igmp6_event_query(struct sk_buff *skb) ...@@ -1203,10 +1268,9 @@ int igmp6_event_query(struct sk_buff *skb)
else else
ma->mca_flags &= ~MAF_GSQUERY; ma->mca_flags &= ~MAF_GSQUERY;
} }
if (ma->mca_flags & MAF_GSQUERY) if (!(ma->mca_flags & MAF_GSQUERY) ||
mld_marksources(ma, ntohs(mlh2->nsrcs), mld_marksources(ma, ntohs(mlh2->nsrcs), mlh2->srcs))
mlh2->srcs); igmp6_group_queried(ma, max_delay);
igmp6_group_queried(ma, max_delay);
spin_unlock_bh(&ma->mca_lock); spin_unlock_bh(&ma->mca_lock);
if (group_type != IPV6_ADDR_ANY) if (group_type != IPV6_ADDR_ANY)
break; break;
...@@ -1281,7 +1345,18 @@ static int is_in(struct ifmcaddr6 *pmc, struct ip6_sf_list *psf, int type, ...@@ -1281,7 +1345,18 @@ static int is_in(struct ifmcaddr6 *pmc, struct ip6_sf_list *psf, int type,
case MLD2_MODE_IS_EXCLUDE: case MLD2_MODE_IS_EXCLUDE:
if (gdeleted || sdeleted) if (gdeleted || sdeleted)
return 0; return 0;
return !((pmc->mca_flags & MAF_GSQUERY) && !psf->sf_gsresp); if (!((pmc->mca_flags & MAF_GSQUERY) && !psf->sf_gsresp)) {
if (pmc->mca_sfmode == MCAST_INCLUDE)
return 1;
/* don't include if this source is excluded
* in all filters
*/
if (psf->sf_count[MCAST_INCLUDE])
return 0;
return pmc->mca_sfcount[MCAST_EXCLUDE] ==
psf->sf_count[MCAST_EXCLUDE];
}
return 0;
case MLD2_CHANGE_TO_INCLUDE: case MLD2_CHANGE_TO_INCLUDE:
if (gdeleted || sdeleted) if (gdeleted || sdeleted)
return 0; return 0;
...@@ -1450,7 +1525,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, ...@@ -1450,7 +1525,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
struct mld2_report *pmr; struct mld2_report *pmr;
struct mld2_grec *pgr = NULL; struct mld2_grec *pgr = NULL;
struct ip6_sf_list *psf, *psf_next, *psf_prev, **psf_list; struct ip6_sf_list *psf, *psf_next, *psf_prev, **psf_list;
int scount, first, isquery, truncate; int scount, stotal, first, isquery, truncate;
if (pmc->mca_flags & MAF_NOREPORT) if (pmc->mca_flags & MAF_NOREPORT)
return skb; return skb;
...@@ -1460,25 +1535,13 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, ...@@ -1460,25 +1535,13 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
truncate = type == MLD2_MODE_IS_EXCLUDE || truncate = type == MLD2_MODE_IS_EXCLUDE ||
type == MLD2_CHANGE_TO_EXCLUDE; type == MLD2_CHANGE_TO_EXCLUDE;
stotal = scount = 0;
psf_list = sdeleted ? &pmc->mca_tomb : &pmc->mca_sources; psf_list = sdeleted ? &pmc->mca_tomb : &pmc->mca_sources;
if (!*psf_list) { if (!*psf_list)
if (type == MLD2_ALLOW_NEW_SOURCES || goto empty_source;
type == MLD2_BLOCK_OLD_SOURCES)
return skb;
if (pmc->mca_crcount || isquery) {
/* make sure we have room for group header and at
* least one source.
*/
if (skb && AVAILABLE(skb) < sizeof(struct mld2_grec)+
sizeof(struct in6_addr)) {
mld_sendpack(skb);
skb = NULL; /* add_grhead will get a new one */
}
skb = add_grhead(skb, pmc, type, &pgr);
}
return skb;
}
pmr = skb ? (struct mld2_report *)skb->h.raw : NULL; pmr = skb ? (struct mld2_report *)skb->h.raw : NULL;
/* EX and TO_EX get a fresh packet, if needed */ /* EX and TO_EX get a fresh packet, if needed */
...@@ -1491,7 +1554,6 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, ...@@ -1491,7 +1554,6 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
} }
} }
first = 1; first = 1;
scount = 0;
psf_prev = NULL; psf_prev = NULL;
for (psf=*psf_list; psf; psf=psf_next) { for (psf=*psf_list; psf; psf=psf_next) {
struct in6_addr *psrc; struct in6_addr *psrc;
...@@ -1525,7 +1587,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, ...@@ -1525,7 +1587,7 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
} }
psrc = (struct in6_addr *)skb_put(skb, sizeof(*psrc)); psrc = (struct in6_addr *)skb_put(skb, sizeof(*psrc));
*psrc = psf->sf_addr; *psrc = psf->sf_addr;
scount++; scount++; stotal++;
if ((type == MLD2_ALLOW_NEW_SOURCES || if ((type == MLD2_ALLOW_NEW_SOURCES ||
type == MLD2_BLOCK_OLD_SOURCES) && psf->sf_crcount) { type == MLD2_BLOCK_OLD_SOURCES) && psf->sf_crcount) {
psf->sf_crcount--; psf->sf_crcount--;
...@@ -1540,6 +1602,21 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc, ...@@ -1540,6 +1602,21 @@ static struct sk_buff *add_grec(struct sk_buff *skb, struct ifmcaddr6 *pmc,
} }
psf_prev = psf; psf_prev = psf;
} }
empty_source:
if (!stotal) {
if (type == MLD2_ALLOW_NEW_SOURCES ||
type == MLD2_BLOCK_OLD_SOURCES)
return skb;
if (pmc->mca_crcount || isquery) {
/* make sure we have room for group header */
if (skb && AVAILABLE(skb) < sizeof(struct mld2_grec)) {
mld_sendpack(skb);
skb = NULL; /* add_grhead will get a new one */
}
skb = add_grhead(skb, pmc, type, &pgr);
}
}
if (pgr) if (pgr)
pgr->grec_nsrcs = htons(scount); pgr->grec_nsrcs = htons(scount);
...@@ -1621,11 +1698,11 @@ static void mld_send_cr(struct inet6_dev *idev) ...@@ -1621,11 +1698,11 @@ static void mld_send_cr(struct inet6_dev *idev)
skb = add_grec(skb, pmc, dtype, 1, 1); skb = add_grec(skb, pmc, dtype, 1, 1);
} }
if (pmc->mca_crcount) { if (pmc->mca_crcount) {
pmc->mca_crcount--;
if (pmc->mca_sfmode == MCAST_EXCLUDE) { if (pmc->mca_sfmode == MCAST_EXCLUDE) {
type = MLD2_CHANGE_TO_INCLUDE; type = MLD2_CHANGE_TO_INCLUDE;
skb = add_grec(skb, pmc, type, 1, 0); skb = add_grec(skb, pmc, type, 1, 0);
} }
pmc->mca_crcount--;
if (pmc->mca_crcount == 0) { if (pmc->mca_crcount == 0) {
mld_clear_zeros(&pmc->mca_tomb); mld_clear_zeros(&pmc->mca_tomb);
mld_clear_zeros(&pmc->mca_sources); mld_clear_zeros(&pmc->mca_sources);
...@@ -1659,12 +1736,12 @@ static void mld_send_cr(struct inet6_dev *idev) ...@@ -1659,12 +1736,12 @@ static void mld_send_cr(struct inet6_dev *idev)
/* filter mode changes */ /* filter mode changes */
if (pmc->mca_crcount) { if (pmc->mca_crcount) {
pmc->mca_crcount--;
if (pmc->mca_sfmode == MCAST_EXCLUDE) if (pmc->mca_sfmode == MCAST_EXCLUDE)
type = MLD2_CHANGE_TO_EXCLUDE; type = MLD2_CHANGE_TO_EXCLUDE;
else else
type = MLD2_CHANGE_TO_INCLUDE; type = MLD2_CHANGE_TO_INCLUDE;
skb = add_grec(skb, pmc, type, 0, 0); skb = add_grec(skb, pmc, type, 0, 0);
pmc->mca_crcount--;
} }
spin_unlock_bh(&pmc->mca_lock); spin_unlock_bh(&pmc->mca_lock);
} }
...@@ -2023,6 +2100,9 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml, ...@@ -2023,6 +2100,9 @@ static int ip6_mc_leave_src(struct sock *sk, struct ipv6_mc_socklist *iml,
{ {
int err; int err;
/* callers have the socket lock and a write lock on ipv6_sk_mc_lock,
* so no other readers or writers of iml or its sflist
*/
if (iml->sflist == 0) { if (iml->sflist == 0) {
/* any-source empty exclude case */ /* any-source empty exclude case */
return ip6_mc_del_src(idev, &iml->addr, iml->sfmode, 0, NULL, 0); return ip6_mc_del_src(idev, &iml->addr, iml->sfmode, 0, NULL, 0);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment