Skip to content

Commit c85bb41

Browse files
fleitnerdavem330
authored andcommitted
igmp: fix ip_mc_sf_allow race [v5]
Almost all igmp functions accessing inet->mc_list are protected by rtnl_lock(), but there is one exception which is ip_mc_sf_allow(), so there is a chance of either ip_mc_drop_socket or ip_mc_leave_group remove an entry while ip_mc_sf_allow is running causing a crash. Signed-off-by: Flavio Leitner <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 8b64056 commit c85bb41

File tree

2 files changed

+64
-21
lines changed

2 files changed

+64
-21
lines changed

include/linux/igmp.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ extern int sysctl_igmp_max_msf;
153153
struct ip_sf_socklist {
154154
unsigned int sl_max;
155155
unsigned int sl_count;
156+
struct rcu_head rcu;
156157
__be32 sl_addr[0];
157158
};
158159

@@ -170,6 +171,7 @@ struct ip_mc_socklist {
170171
struct ip_mreqn multi;
171172
unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */
172173
struct ip_sf_socklist *sflist;
174+
struct rcu_head rcu;
173175
};
174176

175177
struct ip_sf_list {

net/ipv4/igmp.c

Lines changed: 62 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1799,32 +1799,54 @@ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr)
17991799
iml->next = inet->mc_list;
18001800
iml->sflist = NULL;
18011801
iml->sfmode = MCAST_EXCLUDE;
1802-
inet->mc_list = iml;
1802+
rcu_assign_pointer(inet->mc_list, iml);
18031803
ip_mc_inc_group(in_dev, addr);
18041804
err = 0;
18051805
done:
18061806
rtnl_unlock();
18071807
return err;
18081808
}
18091809

1810+
static void ip_sf_socklist_reclaim(struct rcu_head *rp)
1811+
{
1812+
struct ip_sf_socklist *psf;
1813+
1814+
psf = container_of(rp, struct ip_sf_socklist, rcu);
1815+
/* sk_omem_alloc should have been decreased by the caller*/
1816+
kfree(psf);
1817+
}
1818+
18101819
static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml,
18111820
struct in_device *in_dev)
18121821
{
1822+
struct ip_sf_socklist *psf = iml->sflist;
18131823
int err;
18141824

1815-
if (iml->sflist == NULL) {
1825+
if (psf == NULL) {
18161826
/* any-source empty exclude case */
18171827
return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr,
18181828
iml->sfmode, 0, NULL, 0);
18191829
}
18201830
err = ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr,
1821-
iml->sfmode, iml->sflist->sl_count,
1822-
iml->sflist->sl_addr, 0);
1823-
sock_kfree_s(sk, iml->sflist, IP_SFLSIZE(iml->sflist->sl_max));
1824-
iml->sflist = NULL;
1831+
iml->sfmode, psf->sl_count, psf->sl_addr, 0);
1832+
rcu_assign_pointer(iml->sflist, NULL);
1833+
/* decrease mem now to avoid the memleak warning */
1834+
atomic_sub(IP_SFLSIZE(psf->sl_max), &sk->sk_omem_alloc);
1835+
call_rcu(&psf->rcu, ip_sf_socklist_reclaim);
18251836
return err;
18261837
}
18271838

1839+
1840+
static void ip_mc_socklist_reclaim(struct rcu_head *rp)
1841+
{
1842+
struct ip_mc_socklist *iml;
1843+
1844+
iml = container_of(rp, struct ip_mc_socklist, rcu);
1845+
/* sk_omem_alloc should have been decreased by the caller*/
1846+
kfree(iml);
1847+
}
1848+
1849+
18281850
/*
18291851
* Ask a socket to leave a group.
18301852
*/
@@ -1854,12 +1876,14 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr)
18541876

18551877
(void) ip_mc_leave_src(sk, iml, in_dev);
18561878

1857-
*imlp = iml->next;
1879+
rcu_assign_pointer(*imlp, iml->next);
18581880

18591881
if (in_dev)
18601882
ip_mc_dec_group(in_dev, group);
18611883
rtnl_unlock();
1862-
sock_kfree_s(sk, iml, sizeof(*iml));
1884+
/* decrease mem now to avoid the memleak warning */
1885+
atomic_sub(sizeof(*iml), &sk->sk_omem_alloc);
1886+
call_rcu(&iml->rcu, ip_mc_socklist_reclaim);
18631887
return 0;
18641888
}
18651889
if (!in_dev)
@@ -1974,9 +1998,12 @@ int ip_mc_source(int add, int omode, struct sock *sk, struct
19741998
if (psl) {
19751999
for (i=0; i<psl->sl_count; i++)
19762000
newpsl->sl_addr[i] = psl->sl_addr[i];
1977-
sock_kfree_s(sk, psl, IP_SFLSIZE(psl->sl_max));
2001+
/* decrease mem now to avoid the memleak warning */
2002+
atomic_sub(IP_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
2003+
call_rcu(&psl->rcu, ip_sf_socklist_reclaim);
19782004
}
1979-
pmc->sflist = psl = newpsl;
2005+
rcu_assign_pointer(pmc->sflist, newpsl);
2006+
psl = newpsl;
19802007
}
19812008
rv = 1; /* > 0 for insert logic below if sl_count is 0 */
19822009
for (i=0; i<psl->sl_count; i++) {
@@ -2072,11 +2099,13 @@ int ip_mc_msfilter(struct sock *sk, struct ip_msfilter *msf, int ifindex)
20722099
if (psl) {
20732100
(void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
20742101
psl->sl_count, psl->sl_addr, 0);
2075-
sock_kfree_s(sk, psl, IP_SFLSIZE(psl->sl_max));
2102+
/* decrease mem now to avoid the memleak warning */
2103+
atomic_sub(IP_SFLSIZE(psl->sl_max), &sk->sk_omem_alloc);
2104+
call_rcu(&psl->rcu, ip_sf_socklist_reclaim);
20762105
} else
20772106
(void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode,
20782107
0, NULL, 0);
2079-
pmc->sflist = newpsl;
2108+
rcu_assign_pointer(pmc->sflist, newpsl);
20802109
pmc->sfmode = msf->imsf_fmode;
20812110
err = 0;
20822111
done:
@@ -2209,30 +2238,40 @@ int ip_mc_sf_allow(struct sock *sk, __be32 loc_addr, __be32 rmt_addr, int dif)
22092238
struct ip_mc_socklist *pmc;
22102239
struct ip_sf_socklist *psl;
22112240
int i;
2241+
int ret;
22122242

2243+
ret = 1;
22132244
if (!ipv4_is_multicast(loc_addr))
2214-
return 1;
2245+
goto out;
22152246

2216-
for (pmc=inet->mc_list; pmc; pmc=pmc->next) {
2247+
rcu_read_lock();
2248+
for (pmc=rcu_dereference(inet->mc_list); pmc; pmc=rcu_dereference(pmc->next)) {
22172249
if (pmc->multi.imr_multiaddr.s_addr == loc_addr &&
22182250
pmc->multi.imr_ifindex == dif)
22192251
break;
22202252
}
2253+
ret = inet->mc_all;
22212254
if (!pmc)
2222-
return inet->mc_all;
2255+
goto unlock;
22232256
psl = pmc->sflist;
2257+
ret = (pmc->sfmode == MCAST_EXCLUDE);
22242258
if (!psl)
2225-
return pmc->sfmode == MCAST_EXCLUDE;
2259+
goto unlock;
22262260

22272261
for (i=0; i<psl->sl_count; i++) {
22282262
if (psl->sl_addr[i] == rmt_addr)
22292263
break;
22302264
}
2265+
ret = 0;
22312266
if (pmc->sfmode == MCAST_INCLUDE && i >= psl->sl_count)
2232-
return 0;
2267+
goto unlock;
22332268
if (pmc->sfmode == MCAST_EXCLUDE && i < psl->sl_count)
2234-
return 0;
2235-
return 1;
2269+
goto unlock;
2270+
ret = 1;
2271+
unlock:
2272+
rcu_read_unlock();
2273+
out:
2274+
return ret;
22362275
}
22372276

22382277
/*
@@ -2251,15 +2290,17 @@ void ip_mc_drop_socket(struct sock *sk)
22512290
rtnl_lock();
22522291
while ((iml = inet->mc_list) != NULL) {
22532292
struct in_device *in_dev;
2254-
inet->mc_list = iml->next;
2293+
rcu_assign_pointer(inet->mc_list, iml->next);
22552294

22562295
in_dev = inetdev_by_index(net, iml->multi.imr_ifindex);
22572296
(void) ip_mc_leave_src(sk, iml, in_dev);
22582297
if (in_dev != NULL) {
22592298
ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr);
22602299
in_dev_put(in_dev);
22612300
}
2262-
sock_kfree_s(sk, iml, sizeof(*iml));
2301+
/* decrease mem now to avoid the memleak warning */
2302+
atomic_sub(sizeof(*iml), &sk->sk_omem_alloc);
2303+
call_rcu(&iml->rcu, ip_mc_socklist_reclaim);
22632304
}
22642305
rtnl_unlock();
22652306
}

0 commit comments

Comments
 (0)