gro: Fix use after free in tcp_gro_receive
authorHerbert Xu <herbert@gondor.apana.org.au>
Fri, 17 Apr 2009 09:34:38 +0000 (02:34 -0700)
committerDavid S. Miller <davem@davemloft.net>
Fri, 17 Apr 2009 09:34:38 +0000 (02:34 -0700)
After calling skb_gro_receive skb->len can no longer be relied
on since if the skb was merged using frags, then its pages will
have been removed and the length reduced.

This caused tcp_gro_receive to prematurely end merging which
resulted in suboptimal performance with ixgbe.

The fix is to store skb->len on the stack.

Reported-by: Mark Wagner <mwagner@redhat.com>
Signed-off-by: Herbert Xu <herbert@gondor.apana.org.au>
Signed-off-by: David S. Miller <davem@davemloft.net>
net/ipv4/tcp.c

index fafbec8..1d7f49c 100644 (file)
@@ -2511,6 +2511,7 @@ struct sk_buff **tcp_gro_receive(struct sk_buff **head, struct sk_buff *skb)
        struct sk_buff *p;
        struct tcphdr *th;
        struct tcphdr *th2;
        struct sk_buff *p;
        struct tcphdr *th;
        struct tcphdr *th2;
+       unsigned int len;
        unsigned int thlen;
        unsigned int flags;
        unsigned int mss = 1;
        unsigned int thlen;
        unsigned int flags;
        unsigned int mss = 1;
@@ -2531,6 +2532,7 @@ struct sk_buff **tcp_gro_receive(struct sk_buff **head, struct sk_buff *skb)
 
        skb_gro_pull(skb, thlen);
 
 
        skb_gro_pull(skb, thlen);
 
+       len = skb_gro_len(skb);
        flags = tcp_flag_word(th);
 
        for (; (p = *head); head = &p->next) {
        flags = tcp_flag_word(th);
 
        for (; (p = *head); head = &p->next) {
@@ -2561,7 +2563,7 @@ found:
 
        mss = skb_shinfo(p)->gso_size;
 
 
        mss = skb_shinfo(p)->gso_size;
 
-       flush |= (skb_gro_len(skb) > mss) | !skb_gro_len(skb);
+       flush |= (len > mss) | !len;
        flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq);
 
        if (flush || skb_gro_receive(head, skb)) {
        flush |= (ntohl(th2->seq) + skb_gro_len(p)) ^ ntohl(th->seq);
 
        if (flush || skb_gro_receive(head, skb)) {
@@ -2574,7 +2576,7 @@ found:
        tcp_flag_word(th2) |= flags & (TCP_FLAG_FIN | TCP_FLAG_PSH);
 
 out_check_final:
        tcp_flag_word(th2) |= flags & (TCP_FLAG_FIN | TCP_FLAG_PSH);
 
 out_check_final:
-       flush = skb_gro_len(skb) < mss;
+       flush = len < mss;
        flush |= flags & (TCP_FLAG_URG | TCP_FLAG_PSH | TCP_FLAG_RST |
                          TCP_FLAG_SYN | TCP_FLAG_FIN);
 
        flush |= flags & (TCP_FLAG_URG | TCP_FLAG_PSH | TCP_FLAG_RST |
                          TCP_FLAG_SYN | TCP_FLAG_FIN);