aboutsummaryrefslogtreecommitdiffstats
path: root/net
diff options
context:
space:
mode:
authorEric Dumazet2013-04-16 07:55:41 -0500
committerGreg Kroah-Hartman2013-05-01 11:46:20 -0500
commitd1b376a21d474a14e29ae80e007591e01f08963f (patch)
tree1cff0557e5cc17e89c98c324bd790fd69644b2ea /net
parent98c287e95527ea25ff4e85d635a8bc42bcb25326 (diff)
downloadkernel-video-d1b376a21d474a14e29ae80e007591e01f08963f.tar.gz
kernel-video-d1b376a21d474a14e29ae80e007591e01f08963f.tar.xz
kernel-video-d1b376a21d474a14e29ae80e007591e01f08963f.zip
net: drop dst before queueing fragments
[ Upstream commit 97599dc792b45b1669c3cdb9a4b365aad0232f65 ] Commit 4a94445c9a5c (net: Use ip_route_input_noref() in input path) added a bug in IP defragmentation handling, as non refcounted dst could escape an RCU protected section. Commit 64f3b9e203bd068 (net: ip_expire() must revalidate route) fixed the case of timeouts, but not the general problem. Tom Parkin noticed crashes in UDP stack and provided a patch, but further analysis permitted us to pinpoint the root cause. Before queueing a packet into a frag list, we must drop its dst, as this dst has limited lifetime (RCU protected) When/if a packet is finally reassembled, we use the dst of the very last skb, still protected by RCU and valid, as the dst of the reassembled packet. Use same logic in IPv6, as there is no need to hold dst references. Reported-by: Tom Parkin <tparkin@katalix.com> Tested-by: Tom Parkin <tparkin@katalix.com> Signed-off-by: Eric Dumazet <edumazet@google.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/ipv4/ip_fragment.c15
-rw-r--r--net/ipv6/reassembly.c13
2 files changed, 22 insertions, 6 deletions
diff --git a/net/ipv4/ip_fragment.c b/net/ipv4/ip_fragment.c
index a8fc332d07f..0fcfee37227 100644
--- a/net/ipv4/ip_fragment.c
+++ b/net/ipv4/ip_fragment.c
@@ -255,8 +255,7 @@ static void ip_expire(unsigned long arg)
255 if (!head->dev) 255 if (!head->dev)
256 goto out_rcu_unlock; 256 goto out_rcu_unlock;
257 257
258 /* skb dst is stale, drop it, and perform route lookup again */ 258 /* skb has no dst, perform route lookup again */
259 skb_dst_drop(head);
260 iph = ip_hdr(head); 259 iph = ip_hdr(head);
261 err = ip_route_input_noref(head, iph->daddr, iph->saddr, 260 err = ip_route_input_noref(head, iph->daddr, iph->saddr,
262 iph->tos, head->dev); 261 iph->tos, head->dev);
@@ -525,8 +524,16 @@ found:
525 qp->q.max_size = skb->len + ihl; 524 qp->q.max_size = skb->len + ihl;
526 525
527 if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) && 526 if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
528 qp->q.meat == qp->q.len) 527 qp->q.meat == qp->q.len) {
529 return ip_frag_reasm(qp, prev, dev); 528 unsigned long orefdst = skb->_skb_refdst;
529
530 skb->_skb_refdst = 0UL;
531 err = ip_frag_reasm(qp, prev, dev);
532 skb->_skb_refdst = orefdst;
533 return err;
534 }
535
536 skb_dst_drop(skb);
530 537
531 write_lock(&ip4_frags.lock); 538 write_lock(&ip4_frags.lock);
532 list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list); 539 list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
diff --git a/net/ipv6/reassembly.c b/net/ipv6/reassembly.c
index d9ba8a27fde..7a610a67363 100644
--- a/net/ipv6/reassembly.c
+++ b/net/ipv6/reassembly.c
@@ -342,8 +342,17 @@ found:
342 } 342 }
343 343
344 if (fq->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) && 344 if (fq->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
345 fq->q.meat == fq->q.len) 345 fq->q.meat == fq->q.len) {
346 return ip6_frag_reasm(fq, prev, dev); 346 int res;
347 unsigned long orefdst = skb->_skb_refdst;
348
349 skb->_skb_refdst = 0UL;
350 res = ip6_frag_reasm(fq, prev, dev);
351 skb->_skb_refdst = orefdst;
352 return res;
353 }
354
355 skb_dst_drop(skb);
347 356
348 write_lock(&ip6_frags.lock); 357 write_lock(&ip6_frags.lock);
349 list_move_tail(&fq->q.lru_list, &fq->q.net->lru_list); 358 list_move_tail(&fq->q.lru_list, &fq->q.net->lru_list);