History

net: fix possible wrong checksum generation

Pravin Shelar mentioned that GSO could potentially generate wrong TX checksum if skb has fragments that are overwritten by the user between the checksum computation and transmit. He suggested to linearize skbs but this extra copy can be avoided for normal tcp skbs cooked by tcp_sendmsg(). This patch introduces a new SKB_GSO_SHARED_FRAG flag, set in skb_shinfo(skb)->gso_type if at least one frag can be modified by the user. Typical sources of such possible overwrites are {vm}splice(), sendfile(), and macvtap/tun/virtio_net drivers. Tested: $ netperf -H 7.7.8.84 MIGRATED TCP STREAM TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 7.7.8.84 () port 0 AF_INET Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 87380 16384 16384 10.00 3959.52 $ netperf -H 7.7.8.84 -t TCP_SENDFILE TCP SENDFILE TEST from 0.0.0.0 (0.0.0.0) port 0 AF_INET to 7.7.8.84 () port 0 AF_INET Recv Send Send Socket Socket Message Elapsed Size Size Size Time Throughput bytes bytes bytes secs. 10^6bits/sec 87380 16384 16384 10.00 3216.80 Performance of the SENDFILE is impacted by the extra allocation and copy, and because we use order-0 pages, while the TCP_STREAM uses bigger pages.

Eric Dumazet committed 13 years ago

Commit cef401d1 parent 615500213 files · +65 −15Browse files

Unified · Split

13 files
  1. drivers/net/macvtap.c +2 −1
  2. drivers/net/tun.c +8 −4
  3. drivers/net/virtio_net.c +8 −4
  4. include/linux/skbuff.h +19 −0
  5. net/core/dev.c +9 −0
  6. net/core/skbuff.c +4 −0
  7. net/ipv4/af_inet.c +1 −0
  8. net/ipv4/ip_gre.c +3 −1
  9. net/ipv4/ipip.c +3 −1
  10. net/ipv4/tcp.c +3 −0
  11. net/ipv4/tcp_input.c +2 −2
  12. net/ipv4/tcp_output.c +2 −2
  13. net/ipv6/ip6_offload.c +1 −0
drivers/net/macvtap.c+2−1
@@ -543,6 +543,7 @@ static int zerocopy_sg_from_iovec(struct sk_buff *skb, const struct iovec *from, · 542 unmodified lines
543543 skb->data_len += len;
544544 skb->len += len;
545545 skb->truesize += truesize;
546+ skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
546547 atomic_add(truesize, &skb->sk->sk_wmem_alloc);
547548 while (len) {
548549 int off = base & ~PAGE_MASK;
@@ -598,7 +599,7 @@ static int macvtap_skb_from_vnet_hdr(struct sk_buff *skb, · 49 unmodified lines
598599
599600 if (vnet_hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
600601 skb_shinfo(skb)->gso_size = vnet_hdr->gso_size;
601− skb_shinfo(skb)->gso_type = gso_type;
602+ skb_shinfo(skb)->gso_type |= gso_type;
602603
603604 /* Header must be checked, and gso_segs computed. */
604605 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;
drivers/net/tun.c+8−4
@@ -1005,6 +1005,7 @@ static int zerocopy_sg_from_iovec(struct sk_buff *skb, const struct iovec *from, · 1004 unmodified lines
10051005 skb->data_len += len;
10061006 skb->len += len;
10071007 skb->truesize += truesize;
1008+ skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
10081009 atomic_add(truesize, &skb->sk->sk_wmem_alloc);
10091010 while (len) {
10101011 int off = base & ~PAGE_MASK;
@@ -1150,16 +1151,18 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, · 139 unmodified lines
11501151 }
11511152
11521153 if (gso.gso_type != VIRTIO_NET_HDR_GSO_NONE) {
1154+ unsigned short gso_type = 0;
1155+
11531156 pr_debug("GSO!\n");
11541157 switch (gso.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
11551158 case VIRTIO_NET_HDR_GSO_TCPV4:
1156− skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
1159+ gso_type = SKB_GSO_TCPV4;
11571160 break;
11581161 case VIRTIO_NET_HDR_GSO_TCPV6:
1159− skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
1162+ gso_type = SKB_GSO_TCPV6;
11601163 break;
11611164 case VIRTIO_NET_HDR_GSO_UDP:
1162− skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
1165+ gso_type = SKB_GSO_UDP;
11631166 break;
11641167 default:
11651168 tun->dev->stats.rx_frame_errors++;
@@ -1168,9 +1171,10 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, · 2 unmodified lines
11681171 }
11691172
11701173 if (gso.gso_type & VIRTIO_NET_HDR_GSO_ECN)
1171− skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN;
1174+ gso_type |= SKB_GSO_TCP_ECN;
11721175
11731176 skb_shinfo(skb)->gso_size = gso.gso_size;
1177+ skb_shinfo(skb)->gso_type |= gso_type;
11741178 if (skb_shinfo(skb)->gso_size == 0) {
11751179 tun->dev->stats.rx_frame_errors++;
11761180 kfree_skb(skb);
drivers/net/virtio_net.c+8−4
@@ -220,6 +220,7 @@ static void set_skb_frag(struct sk_buff *skb, struct page *page, · 219 unmodified lines
220220 skb->len += size;
221221 skb->truesize += PAGE_SIZE;
222222 skb_shinfo(skb)->nr_frags++;
223+ skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
223224 *len -= size;
224225 }
225226
@@ -379,16 +380,18 @@ static void receive_buf(struct receive_queue *rq, void *buf, unsigned int len) · 153 unmodified lines
379380 ntohs(skb->protocol), skb->len, skb->pkt_type);
380381
381382 if (hdr->hdr.gso_type != VIRTIO_NET_HDR_GSO_NONE) {
383+ unsigned short gso_type = 0;
384+
382385 pr_debug("GSO!\n");
383386 switch (hdr->hdr.gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
384387 case VIRTIO_NET_HDR_GSO_TCPV4:
385− skb_shinfo(skb)->gso_type = SKB_GSO_TCPV4;
388+ gso_type = SKB_GSO_TCPV4;
386389 break;
387390 case VIRTIO_NET_HDR_GSO_UDP:
388− skb_shinfo(skb)->gso_type = SKB_GSO_UDP;
391+ gso_type = SKB_GSO_UDP;
389392 break;
390393 case VIRTIO_NET_HDR_GSO_TCPV6:
391− skb_shinfo(skb)->gso_type = SKB_GSO_TCPV6;
394+ gso_type = SKB_GSO_TCPV6;
392395 break;
393396 default:
394397 net_warn_ratelimited("%s: bad gso type %u.\n",
@@ -397,7 +400,7 @@ static void receive_buf(struct receive_queue *rq, void *buf, unsigned int len) · 2 unmodified lines
397400 }
398401
399402 if (hdr->hdr.gso_type & VIRTIO_NET_HDR_GSO_ECN)
400− skb_shinfo(skb)->gso_type |= SKB_GSO_TCP_ECN;
403+ gso_type |= SKB_GSO_TCP_ECN;
401404
402405 skb_shinfo(skb)->gso_size = hdr->hdr.gso_size;
403406 if (skb_shinfo(skb)->gso_size == 0) {
@@ -405,6 +408,7 @@ static void receive_buf(struct receive_queue *rq, void *buf, unsigned int len) · 1 unmodified line
405408 goto frame_err;
406409 }
407410
411+ skb_shinfo(skb)->gso_type |= gso_type;
408412 /* Header must be checked, and gso_segs computed. */
409413 skb_shinfo(skb)->gso_type |= SKB_GSO_DODGY;
410414 skb_shinfo(skb)->gso_segs = 0;
include/linux/skbuff.h+19−0
@@ -307,6 +307,13 @@ enum { · 306 unmodified lines
307307 SKB_GSO_TCPV6 = 1 << 4,
308308
309309 SKB_GSO_FCOE = 1 << 5,
310+
311+ /* This indicates at least one fragment might be overwritten
312+ * (as in vmsplice(), sendfile() ...)
313+ * If we need to compute a TX checksum, we'll need to copy
314+ * all frags to avoid possible bad checksum
315+ */
316+ SKB_GSO_SHARED_FRAG = 1 << 6,
310317 };
311318
312319 #if BITS_PER_LONG > 32
@@ -2200,6 +2207,18 @@ static inline int skb_linearize(struct sk_buff *skb) · 1887 unmodified lines
22002207 return skb_is_nonlinear(skb) ? __skb_linearize(skb) : 0;
22012208 }
22022209
2210+/**
2211+ * skb_has_shared_frag - can any frag be overwritten
2212+ * @skb: buffer to test
2213+ *
2214+ * Return true if the skb has at least one frag that might be modified
2215+ * by an external entity (as in vmsplice()/sendfile())
2216+ */
2217+static inline bool skb_has_shared_frag(const struct sk_buff *skb)
2218+{
2219+ return skb_shinfo(skb)->gso_type & SKB_GSO_SHARED_FRAG;
2220+}
2221+
22032222 /**
22042223 * skb_linearize_cow - make sure skb is linear and writable
22052224 * @skb: buffer to process
net/core/dev.c+9−0
@@ -2271,6 +2271,15 @@ int skb_checksum_help(struct sk_buff *skb) · 2270 unmodified lines
22712271 return -EINVAL;
22722272 }
22732273
2274+ /* Before computing a checksum, we should make sure no frag could
2275+ * be modified by an external entity : checksum could be wrong.
2276+ */
2277+ if (skb_has_shared_frag(skb)) {
2278+ ret = __skb_linearize(skb);
2279+ if (ret)
2280+ goto out;
2281+ }
2282+
22742283 offset = skb_checksum_start_offset(skb);
22752284 BUG_ON(offset >= skb_headlen(skb));
22762285 csum = skb_checksum(skb, offset, skb->len - offset, 0);
net/core/skbuff.c+4−0
@@ -2340,6 +2340,8 @@ void skb_split(struct sk_buff *skb, struct sk_buff *skb1, const u32 len) · 2339 unmodified lines
23402340 {
23412341 int pos = skb_headlen(skb);
23422342
2343+ skb_shinfo(skb1)->gso_type = skb_shinfo(skb)->gso_type;
2344+
23432345 if (len < pos) /* Split line is inside header. */
23442346 skb_split_inside_header(skb, skb1, len, pos);
23452347 else /* Second chunk has no header, nothing to copy. */
@@ -2845,6 +2847,8 @@ struct sk_buff *skb_segment(struct sk_buff *skb, netdev_features_t features) · 499 unmodified lines
28452847 skb_copy_from_linear_data_offset(skb, offset,
28462848 skb_put(nskb, hsize), hsize);
28472849
2850+ skb_shinfo(nskb)->gso_type = skb_shinfo(skb)->gso_type;
2851+
28482852 while (pos < offset + len && i < nfrags) {
28492853 *frag = skb_shinfo(skb)->frags[i];
28502854 __skb_frag_ref(frag);
net/ipv4/af_inet.c+1−0
@@ -1306,6 +1306,7 @@ static struct sk_buff *inet_gso_segment(struct sk_buff *skb, · 1305 unmodified lines
13061306 SKB_GSO_UDP |
13071307 SKB_GSO_DODGY |
13081308 SKB_GSO_TCP_ECN |
1309+ SKB_GSO_SHARED_FRAG |
13091310 0)))
13101311 goto out;
13111312
net/ipv4/ip_gre.c+3−1
@@ -738,7 +738,7 @@ drop: · 737 unmodified lines
738738 static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev)
739739 {
740740 struct ip_tunnel *tunnel = netdev_priv(dev);
741− const struct iphdr *old_iph = ip_hdr(skb);
741+ const struct iphdr *old_iph;
742742 const struct iphdr *tiph;
743743 struct flowi4 fl4;
744744 u8 tos;
@@ -756,6 +756,8 @@ static netdev_tx_t ipgre_tunnel_xmit(struct sk_buff *skb, struct net_device *dev · 11 unmodified lines
756756 skb_checksum_help(skb))
757757 goto tx_error;
758758
759+ old_iph = ip_hdr(skb);
760+
759761 if (dev->type == ARPHRD_ETHER)
760762 IPCB(skb)->flags = 0;
761763
net/ipv4/ipip.c+3−1
@@ -472,7 +472,7 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) · 471 unmodified lines
472472 __be16 df = tiph->frag_off;
473473 struct rtable *rt; /* Route to the other host */
474474 struct net_device *tdev; /* Device to other host */
475− const struct iphdr *old_iph = ip_hdr(skb);
475+ const struct iphdr *old_iph;
476476 struct iphdr *iph; /* Our new IP header */
477477 unsigned int max_headroom; /* The extra header space needed */
478478 __be32 dst = tiph->daddr;
@@ -486,6 +486,8 @@ static netdev_tx_t ipip_tunnel_xmit(struct sk_buff *skb, struct net_device *dev) · 7 unmodified lines
486486 skb_checksum_help(skb))
487487 goto tx_error;
488488
489+ old_iph = ip_hdr(skb);
490+
489491 if (tos & 1)
490492 tos = old_iph->tos;
491493
net/ipv4/tcp.c+3−0
@@ -896,6 +896,8 @@ new_segment: · 895 unmodified lines
896896 skb_fill_page_desc(skb, i, page, offset, copy);
897897 }
898898
899+ skb_shinfo(skb)->gso_type |= SKB_GSO_SHARED_FRAG;
900+
899901 skb->len += copy;
900902 skb->data_len += copy;
901903 skb->truesize += copy;
@@ -3032,6 +3034,7 @@ struct sk_buff *tcp_tso_segment(struct sk_buff *skb, · 2130 unmodified lines
30323034 SKB_GSO_DODGY |
30333035 SKB_GSO_TCP_ECN |
30343036 SKB_GSO_TCPV6 |
3037+ SKB_GSO_SHARED_FRAG |
30353038 0) ||
30363039 !(type & (SKB_GSO_TCPV4 | SKB_GSO_TCPV6))))
30373040 goto out;
net/ipv4/tcp_input.c+2−2
@@ -1240,13 +1240,13 @@ static bool tcp_shifted_skb(struct sock *sk, struct sk_buff *skb, · 1239 unmodified lines
12401240 */
12411241 if (!skb_shinfo(prev)->gso_size) {
12421242 skb_shinfo(prev)->gso_size = mss;
1243− skb_shinfo(prev)->gso_type = sk->sk_gso_type;
1243+ skb_shinfo(prev)->gso_type |= sk->sk_gso_type;
12441244 }
12451245
12461246 /* CHECKME: To clear or not to clear? Mimics normal skb currently */
12471247 if (skb_shinfo(skb)->gso_segs <= 1) {
12481248 skb_shinfo(skb)->gso_size = 0;
1249− skb_shinfo(skb)->gso_type = 0;
1249+ skb_shinfo(skb)->gso_type &= SKB_GSO_SHARED_FRAG;
12501250 }
12511251
12521252 /* Difference in this won't matter, both ACKed by the same cumul. ACK */
net/ipv4/tcp_output.c+2−2
@@ -1133,6 +1133,7 @@ static void tcp_queue_skb(struct sock *sk, struct sk_buff *skb) · 1132 unmodified lines
11331133 static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb,
11341134 unsigned int mss_now)
11351135 {
1136+ skb_shinfo(skb)->gso_type &= SKB_GSO_SHARED_FRAG;
11361137 if (skb->len <= mss_now || !sk_can_gso(sk) ||
11371138 skb->ip_summed == CHECKSUM_NONE) {
11381139 /* Avoid the costly divide in the normal
@@ -1140,11 +1141,10 @@ static void tcp_set_skb_tso_segs(const struct sock *sk, struct sk_buff *skb, · 1 unmodified line
11401141 */
11411142 skb_shinfo(skb)->gso_segs = 1;
11421143 skb_shinfo(skb)->gso_size = 0;
1143− skb_shinfo(skb)->gso_type = 0;
11441144 } else {
11451145 skb_shinfo(skb)->gso_segs = DIV_ROUND_UP(skb->len, mss_now);
11461146 skb_shinfo(skb)->gso_size = mss_now;
1147− skb_shinfo(skb)->gso_type = sk->sk_gso_type;
1147+ skb_shinfo(skb)->gso_type |= sk->sk_gso_type;
11481148 }
11491149 }
11501150
net/ipv6/ip6_offload.c+1−0
@@ -100,6 +100,7 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, · 99 unmodified lines
100100 SKB_GSO_DODGY |
101101 SKB_GSO_TCP_ECN |
102102 SKB_GSO_TCPV6 |
103+ SKB_GSO_SHARED_FRAG |
103104 0)))
104105 goto out;
105106

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t