diff options
author | Flavio Leitner <fleitner@redhat.com> | 2010-02-02 10:32:29 -0500 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-02-02 10:32:29 -0500 |
commit | c85bb41e93184bf5494dde6d8fe5a81b564c84c8 (patch) | |
tree | 2a5daec2aecc3fb3eedfba3f63a2156f38575b2d | |
parent | 8b64056dacf6ec81986d63dff96fca039fe95f6e (diff) |
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 <fleitner@redhat.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/linux/igmp.h | 2 | ||||
-rw-r--r-- | net/ipv4/igmp.c | 83 |
2 files changed, 64 insertions, 21 deletions
diff --git a/include/linux/igmp.h b/include/linux/igmp.h index 724c27e5d173..93fc2449af10 100644 --- a/include/linux/igmp.h +++ b/include/linux/igmp.h | |||
@@ -153,6 +153,7 @@ extern int sysctl_igmp_max_msf; | |||
153 | struct ip_sf_socklist { | 153 | struct ip_sf_socklist { |
154 | unsigned int sl_max; | 154 | unsigned int sl_max; |
155 | unsigned int sl_count; | 155 | unsigned int sl_count; |
156 | struct rcu_head rcu; | ||
156 | __be32 sl_addr[0]; | 157 | __be32 sl_addr[0]; |
157 | }; | 158 | }; |
158 | 159 | ||
@@ -170,6 +171,7 @@ struct ip_mc_socklist { | |||
170 | struct ip_mreqn multi; | 171 | struct ip_mreqn multi; |
171 | unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */ | 172 | unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */ |
172 | struct ip_sf_socklist *sflist; | 173 | struct ip_sf_socklist *sflist; |
174 | struct rcu_head rcu; | ||
173 | }; | 175 | }; |
174 | 176 | ||
175 | struct ip_sf_list { | 177 | struct ip_sf_list { |
diff --git a/net/ipv4/igmp.c b/net/ipv4/igmp.c index 8f5468393f01..d28363998743 100644 --- a/net/ipv4/igmp.c +++ b/net/ipv4/igmp.c | |||
@@ -1799,7 +1799,7 @@ int ip_mc_join_group(struct sock *sk , struct ip_mreqn *imr) | |||
1799 | iml->next = inet->mc_list; | 1799 | iml->next = inet->mc_list; |
1800 | iml->sflist = NULL; | 1800 | iml->sflist = NULL; |
1801 | iml->sfmode = MCAST_EXCLUDE; | 1801 | iml->sfmode = MCAST_EXCLUDE; |
1802 | inet->mc_list = iml; | 1802 | rcu_assign_pointer(inet->mc_list, iml); |
1803 | ip_mc_inc_group(in_dev, addr); | 1803 | ip_mc_inc_group(in_dev, addr); |
1804 | err = 0; | 1804 | err = 0; |
1805 | done: | 1805 | done: |
@@ -1807,24 +1807,46 @@ done: | |||
1807 | return err; | 1807 | return err; |
1808 | } | 1808 | } |
1809 | 1809 | ||
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 | |||
1810 | static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml, | 1819 | static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml, |
1811 | struct in_device *in_dev) | 1820 | struct in_device *in_dev) |
1812 | { | 1821 | { |
1822 | struct ip_sf_socklist *psf = iml->sflist; | ||
1813 | int err; | 1823 | int err; |
1814 | 1824 | ||
1815 | if (iml->sflist == NULL) { | 1825 | if (psf == NULL) { |
1816 | /* any-source empty exclude case */ | 1826 | /* any-source empty exclude case */ |
1817 | return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, | 1827 | return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, |
1818 | iml->sfmode, 0, NULL, 0); | 1828 | iml->sfmode, 0, NULL, 0); |
1819 | } | 1829 | } |
1820 | err = ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, | 1830 | err = ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, |
1821 | iml->sfmode, iml->sflist->sl_count, | 1831 | iml->sfmode, psf->sl_count, psf->sl_addr, 0); |
1822 | iml->sflist->sl_addr, 0); | 1832 | rcu_assign_pointer(iml->sflist, NULL); |
1823 | sock_kfree_s(sk, iml->sflist, IP_SFLSIZE(iml->sflist->sl_max)); | 1833 | /* decrease mem now to avoid the memleak warning */ |
1824 | iml->sflist = NULL; | 1834 | atomic_sub(IP_SFLSIZE(psf->sl_max), &sk->sk_omem_alloc); |
1835 | call_rcu(&psf->rcu, ip_sf_socklist_reclaim); | ||
1825 | return err; | 1836 | return err; |
1826 | } | 1837 | } |
1827 | 1838 | ||
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 | |||
1828 | /* | 1850 | /* |
1829 | * Ask a socket to leave a group. | 1851 | * Ask a socket to leave a group. |
1830 | */ | 1852 | */ |
@@ -1854,12 +1876,14 @@ int ip_mc_leave_group(struct sock *sk, struct ip_mreqn *imr) | |||
1854 | 1876 | ||
1855 | (void) ip_mc_leave_src(sk, iml, in_dev); | 1877 | (void) ip_mc_leave_src(sk, iml, in_dev); |
1856 | 1878 | ||
1857 | *imlp = iml->next; | 1879 | rcu_assign_pointer(*imlp, iml->next); |
1858 | 1880 | ||
1859 | if (in_dev) | 1881 | if (in_dev) |
1860 | ip_mc_dec_group(in_dev, group); | 1882 | ip_mc_dec_group(in_dev, group); |
1861 | rtnl_unlock(); | 1883 | 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); | ||
1863 | return 0; | 1887 | return 0; |
1864 | } | 1888 | } |
1865 | if (!in_dev) | 1889 | if (!in_dev) |
@@ -1974,9 +1998,12 @@ int ip_mc_source(int add, int omode, struct sock *sk, struct | |||
1974 | if (psl) { | 1998 | if (psl) { |
1975 | for (i=0; i<psl->sl_count; i++) | 1999 | for (i=0; i<psl->sl_count; i++) |
1976 | newpsl->sl_addr[i] = psl->sl_addr[i]; | 2000 | 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); | ||
1978 | } | 2004 | } |
1979 | pmc->sflist = psl = newpsl; | 2005 | rcu_assign_pointer(pmc->sflist, newpsl); |
2006 | psl = newpsl; | ||
1980 | } | 2007 | } |
1981 | rv = 1; /* > 0 for insert logic below if sl_count is 0 */ | 2008 | rv = 1; /* > 0 for insert logic below if sl_count is 0 */ |
1982 | for (i=0; i<psl->sl_count; i++) { | 2009 | 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) | |||
2072 | if (psl) { | 2099 | if (psl) { |
2073 | (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, | 2100 | (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, |
2074 | psl->sl_count, psl->sl_addr, 0); | 2101 | 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); | ||
2076 | } else | 2105 | } else |
2077 | (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, | 2106 | (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, |
2078 | 0, NULL, 0); | 2107 | 0, NULL, 0); |
2079 | pmc->sflist = newpsl; | 2108 | rcu_assign_pointer(pmc->sflist, newpsl); |
2080 | pmc->sfmode = msf->imsf_fmode; | 2109 | pmc->sfmode = msf->imsf_fmode; |
2081 | err = 0; | 2110 | err = 0; |
2082 | done: | 2111 | done: |
@@ -2209,30 +2238,40 @@ int ip_mc_sf_allow(struct sock *sk, __be32 loc_addr, __be32 rmt_addr, int dif) | |||
2209 | struct ip_mc_socklist *pmc; | 2238 | struct ip_mc_socklist *pmc; |
2210 | struct ip_sf_socklist *psl; | 2239 | struct ip_sf_socklist *psl; |
2211 | int i; | 2240 | int i; |
2241 | int ret; | ||
2212 | 2242 | ||
2243 | ret = 1; | ||
2213 | if (!ipv4_is_multicast(loc_addr)) | 2244 | if (!ipv4_is_multicast(loc_addr)) |
2214 | return 1; | 2245 | goto out; |
2215 | 2246 | ||
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)) { | ||
2217 | if (pmc->multi.imr_multiaddr.s_addr == loc_addr && | 2249 | if (pmc->multi.imr_multiaddr.s_addr == loc_addr && |
2218 | pmc->multi.imr_ifindex == dif) | 2250 | pmc->multi.imr_ifindex == dif) |
2219 | break; | 2251 | break; |
2220 | } | 2252 | } |
2253 | ret = inet->mc_all; | ||
2221 | if (!pmc) | 2254 | if (!pmc) |
2222 | return inet->mc_all; | 2255 | goto unlock; |
2223 | psl = pmc->sflist; | 2256 | psl = pmc->sflist; |
2257 | ret = (pmc->sfmode == MCAST_EXCLUDE); | ||
2224 | if (!psl) | 2258 | if (!psl) |
2225 | return pmc->sfmode == MCAST_EXCLUDE; | 2259 | goto unlock; |
2226 | 2260 | ||
2227 | for (i=0; i<psl->sl_count; i++) { | 2261 | for (i=0; i<psl->sl_count; i++) { |
2228 | if (psl->sl_addr[i] == rmt_addr) | 2262 | if (psl->sl_addr[i] == rmt_addr) |
2229 | break; | 2263 | break; |
2230 | } | 2264 | } |
2265 | ret = 0; | ||
2231 | if (pmc->sfmode == MCAST_INCLUDE && i >= psl->sl_count) | 2266 | if (pmc->sfmode == MCAST_INCLUDE && i >= psl->sl_count) |
2232 | return 0; | 2267 | goto unlock; |
2233 | if (pmc->sfmode == MCAST_EXCLUDE && i < psl->sl_count) | 2268 | if (pmc->sfmode == MCAST_EXCLUDE && i < psl->sl_count) |
2234 | return 0; | 2269 | goto unlock; |
2235 | return 1; | 2270 | ret = 1; |
2271 | unlock: | ||
2272 | rcu_read_unlock(); | ||
2273 | out: | ||
2274 | return ret; | ||
2236 | } | 2275 | } |
2237 | 2276 | ||
2238 | /* | 2277 | /* |
@@ -2251,7 +2290,7 @@ void ip_mc_drop_socket(struct sock *sk) | |||
2251 | rtnl_lock(); | 2290 | rtnl_lock(); |
2252 | while ((iml = inet->mc_list) != NULL) { | 2291 | while ((iml = inet->mc_list) != NULL) { |
2253 | struct in_device *in_dev; | 2292 | struct in_device *in_dev; |
2254 | inet->mc_list = iml->next; | 2293 | rcu_assign_pointer(inet->mc_list, iml->next); |
2255 | 2294 | ||
2256 | in_dev = inetdev_by_index(net, iml->multi.imr_ifindex); | 2295 | in_dev = inetdev_by_index(net, iml->multi.imr_ifindex); |
2257 | (void) ip_mc_leave_src(sk, iml, in_dev); | 2296 | (void) ip_mc_leave_src(sk, iml, in_dev); |
@@ -2259,7 +2298,9 @@ void ip_mc_drop_socket(struct sock *sk) | |||
2259 | ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr); | 2298 | ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr); |
2260 | in_dev_put(in_dev); | 2299 | in_dev_put(in_dev); |
2261 | } | 2300 | } |
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); | ||
2263 | } | 2304 | } |
2264 | rtnl_unlock(); | 2305 | rtnl_unlock(); |
2265 | } | 2306 | } |