Commit c85bb41e93184bf5494dde6d8fe5a81b564c84c8
Committed by
David S. Miller
1 parent
8b64056dac
Exists in
master
and in
7 other branches
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>
Showing 2 changed files with 64 additions and 21 deletions Side-by-side Diff
include/linux/igmp.h
... | ... | @@ -153,6 +153,7 @@ |
153 | 153 | struct ip_sf_socklist { |
154 | 154 | unsigned int sl_max; |
155 | 155 | unsigned int sl_count; |
156 | + struct rcu_head rcu; | |
156 | 157 | __be32 sl_addr[0]; |
157 | 158 | }; |
158 | 159 | |
... | ... | @@ -170,6 +171,7 @@ |
170 | 171 | struct ip_mreqn multi; |
171 | 172 | unsigned int sfmode; /* MCAST_{INCLUDE,EXCLUDE} */ |
172 | 173 | struct ip_sf_socklist *sflist; |
174 | + struct rcu_head rcu; | |
173 | 175 | }; |
174 | 176 | |
175 | 177 | struct ip_sf_list { |
net/ipv4/igmp.c
... | ... | @@ -1799,7 +1799,7 @@ |
1799 | 1799 | iml->next = inet->mc_list; |
1800 | 1800 | iml->sflist = NULL; |
1801 | 1801 | iml->sfmode = MCAST_EXCLUDE; |
1802 | - inet->mc_list = iml; | |
1802 | + rcu_assign_pointer(inet->mc_list, iml); | |
1803 | 1803 | ip_mc_inc_group(in_dev, addr); |
1804 | 1804 | err = 0; |
1805 | 1805 | done: |
1806 | 1806 | |
1807 | 1807 | |
1808 | 1808 | |
1809 | 1809 | |
... | ... | @@ -1807,24 +1807,46 @@ |
1807 | 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 | 1819 | static int ip_mc_leave_src(struct sock *sk, struct ip_mc_socklist *iml, |
1811 | 1820 | struct in_device *in_dev) |
1812 | 1821 | { |
1822 | + struct ip_sf_socklist *psf = iml->sflist; | |
1813 | 1823 | int err; |
1814 | 1824 | |
1815 | - if (iml->sflist == NULL) { | |
1825 | + if (psf == NULL) { | |
1816 | 1826 | /* any-source empty exclude case */ |
1817 | 1827 | return ip_mc_del_src(in_dev, &iml->multi.imr_multiaddr.s_addr, |
1818 | 1828 | iml->sfmode, 0, NULL, 0); |
1819 | 1829 | } |
1820 | 1830 | 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); | |
1825 | 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 | 1851 | * Ask a socket to leave a group. |
1830 | 1852 | */ |
1831 | 1853 | |
... | ... | @@ -1854,12 +1876,14 @@ |
1854 | 1876 | |
1855 | 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 | 1881 | if (in_dev) |
1860 | 1882 | ip_mc_dec_group(in_dev, group); |
1861 | 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 | 1887 | return 0; |
1864 | 1888 | } |
1865 | 1889 | if (!in_dev) |
1866 | 1890 | |
... | ... | @@ -1974,9 +1998,12 @@ |
1974 | 1998 | if (psl) { |
1975 | 1999 | for (i=0; i<psl->sl_count; i++) |
1976 | 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 | 2008 | rv = 1; /* > 0 for insert logic below if sl_count is 0 */ |
1982 | 2009 | for (i=0; i<psl->sl_count; i++) { |
1983 | 2010 | |
... | ... | @@ -2072,11 +2099,13 @@ |
2072 | 2099 | if (psl) { |
2073 | 2100 | (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, |
2074 | 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 | 2105 | } else |
2077 | 2106 | (void) ip_mc_del_src(in_dev, &msf->imsf_multiaddr, pmc->sfmode, |
2078 | 2107 | 0, NULL, 0); |
2079 | - pmc->sflist = newpsl; | |
2108 | + rcu_assign_pointer(pmc->sflist, newpsl); | |
2080 | 2109 | pmc->sfmode = msf->imsf_fmode; |
2081 | 2110 | err = 0; |
2082 | 2111 | done: |
2083 | 2112 | |
2084 | 2113 | |
2085 | 2114 | |
2086 | 2115 | |
2087 | 2116 | |
2088 | 2117 | |
2089 | 2118 | |
2090 | 2119 | |
2091 | 2120 | |
2092 | 2121 | |
... | ... | @@ -2209,30 +2238,40 @@ |
2209 | 2238 | struct ip_mc_socklist *pmc; |
2210 | 2239 | struct ip_sf_socklist *psl; |
2211 | 2240 | int i; |
2241 | + int ret; | |
2212 | 2242 | |
2243 | + ret = 1; | |
2213 | 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 | 2249 | if (pmc->multi.imr_multiaddr.s_addr == loc_addr && |
2218 | 2250 | pmc->multi.imr_ifindex == dif) |
2219 | 2251 | break; |
2220 | 2252 | } |
2253 | + ret = inet->mc_all; | |
2221 | 2254 | if (!pmc) |
2222 | - return inet->mc_all; | |
2255 | + goto unlock; | |
2223 | 2256 | psl = pmc->sflist; |
2257 | + ret = (pmc->sfmode == MCAST_EXCLUDE); | |
2224 | 2258 | if (!psl) |
2225 | - return pmc->sfmode == MCAST_EXCLUDE; | |
2259 | + goto unlock; | |
2226 | 2260 | |
2227 | 2261 | for (i=0; i<psl->sl_count; i++) { |
2228 | 2262 | if (psl->sl_addr[i] == rmt_addr) |
2229 | 2263 | break; |
2230 | 2264 | } |
2265 | + ret = 0; | |
2231 | 2266 | if (pmc->sfmode == MCAST_INCLUDE && i >= psl->sl_count) |
2232 | - return 0; | |
2267 | + goto unlock; | |
2233 | 2268 | 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; | |
2236 | 2275 | } |
2237 | 2276 | |
2238 | 2277 | /* |
... | ... | @@ -2251,7 +2290,7 @@ |
2251 | 2290 | rtnl_lock(); |
2252 | 2291 | while ((iml = inet->mc_list) != NULL) { |
2253 | 2292 | struct in_device *in_dev; |
2254 | - inet->mc_list = iml->next; | |
2293 | + rcu_assign_pointer(inet->mc_list, iml->next); | |
2255 | 2294 | |
2256 | 2295 | in_dev = inetdev_by_index(net, iml->multi.imr_ifindex); |
2257 | 2296 | (void) ip_mc_leave_src(sk, iml, in_dev); |
... | ... | @@ -2259,7 +2298,9 @@ |
2259 | 2298 | ip_mc_dec_group(in_dev, iml->multi.imr_multiaddr.s_addr); |
2260 | 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 | 2305 | rtnl_unlock(); |
2265 | 2306 | } |