提交 9274124f 编写于 作者: W Willem de Bruijn 提交者: David S. Miller

net: stricter validation of untrusted gso packets

Syzkaller again found a path to a kernel crash through bad gso input:
a packet with transport header extending beyond skb_headlen(skb).

Tighten validation at kernel entry:

- Verify that the transport header lies within the linear section.

    To avoid pulling linux/tcp.h, verify just sizeof tcphdr.
    tcp_gso_segment will call pskb_may_pull (th->doff * 4) before use.

- Match the gso_type against the ip_proto found by the flow dissector.

Fixes: bfd5f4a3 ("packet: Add GSO/csum offload support.")
Reported-by: Nsyzbot <syzkaller@googlegroups.com>
Signed-off-by: NWillem de Bruijn <willemb@google.com>
Signed-off-by: NDavid S. Miller <davem@davemloft.net>
上级 0cb7498f
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
#define _LINUX_VIRTIO_NET_H #define _LINUX_VIRTIO_NET_H
#include <linux/if_vlan.h> #include <linux/if_vlan.h>
#include <uapi/linux/tcp.h>
#include <uapi/linux/udp.h>
#include <uapi/linux/virtio_net.h> #include <uapi/linux/virtio_net.h>
static inline int virtio_net_hdr_set_proto(struct sk_buff *skb, static inline int virtio_net_hdr_set_proto(struct sk_buff *skb,
...@@ -28,17 +30,25 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, ...@@ -28,17 +30,25 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb,
bool little_endian) bool little_endian)
{ {
unsigned int gso_type = 0; unsigned int gso_type = 0;
unsigned int thlen = 0;
unsigned int ip_proto;
if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) { if (hdr->gso_type != VIRTIO_NET_HDR_GSO_NONE) {
switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) { switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
case VIRTIO_NET_HDR_GSO_TCPV4: case VIRTIO_NET_HDR_GSO_TCPV4:
gso_type = SKB_GSO_TCPV4; gso_type = SKB_GSO_TCPV4;
ip_proto = IPPROTO_TCP;
thlen = sizeof(struct tcphdr);
break; break;
case VIRTIO_NET_HDR_GSO_TCPV6: case VIRTIO_NET_HDR_GSO_TCPV6:
gso_type = SKB_GSO_TCPV6; gso_type = SKB_GSO_TCPV6;
ip_proto = IPPROTO_TCP;
thlen = sizeof(struct tcphdr);
break; break;
case VIRTIO_NET_HDR_GSO_UDP: case VIRTIO_NET_HDR_GSO_UDP:
gso_type = SKB_GSO_UDP; gso_type = SKB_GSO_UDP;
ip_proto = IPPROTO_UDP;
thlen = sizeof(struct udphdr);
break; break;
default: default:
return -EINVAL; return -EINVAL;
...@@ -57,16 +67,22 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, ...@@ -57,16 +67,22 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb,
if (!skb_partial_csum_set(skb, start, off)) if (!skb_partial_csum_set(skb, start, off))
return -EINVAL; return -EINVAL;
if (skb_transport_offset(skb) + thlen > skb_headlen(skb))
return -EINVAL;
} else { } else {
/* gso packets without NEEDS_CSUM do not set transport_offset. /* gso packets without NEEDS_CSUM do not set transport_offset.
* probe and drop if does not match one of the above types. * probe and drop if does not match one of the above types.
*/ */
if (gso_type && skb->network_header) { if (gso_type && skb->network_header) {
struct flow_keys_basic keys;
if (!skb->protocol) if (!skb->protocol)
virtio_net_hdr_set_proto(skb, hdr); virtio_net_hdr_set_proto(skb, hdr);
retry: retry:
skb_probe_transport_header(skb); if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys,
if (!skb_transport_header_was_set(skb)) { NULL, 0, 0, 0,
0)) {
/* UFO does not specify ipv4 or 6: try both */ /* UFO does not specify ipv4 or 6: try both */
if (gso_type & SKB_GSO_UDP && if (gso_type & SKB_GSO_UDP &&
skb->protocol == htons(ETH_P_IP)) { skb->protocol == htons(ETH_P_IP)) {
...@@ -75,6 +91,12 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb, ...@@ -75,6 +91,12 @@ static inline int virtio_net_hdr_to_skb(struct sk_buff *skb,
} }
return -EINVAL; return -EINVAL;
} }
if (keys.control.thoff + thlen > skb_headlen(skb) ||
keys.basic.ip_proto != ip_proto)
return -EINVAL;
skb_set_transport_header(skb, keys.control.thoff);
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册