From: Willem de Bruijn <will...@google.com> Validate gso_type of untrusted SKB_GSO_DODGY packets during segmentation.
Untrusted user packets are limited to a small set of gso types in virtio_net_hdr_to_skb. But segmentation occurs on packet contents. Syzkaller was able to enter gso callbacks that are not hardened against untrusted user input. Fixes: f43798c27684 ("tun: Allow GSO using virtio_net_hdr") Link: http://lkml.kernel.org/r/<001a1137452496ffc305617e5...@google.com> Reported-by: syzbot+fee64147a25aecd48...@syzkaller.appspotmail.com Signed-off-by: Willem de Bruijn <will...@google.com> --- net/ipv4/af_inet.c | 21 +++++++++++++++++++++ net/ipv6/ip6_offload.c | 23 ++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/net/ipv4/af_inet.c b/net/ipv4/af_inet.c index f00499a46927..d5a36827f7b1 100644 --- a/net/ipv4/af_inet.c +++ b/net/ipv4/af_inet.c @@ -1220,6 +1220,25 @@ int inet_sk_rebuild_header(struct sock *sk) } EXPORT_SYMBOL(inet_sk_rebuild_header); +static bool inet_gso_validate_dodgy(struct sk_buff *skb, int ipproto) +{ + unsigned int gso_type = skb_shinfo(skb)->gso_type; + + if (gso_type & SKB_GSO_DODGY) { + switch (gso_type & ~SKB_GSO_DODGY) { + case SKB_GSO_TCPV4: + case SKB_GSO_TCPV4 | SKB_GSO_TCP_ECN: + return ipproto == IPPROTO_TCP; + case SKB_GSO_UDP: + return ipproto == IPPROTO_UDP; + default: + return false; + } + } + + return true; +} + struct sk_buff *inet_gso_segment(struct sk_buff *skb, netdev_features_t features) { @@ -1245,6 +1264,8 @@ struct sk_buff *inet_gso_segment(struct sk_buff *skb, id = ntohs(iph->id); proto = iph->protocol; + if (!inet_gso_validate_dodgy(skb, proto)) + goto out; /* Warning: after this point, iph might be no longer valid */ if (unlikely(!pskb_may_pull(skb, ihl))) diff --git a/net/ipv6/ip6_offload.c b/net/ipv6/ip6_offload.c index 4a87f9428ca5..662e16548104 100644 --- a/net/ipv6/ip6_offload.c +++ b/net/ipv6/ip6_offload.c @@ -55,6 +55,25 @@ static int ipv6_gso_pull_exthdrs(struct sk_buff *skb, int proto) return proto; } +static bool ipv6_gso_validate_dodgy(struct sk_buff *skb, int ipproto) +{ + unsigned int gso_type = skb_shinfo(skb)->gso_type; + + if (gso_type & SKB_GSO_DODGY) { + switch (gso_type & ~SKB_GSO_DODGY) { + case SKB_GSO_TCPV6: + case SKB_GSO_TCPV6 | SKB_GSO_TCP_ECN: + return ipproto == IPPROTO_TCP; + case SKB_GSO_UDP: + return ipproto == IPPROTO_UDP; + default: + return false; + } + } + + return true; +} + static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, netdev_features_t features) { @@ -82,10 +101,12 @@ static struct sk_buff *ipv6_gso_segment(struct sk_buff *skb, ipv6h = ipv6_hdr(skb); __skb_pull(skb, sizeof(*ipv6h)); - segs = ERR_PTR(-EPROTONOSUPPORT); proto = ipv6_gso_pull_exthdrs(skb, ipv6h->nexthdr); + if (!ipv6_gso_validate_dodgy(skb, proto)) + goto out; + segs = ERR_PTR(-EPROTONOSUPPORT); if (skb->encapsulation && skb_shinfo(skb)->gso_type & (SKB_GSO_IPXIP4 | SKB_GSO_IPXIP6)) udpfrag = proto == IPPROTO_UDP && encap; -- 2.16.0.rc1.238.g530d649a79-goog