diff options
author | Daniel Borkmann <dborkman@redhat.com> | 2013-11-21 10:50:58 -0500 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-12-08 10:29:26 -0500 |
commit | 026bb4057a0473132bdfa910bd2089de1a7af3f6 (patch) | |
tree | 93c3ec3a9b118a322ea0b1f55acdb1997369fafc /net | |
parent | 1acb97aeff84a72999eeb9cbea523a0079c5ce6d (diff) |
packet: fix use after free race in send path when dev is released
[ Upstream commit e40526cb20b5ee53419452e1f03d97092f144418 ]
Salam reported a use after free bug in PF_PACKET that occurs when
we're sending out frames on a socket bound device and suddenly the
net device is being unregistered. It appears that commit 827d9780
introduced a possible race condition between {t,}packet_snd() and
packet_notifier(). In the case of a bound socket, packet_notifier()
can drop the last reference to the net_device and {t,}packet_snd()
might end up suddenly sending a packet over a freed net_device.
To avoid reverting 827d9780 and thus introducing a performance
regression compared to the current state of things, we decided to
hold a cached RCU protected pointer to the net device and maintain
it on write side via bind spin_lock protected register_prot_hook()
and __unregister_prot_hook() calls.
In {t,}packet_snd() path, we access this pointer under rcu_read_lock
through packet_cached_dev_get() that holds reference to the device
to prevent it from being freed through packet_notifier() while
we're in send path. This is okay to do as dev_put()/dev_hold() are
per-cpu counters, so this should not be a performance issue. Also,
the code simplifies a bit as we don't need need_rls_dev anymore.
Fixes: 827d978037d7 ("af-packet: Use existing netdev reference for bound sockets.")
Reported-by: Salam Noureddine <noureddine@aristanetworks.com>
Signed-off-by: Daniel Borkmann <dborkman@redhat.com>
Signed-off-by: Salam Noureddine <noureddine@aristanetworks.com>
Cc: Ben Greear <greearb@candelatech.com>
Cc: Eric Dumazet <eric.dumazet@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'net')
-rw-r--r-- | net/packet/af_packet.c | 59 | ||||
-rw-r--r-- | net/packet/internal.h | 1 |
2 files changed, 37 insertions, 23 deletions
diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 92e2d0201f90..f0aa20c2bfbf 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c | |||
@@ -244,11 +244,15 @@ static void __fanout_link(struct sock *sk, struct packet_sock *po); | |||
244 | static void register_prot_hook(struct sock *sk) | 244 | static void register_prot_hook(struct sock *sk) |
245 | { | 245 | { |
246 | struct packet_sock *po = pkt_sk(sk); | 246 | struct packet_sock *po = pkt_sk(sk); |
247 | |||
247 | if (!po->running) { | 248 | if (!po->running) { |
248 | if (po->fanout) | 249 | if (po->fanout) { |
249 | __fanout_link(sk, po); | 250 | __fanout_link(sk, po); |
250 | else | 251 | } else { |
251 | dev_add_pack(&po->prot_hook); | 252 | dev_add_pack(&po->prot_hook); |
253 | rcu_assign_pointer(po->cached_dev, po->prot_hook.dev); | ||
254 | } | ||
255 | |||
252 | sock_hold(sk); | 256 | sock_hold(sk); |
253 | po->running = 1; | 257 | po->running = 1; |
254 | } | 258 | } |
@@ -266,10 +270,13 @@ static void __unregister_prot_hook(struct sock *sk, bool sync) | |||
266 | struct packet_sock *po = pkt_sk(sk); | 270 | struct packet_sock *po = pkt_sk(sk); |
267 | 271 | ||
268 | po->running = 0; | 272 | po->running = 0; |
269 | if (po->fanout) | 273 | if (po->fanout) { |
270 | __fanout_unlink(sk, po); | 274 | __fanout_unlink(sk, po); |
271 | else | 275 | } else { |
272 | __dev_remove_pack(&po->prot_hook); | 276 | __dev_remove_pack(&po->prot_hook); |
277 | RCU_INIT_POINTER(po->cached_dev, NULL); | ||
278 | } | ||
279 | |||
273 | __sock_put(sk); | 280 | __sock_put(sk); |
274 | 281 | ||
275 | if (sync) { | 282 | if (sync) { |
@@ -2041,12 +2048,24 @@ static int tpacket_fill_skb(struct packet_sock *po, struct sk_buff *skb, | |||
2041 | return tp_len; | 2048 | return tp_len; |
2042 | } | 2049 | } |
2043 | 2050 | ||
2051 | static struct net_device *packet_cached_dev_get(struct packet_sock *po) | ||
2052 | { | ||
2053 | struct net_device *dev; | ||
2054 | |||
2055 | rcu_read_lock(); | ||
2056 | dev = rcu_dereference(po->cached_dev); | ||
2057 | if (dev) | ||
2058 | dev_hold(dev); | ||
2059 | rcu_read_unlock(); | ||
2060 | |||
2061 | return dev; | ||
2062 | } | ||
2063 | |||
2044 | static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) | 2064 | static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) |
2045 | { | 2065 | { |
2046 | struct sk_buff *skb; | 2066 | struct sk_buff *skb; |
2047 | struct net_device *dev; | 2067 | struct net_device *dev; |
2048 | __be16 proto; | 2068 | __be16 proto; |
2049 | bool need_rls_dev = false; | ||
2050 | int err, reserve = 0; | 2069 | int err, reserve = 0; |
2051 | void *ph; | 2070 | void *ph; |
2052 | struct sockaddr_ll *saddr = (struct sockaddr_ll *)msg->msg_name; | 2071 | struct sockaddr_ll *saddr = (struct sockaddr_ll *)msg->msg_name; |
@@ -2059,7 +2078,7 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) | |||
2059 | mutex_lock(&po->pg_vec_lock); | 2078 | mutex_lock(&po->pg_vec_lock); |
2060 | 2079 | ||
2061 | if (saddr == NULL) { | 2080 | if (saddr == NULL) { |
2062 | dev = po->prot_hook.dev; | 2081 | dev = packet_cached_dev_get(po); |
2063 | proto = po->num; | 2082 | proto = po->num; |
2064 | addr = NULL; | 2083 | addr = NULL; |
2065 | } else { | 2084 | } else { |
@@ -2073,19 +2092,17 @@ static int tpacket_snd(struct packet_sock *po, struct msghdr *msg) | |||
2073 | proto = saddr->sll_protocol; | 2092 | proto = saddr->sll_protocol; |
2074 | addr = saddr->sll_addr; | 2093 | addr = saddr->sll_addr; |
2075 | dev = dev_get_by_index(sock_net(&po->sk), saddr->sll_ifindex); | 2094 | dev = dev_get_by_index(sock_net(&po->sk), saddr->sll_ifindex); |
2076 | need_rls_dev = true; | ||
2077 | } | 2095 | } |
2078 | 2096 | ||
2079 | err = -ENXIO; | 2097 | err = -ENXIO; |
2080 | if (unlikely(dev == NULL)) | 2098 | if (unlikely(dev == NULL)) |
2081 | goto out; | 2099 | goto out; |
2082 | |||
2083 | reserve = dev->hard_header_len; | ||
2084 | |||
2085 | err = -ENETDOWN; | 2100 | err = -ENETDOWN; |
2086 | if (unlikely(!(dev->flags & IFF_UP))) | 2101 | if (unlikely(!(dev->flags & IFF_UP))) |
2087 | goto out_put; | 2102 | goto out_put; |
2088 | 2103 | ||
2104 | reserve = dev->hard_header_len; | ||
2105 | |||
2089 | size_max = po->tx_ring.frame_size | 2106 | size_max = po->tx_ring.frame_size |
2090 | - (po->tp_hdrlen - sizeof(struct sockaddr_ll)); | 2107 | - (po->tp_hdrlen - sizeof(struct sockaddr_ll)); |
2091 | 2108 | ||
@@ -2162,8 +2179,7 @@ out_status: | |||
2162 | __packet_set_status(po, ph, status); | 2179 | __packet_set_status(po, ph, status); |
2163 | kfree_skb(skb); | 2180 | kfree_skb(skb); |
2164 | out_put: | 2181 | out_put: |
2165 | if (need_rls_dev) | 2182 | dev_put(dev); |
2166 | dev_put(dev); | ||
2167 | out: | 2183 | out: |
2168 | mutex_unlock(&po->pg_vec_lock); | 2184 | mutex_unlock(&po->pg_vec_lock); |
2169 | return err; | 2185 | return err; |
@@ -2201,7 +2217,6 @@ static int packet_snd(struct socket *sock, | |||
2201 | struct sk_buff *skb; | 2217 | struct sk_buff *skb; |
2202 | struct net_device *dev; | 2218 | struct net_device *dev; |
2203 | __be16 proto; | 2219 | __be16 proto; |
2204 | bool need_rls_dev = false; | ||
2205 | unsigned char *addr; | 2220 | unsigned char *addr; |
2206 | int err, reserve = 0; | 2221 | int err, reserve = 0; |
2207 | struct virtio_net_hdr vnet_hdr = { 0 }; | 2222 | struct virtio_net_hdr vnet_hdr = { 0 }; |
@@ -2217,7 +2232,7 @@ static int packet_snd(struct socket *sock, | |||
2217 | */ | 2232 | */ |
2218 | 2233 | ||
2219 | if (saddr == NULL) { | 2234 | if (saddr == NULL) { |
2220 | dev = po->prot_hook.dev; | 2235 | dev = packet_cached_dev_get(po); |
2221 | proto = po->num; | 2236 | proto = po->num; |
2222 | addr = NULL; | 2237 | addr = NULL; |
2223 | } else { | 2238 | } else { |
@@ -2229,19 +2244,17 @@ static int packet_snd(struct socket *sock, | |||
2229 | proto = saddr->sll_protocol; | 2244 | proto = saddr->sll_protocol; |
2230 | addr = saddr->sll_addr; | 2245 | addr = saddr->sll_addr; |
2231 | dev = dev_get_by_index(sock_net(sk), saddr->sll_ifindex); | 2246 | dev = dev_get_by_index(sock_net(sk), saddr->sll_ifindex); |
2232 | need_rls_dev = true; | ||
2233 | } | 2247 | } |
2234 | 2248 | ||
2235 | err = -ENXIO; | 2249 | err = -ENXIO; |
2236 | if (dev == NULL) | 2250 | if (unlikely(dev == NULL)) |
2237 | goto out_unlock; | 2251 | goto out_unlock; |
2238 | if (sock->type == SOCK_RAW) | ||
2239 | reserve = dev->hard_header_len; | ||
2240 | |||
2241 | err = -ENETDOWN; | 2252 | err = -ENETDOWN; |
2242 | if (!(dev->flags & IFF_UP)) | 2253 | if (unlikely(!(dev->flags & IFF_UP))) |
2243 | goto out_unlock; | 2254 | goto out_unlock; |
2244 | 2255 | ||
2256 | if (sock->type == SOCK_RAW) | ||
2257 | reserve = dev->hard_header_len; | ||
2245 | if (po->has_vnet_hdr) { | 2258 | if (po->has_vnet_hdr) { |
2246 | vnet_hdr_len = sizeof(vnet_hdr); | 2259 | vnet_hdr_len = sizeof(vnet_hdr); |
2247 | 2260 | ||
@@ -2375,15 +2388,14 @@ static int packet_snd(struct socket *sock, | |||
2375 | if (err > 0 && (err = net_xmit_errno(err)) != 0) | 2388 | if (err > 0 && (err = net_xmit_errno(err)) != 0) |
2376 | goto out_unlock; | 2389 | goto out_unlock; |
2377 | 2390 | ||
2378 | if (need_rls_dev) | 2391 | dev_put(dev); |
2379 | dev_put(dev); | ||
2380 | 2392 | ||
2381 | return len; | 2393 | return len; |
2382 | 2394 | ||
2383 | out_free: | 2395 | out_free: |
2384 | kfree_skb(skb); | 2396 | kfree_skb(skb); |
2385 | out_unlock: | 2397 | out_unlock: |
2386 | if (dev && need_rls_dev) | 2398 | if (dev) |
2387 | dev_put(dev); | 2399 | dev_put(dev); |
2388 | out: | 2400 | out: |
2389 | return err; | 2401 | return err; |
@@ -2603,6 +2615,7 @@ static int packet_create(struct net *net, struct socket *sock, int protocol, | |||
2603 | po = pkt_sk(sk); | 2615 | po = pkt_sk(sk); |
2604 | sk->sk_family = PF_PACKET; | 2616 | sk->sk_family = PF_PACKET; |
2605 | po->num = proto; | 2617 | po->num = proto; |
2618 | RCU_INIT_POINTER(po->cached_dev, NULL); | ||
2606 | 2619 | ||
2607 | sk->sk_destruct = packet_sock_destruct; | 2620 | sk->sk_destruct = packet_sock_destruct; |
2608 | sk_refcnt_debug_inc(sk); | 2621 | sk_refcnt_debug_inc(sk); |
diff --git a/net/packet/internal.h b/net/packet/internal.h index c4e4b4561207..1035fa2d909c 100644 --- a/net/packet/internal.h +++ b/net/packet/internal.h | |||
@@ -113,6 +113,7 @@ struct packet_sock { | |||
113 | unsigned int tp_loss:1; | 113 | unsigned int tp_loss:1; |
114 | unsigned int tp_tx_has_off:1; | 114 | unsigned int tp_tx_has_off:1; |
115 | unsigned int tp_tstamp; | 115 | unsigned int tp_tstamp; |
116 | struct net_device __rcu *cached_dev; | ||
116 | struct packet_type prot_hook ____cacheline_aligned_in_smp; | 117 | struct packet_type prot_hook ____cacheline_aligned_in_smp; |
117 | }; | 118 | }; |
118 | 119 | ||