Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

ipv6: udp packets following an UFO enqueued packet need also be handled by UFO

In the following scenario the socket is corked:
If the first UDP packet is larger then the mtu we try to append it to the
write queue via ip6_ufo_append_data. A following packet, which is smaller
than the mtu would be appended to the already queued up gso-skb via
plain ip6_append_data. This causes random memory corruptions.

In ip6_ufo_append_data we also have to be careful to not queue up the
same skb multiple times. So setup the gso frame only when no first skb
is available.

This also fixes a shortcoming where we add the current packet's length to
cork->length but return early because of a packet > mtu with dontfrag set
(instead of sutracting it again).

Found with trinity.

Cc: YOSHIFUJI Hideaki <yoshfuji@linux-ipv6.org>
Signed-off-by: Hannes Frederic Sowa <hannes@stressinduktion.org>
Reported-by: Dmitry Vyukov <dvyukov@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>

authored by

Hannes Frederic Sowa and committed by
David S. Miller
2811ebac 3db91802

+23 -32
+23 -32
net/ipv6/ip6_output.c
··· 1015 1015 * udp datagram 1016 1016 */ 1017 1017 if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL) { 1018 + struct frag_hdr fhdr; 1019 + 1018 1020 skb = sock_alloc_send_skb(sk, 1019 1021 hh_len + fragheaderlen + transhdrlen + 20, 1020 1022 (flags & MSG_DONTWAIT), &err); ··· 1038 1036 skb->protocol = htons(ETH_P_IPV6); 1039 1037 skb->ip_summed = CHECKSUM_PARTIAL; 1040 1038 skb->csum = 0; 1041 - } 1042 - 1043 - err = skb_append_datato_frags(sk,skb, getfrag, from, 1044 - (length - transhdrlen)); 1045 - if (!err) { 1046 - struct frag_hdr fhdr; 1047 1039 1048 1040 /* Specify the length of each IPv6 datagram fragment. 1049 1041 * It has to be a multiple of 8. ··· 1048 1052 ipv6_select_ident(&fhdr, rt); 1049 1053 skb_shinfo(skb)->ip6_frag_id = fhdr.identification; 1050 1054 __skb_queue_tail(&sk->sk_write_queue, skb); 1051 - 1052 - return 0; 1053 1055 } 1054 - /* There is not enough support do UPD LSO, 1055 - * so follow normal path 1056 - */ 1057 - kfree_skb(skb); 1058 1056 1059 - return err; 1057 + return skb_append_datato_frags(sk, skb, getfrag, from, 1058 + (length - transhdrlen)); 1060 1059 } 1061 1060 1062 1061 static inline struct ipv6_opt_hdr *ip6_opt_dup(struct ipv6_opt_hdr *src, ··· 1218 1227 * --yoshfuji 1219 1228 */ 1220 1229 1221 - cork->length += length; 1222 - if (length > mtu) { 1223 - int proto = sk->sk_protocol; 1224 - if (dontfrag && (proto == IPPROTO_UDP || proto == IPPROTO_RAW)){ 1225 - ipv6_local_rxpmtu(sk, fl6, mtu-exthdrlen); 1226 - return -EMSGSIZE; 1227 - } 1228 - 1229 - if (proto == IPPROTO_UDP && 1230 - (rt->dst.dev->features & NETIF_F_UFO)) { 1231 - 1232 - err = ip6_ufo_append_data(sk, getfrag, from, length, 1233 - hh_len, fragheaderlen, 1234 - transhdrlen, mtu, flags, rt); 1235 - if (err) 1236 - goto error; 1237 - return 0; 1238 - } 1230 + if ((length > mtu) && dontfrag && (sk->sk_protocol == IPPROTO_UDP || 1231 + sk->sk_protocol == IPPROTO_RAW)) { 1232 + ipv6_local_rxpmtu(sk, fl6, mtu-exthdrlen); 1233 + return -EMSGSIZE; 1239 1234 } 1240 1235 1241 - if ((skb = skb_peek_tail(&sk->sk_write_queue)) == NULL) 1236 + skb = skb_peek_tail(&sk->sk_write_queue); 1237 + cork->length += length; 1238 + if (((length > mtu) || 1239 + (skb && skb_is_gso(skb))) && 1240 + (sk->sk_protocol == IPPROTO_UDP) && 1241 + (rt->dst.dev->features & NETIF_F_UFO)) { 1242 + err = ip6_ufo_append_data(sk, getfrag, from, length, 1243 + hh_len, fragheaderlen, 1244 + transhdrlen, mtu, flags, rt); 1245 + if (err) 1246 + goto error; 1247 + return 0; 1248 + } 1249 + 1250 + if (!skb) 1242 1251 goto alloc_new_skb; 1243 1252 1244 1253 while (length > 0) {