diff options
author | Andrea Shepard <andrea@persephoneslair.org> | 2010-07-22 05:12:35 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2010-07-22 16:25:18 -0400 |
commit | 00c5a9834b476a138158fb17d576da751727a9f1 (patch) | |
tree | a06b8b779d11784bb29f380e00c438e16c28f41a /net | |
parent | 8a35747a5d13b99e076b0222729e0caa48cb69b6 (diff) |
net: Fix corruption of skb csum field in pskb_expand_head() of net/core/skbuff.c
Make pskb_expand_head() check ip_summed to make sure csum_start is really
csum_start and not csum before adjusting it.
This fixes a bug I encountered using a Sun Quad-Fast Ethernet card and VLANs.
On my configuration, the sunhme driver produces skbs with differing amounts
of headroom on receive depending on the packet size. See line 2030 of
drivers/net/sunhme.c; packets smaller than RX_COPY_THRESHOLD have 52 bytes
of headroom but packets larger than that cutoff have only 20 bytes.
When these packets reach the VLAN driver, vlan_check_reorder_header()
calls skb_cow(), which, if the packet has less than NET_SKB_PAD (== 32) bytes
of headroom, uses pskb_expand_head() to make more.
Then, pskb_expand_head() needs to adjust a lot of offsets into the skb,
including csum_start. Since csum_start is a union with csum, if the packet
has a valid csum value this will corrupt it, which was the effect I observed.
The sunhme hardware computes receive checksums, so the skbs would be created
by the driver with ip_summed == CHECKSUM_COMPLETE and a valid csum field, and
then pskb_expand_head() would corrupt the csum field, leading to an "hw csum
error" message later on, for example in icmp_rcv() for pings larger than the
sunhme RX_COPY_THRESHOLD.
On the basis of the comment at the beginning of include/linux/skbuff.h,
I believe that the csum_start skb field is only meaningful if ip_csummed is
CSUM_PARTIAL, so this patch makes pskb_expand_head() adjust it only in that
case to avoid corrupting a valid csum value.
Please see my more in-depth disucssion of tracking down this bug for
more details if you like:
http://puellavulnerata.livejournal.com/112186.html
http://puellavulnerata.livejournal.com/112567.html
http://puellavulnerata.livejournal.com/112891.html
http://puellavulnerata.livejournal.com/113096.html
http://puellavulnerata.livejournal.com/113591.html
I am not subscribed to this list, so please CC me on replies.
Signed-off-by: Andrea Shepard <andrea@persephoneslair.org>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
-rw-r--r-- | net/core/skbuff.c | 4 |
1 files changed, 3 insertions, 1 deletions
diff --git a/net/core/skbuff.c b/net/core/skbuff.c index 34432b4e96bb..c699159b3ede 100644 --- a/net/core/skbuff.c +++ b/net/core/skbuff.c | |||
@@ -843,7 +843,9 @@ int pskb_expand_head(struct sk_buff *skb, int nhead, int ntail, | |||
843 | skb->network_header += off; | 843 | skb->network_header += off; |
844 | if (skb_mac_header_was_set(skb)) | 844 | if (skb_mac_header_was_set(skb)) |
845 | skb->mac_header += off; | 845 | skb->mac_header += off; |
846 | skb->csum_start += nhead; | 846 | /* Only adjust this if it actually is csum_start rather than csum */ |
847 | if (skb->ip_summed == CHECKSUM_PARTIAL) | ||
848 | skb->csum_start += nhead; | ||
847 | skb->cloned = 0; | 849 | skb->cloned = 0; |
848 | skb->hdr_len = 0; | 850 | skb->hdr_len = 0; |
849 | skb->nohdr = 0; | 851 | skb->nohdr = 0; |