udp: consistently apply ufo or fragmentation
authorWillem de Bruijn <willemb@google.com>
Thu, 10 Aug 2017 16:29:19 +0000 (12:29 -0400)
committerWilly Tarreau <w@1wt.eu>
Wed, 1 Nov 2017 21:12:41 +0000 (22:12 +0100)
commit 85f1bd9a7b5a79d5baa8bf44af19658f7bf77bfa upstream.

When iteratively building a UDP datagram with MSG_MORE and that
datagram exceeds MTU, consistently choose UFO or fragmentation.

Once skb_is_gso, always apply ufo. Conversely, once a datagram is
split across multiple skbs, do not consider ufo.

Sendpage already maintains the first invariant, only add the second.
IPv6 does not have a sendpage implementation to modify.

A gso skb must have a partial checksum, do not follow sk_no_check_tx
in udp_send_skb.

Found by syzkaller.

[gregkh - tweaks for 3.18 for ipv6, hopefully they are correct...]
[wt: s/skb_is_gso/skb_has_frags for 3.10]

Fixes: e89e9cf539a2 ("[IPv4/IPv6]: UFO Scatter-gather approach")
Reported-by: Andrey Konovalov <andreyknvl@google.com>
Signed-off-by: Willem de Bruijn <willemb@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: Willy Tarreau <w@1wt.eu>
net/ipv4/ip_output.c
net/ipv4/udp.c
net/ipv6/ip6_output.c

index 5f077efad29d08e1da8b982b6c62d7a29c21743f..40faf48cb10c14303931fd146f4e3e60090ae078 100644 (file)
@@ -846,10 +846,12 @@ static int __ip_append_data(struct sock *sk,
                csummode = CHECKSUM_PARTIAL;
 
        cork->length += length;
-       if (((length > mtu) || (skb && skb_has_frags(skb))) &&
+       if ((skb && skb_has_frags(skb)) ||
+           ((length > mtu) &&
+           (skb_queue_len(queue) <= 1) &&
            (sk->sk_protocol == IPPROTO_UDP) &&
            (rt->dst.dev->features & NETIF_F_UFO) && !rt->dst.header_len &&
-           (sk->sk_type == SOCK_DGRAM)) {
+           (sk->sk_type == SOCK_DGRAM))) {
                err = ip_ufo_append_data(sk, queue, getfrag, from, length,
                                         hh_len, fragheaderlen, transhdrlen,
                                         maxfraglen, flags);
@@ -1160,6 +1162,7 @@ ssize_t   ip_append_page(struct sock *sk, struct flowi4 *fl4, struct page *page,
 
        cork->length += size;
        if ((size + skb->len > mtu) &&
+           (skb_queue_len(&sk->sk_write_queue) == 1) &&
            (sk->sk_protocol == IPPROTO_UDP) &&
            (rt->dst.dev->features & NETIF_F_UFO)) {
                skb_shinfo(skb)->gso_size = mtu - fragheaderlen;
index 68174e4d88c798b3983c2c40c20f8d0b09577686..882b23e8e77735e5741ab104a658395507561056 100644 (file)
@@ -763,7 +763,7 @@ static int udp_send_skb(struct sk_buff *skb, struct flowi4 *fl4)
        if (is_udplite)                                  /*     UDP-Lite      */
                csum = udplite_csum(skb);
 
-       else if (sk->sk_no_check == UDP_CSUM_NOXMIT) {   /* UDP csum disabled */
+       else if (sk->sk_no_check == UDP_CSUM_NOXMIT && !skb_has_frags(skb)) {   /* UDP csum off */
 
                skb->ip_summed = CHECKSUM_NONE;
                goto send;
index 17a88ebcc8450b2a25aaa1201da0677a78f4ff19..3a65b9a9cb4d3691a680bd2055cfc513944102f0 100644 (file)
@@ -1288,11 +1288,12 @@ int ip6_append_data(struct sock *sk, int getfrag(void *from, char *to,
 
        skb = skb_peek_tail(&sk->sk_write_queue);
        cork->length += length;
-       if (((length > mtu) ||
-            (skb && skb_has_frags(skb))) &&
+       if ((skb && skb_has_frags(skb)) ||
+           (((length + fragheaderlen) > mtu) &&
+           (skb_queue_len(&sk->sk_write_queue) <= 1) &&
            (sk->sk_protocol == IPPROTO_UDP) &&
            (rt->dst.dev->features & NETIF_F_UFO) &&
-           (sk->sk_type == SOCK_DGRAM)) {
+           (sk->sk_type == SOCK_DGRAM))) {
                err = ip6_ufo_append_data(sk, getfrag, from, length,
                                          hh_len, fragheaderlen,
                                          transhdrlen, mtu, flags, rt);