Skip to content

Commit

Permalink
net: drop dst before queueing fragments
Browse files Browse the repository at this point in the history
[ Upstream commit 97599dc ]

Commit 4a94445 (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 64f3b9e (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>
  • Loading branch information
Eric Dumazet authored and Greg Kroah-Hartman committed May 1, 2013
1 parent ca4bf7c commit 1d0dd1d
Showing 2 changed files with 22 additions and 6 deletions.
15 changes: 11 additions & 4 deletions net/ipv4/ip_fragment.c
Original file line number Diff line number Diff line change
@@ -251,8 +251,7 @@ static void ip_expire(unsigned long arg)
if (!head->dev)
goto out_rcu_unlock;

/* skb dst is stale, drop it, and perform route lookup again */
skb_dst_drop(head);
/* skb has no dst, perform route lookup again */
iph = ip_hdr(head);
err = ip_route_input_noref(head, iph->daddr, iph->saddr,
iph->tos, head->dev);
@@ -517,8 +516,16 @@ static int ip_frag_queue(struct ipq *qp, struct sk_buff *skb)
qp->q.last_in |= INET_FRAG_FIRST_IN;

if (qp->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
qp->q.meat == qp->q.len)
return ip_frag_reasm(qp, prev, dev);
qp->q.meat == qp->q.len) {
unsigned long orefdst = skb->_skb_refdst;

skb->_skb_refdst = 0UL;
err = ip_frag_reasm(qp, prev, dev);
skb->_skb_refdst = orefdst;
return err;
}

skb_dst_drop(skb);

write_lock(&ip4_frags.lock);
list_move_tail(&qp->q.lru_list, &qp->q.net->lru_list);
13 changes: 11 additions & 2 deletions net/ipv6/reassembly.c
Original file line number Diff line number Diff line change
@@ -385,8 +385,17 @@ static int ip6_frag_queue(struct frag_queue *fq, struct sk_buff *skb,
}

if (fq->q.last_in == (INET_FRAG_FIRST_IN | INET_FRAG_LAST_IN) &&
fq->q.meat == fq->q.len)
return ip6_frag_reasm(fq, prev, dev);
fq->q.meat == fq->q.len) {
int res;
unsigned long orefdst = skb->_skb_refdst;

skb->_skb_refdst = 0UL;
res = ip6_frag_reasm(fq, prev, dev);
skb->_skb_refdst = orefdst;
return res;
}

skb_dst_drop(skb);

write_lock(&ip6_frags.lock);
list_move_tail(&fq->q.lru_list, &fq->q.net->lru_list);

0 comments on commit 1d0dd1d

Please sign in to comment.