All of lore.kernel.org
 help / color / mirror / Atom feed
From: Willem de Bruijn <willemdebruijn.kernel@gmail.com>
To: Paulos Yibelo <habte.yibelo@gmail.com>,  netdev@vger.kernel.org
Cc: richard@nod.at,  anton.ivanov@cambridgegreys.com,
	 johannes@sipsolutions.net,  willemdebruijn.kernel@gmail.com,
	 jasowangio@gmail.com,  mst@redhat.com,  eperezma@redhat.com,
	 xuanzhuo@linux.alibaba.com,  andrew+netdev@lunn.ch,
	 pablo@netfilter.org,  fw@strlen.de,  phil@nwl.cc,
	 razor@blackwall.org,  idosch@nvidia.com,  dsahern@kernel.org,
	 davem@davemloft.net,  edumazet@google.com,  kuba@kernel.org,
	 pabeni@redhat.com,  horms@kernel.org,
	 linux-um@lists.infradead.org,  virtualization@lists.linux.dev,
	 netfilter-devel@vger.kernel.org,  coreteam@netfilter.org,
	 bridge@lists.linux.dev,  linux-kernel@vger.kernel.org
Subject: Re: [PATCH net v6 1/2] net: validate virtio checksum start after network header
Date: Tue, 22 Sep 2026 18:29:49 -0400	[thread overview]
Message-ID: <willemdebruijn.kernel.259bc7096258d@gmail.com> (raw)
In-Reply-To: <20260922030310.8684-2-habte.yibelo@gmail.com>

Paulos Yibelo wrote:
> __virtio_net_hdr_to_skb() checks a minimum network-header length for
> CHECKSUM_PARTIAL packets. Its checksum start is relative to skb->data,
> but some callers have not established skb->network_header when they
> convert the virtio header.
> 
> Pass the data-relative L3 origin explicitly. Ethernet receive paths
> parse the frame and nested VLAN headers without changing skb state.
> AF_PACKET uses the frame's actual L3 origin even when the socket
> protocol is ETH_P_IP and the raw frame carries VLAN tags. Non-Ethernet
> AF_PACKET devices retain their established skb network offset.
> 
> Also pass the actual L3 protocol so IPv6 packets use the 40-byte base
> header minimum even without TCPv6 GSO. IFF_TUN obtains that protocol
> from the packet before skb->protocol is set. Name the Ethernet parser
> accordingly, use the same origin for tunnel validation, and propagate
> conversion failures in UML.
> 
> The bound remains a minimum; fragmentation paths separately validate
> the parsed IPv4 or IPv6 header length before completing a checksum.
> 
> Fixes: 49d14b54a527 ("net: test for not too small csum_start in virtio_net_hdr_to_skb()")
> Fixes: a2fb4bc4e2a6 ("net: implement virtio helpers to handle UDP GSO tunneling.")
> Reported-by: Paulos Yibelo <habte.yibelo@gmail.com>
> Link: https://lore.kernel.org/netdev/20260920004733.6473-2-habte.yibelo@gmail.com/
> Cc: stable@vger.kernel.org
> Assisted-by: LLM
> Signed-off-by: Paulos Yibelo <habte.yibelo@gmail.com>
> ---
>  arch/um/drivers/vector_transports.c | 13 ++++-
>  drivers/net/tun_vnet.h              | 52 ++++++++++++++++-
>  drivers/net/virtio_net.c            | 10 +++-
>  include/linux/virtio_net.h          | 87 ++++++++++++++++++++++++-----
>  net/packet/af_packet.c              | 24 +++++++-
>  5 files changed, 163 insertions(+), 23 deletions(-)

The fix may still miss the case IPv4 packets have options.

This version is a very large patch.

Untested shorter first suggestion by bot, which looks plausible as a
starting point for discussion.

    --- a/drivers/net/tun_vnet.h
    +++ b/drivers/net/tun_vnet.h
    @@ -152,6 +152,9 @@ static inline int tun_vnet_hdr_to_skb(unsigned int flags, struct sk_buff *skb,
                           const struct virtio_net_hdr *hdr)
     {
    +    if ((flags & TUN_TYPE_MASK) == IFF_TUN)
    +        skb_reset_network_header(skb);
    +
         return virtio_net_hdr_to_skb(skb, hdr, tun_is_little_endian(flags));
     }

    --- a/include/linux/virtio_net.h
    +++ b/include/linux/virtio_net.h
    @@ -71,9 +71,27 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb,
             if (!pskb_may_pull(skb, needed))
                 return -EINVAL;
     
    +        if (!skb_network_header_was_set(skb) ||
    +            (skb->dev && skb->dev->type == ARPHRD_ETHER)) {
    +            int nhoff = ETH_HLEN;
    +
    +            if (unlikely(start < ETH_HLEN + nh_min_len))
    +                return -EINVAL;
    +            __vlan_get_protocol(skb, eth_hdr(skb)->h_proto, &nhoff);
    +            nh_min_len += nhoff;
    +        } else {
    +            nh_min_len += skb_network_offset(skb);
    +        }
    +        if (unlikely(start < nh_min_len))
    +            return -EINVAL;
    +
    +        const struct iphdr *iph = (void *)(skb->data + nh_min_len - sizeof(*iph));
    +        if (iph->version == 4)
    +            nh_min_len += max_t(u32, iph->ihl * 4, sizeof(*iph)) - sizeof(*iph);
    +        else if (iph->version == 6)
                 /* ..here I don't trust the initial bug output, but the branch is clear.. */
    +        if (unlikely(start < nh_min_len))
    +            return -EINVAL;
    +
             if (!skb_partial_csum_set(skb, start, off))
                 return -EINVAL;
    -        if (skb_transport_offset(skb) < nh_min_len)
    -            return -EINVAL;
         }

    --- a/net/ipv4/ip_output.c
    +++ b/net/ipv4/ip_output.c
    @@ -772,7 +772,9 @@ int ip_do_fragment(struct net *net, struct sock *sk, struct sk_buff *skb,
         if (skb->ip_summed == CHECKSUM_PARTIAL &&
             (!(dev->features & NETIF_F_CSUM_MASK) ||
    -         skb_checksum_help(skb)))
    +         skb_checksum_start_offset(skb) < (int)(iph->ihl * 4) ||
    +         skb_checksum_help(skb)))
             goto fail;
  
And the summary of the problem:

  ### 1. What the Problem Is

  When userspace (AF_PACKET, TUN/TAP, vhost-net) or a device (virtio_net, UML) injects a CHECKSUM_PARTIAL packet using struct virtio_net_hdr,
  virtio_net.h:47-119 validates hdr->csum_start:

    // include/linux/virtio_net.h:107 (added by commit 49d14b54a527)
    if (skb_transport_offset(skb) < nh_min_len)
        return -EINVAL;

  This validation has three bugs:

  1. Missing L2 + VLAN offset: skb_transport_offset(skb) (hdr->csum_start) is an offset from skb->data, which points to the L2 header (14
  bytes for Ethernet + 4 * n bytes for 802.1Q/802.1ad VLAN tags) on Ethernet callers (virtio_net, IFF_TAP, PACKET_SOCK_DGRAM/SOCK_RAW).
  Comparing csum_start < 20 allows csum_start to land inside the L2 VLAN tags (14..21) or inside the L3 header (22..33).
  2. Wrong nh_min_len for IPv6 non-GSO: nh_min_len defaults to sizeof(struct iphdr) (20) and is only raised to sizeof(struct ipv6hdr) (40)
  when gso_type is VIRTIO_NET_HDR_GSO_TCPV6. A non-GSO or GSO_UDP_L4 IPv6 packet is only checked against 20 bytes.
  3. Ignores IPv4 options (iph->ihl > 5): Even without an L2 header (IFF_TUN), an IPv4 header with options can be up to 60 bytes (ihl = 15),
  allowing csum_start = 20 to point 40 bytes inside the IPv4 options area.

  This causes two kernel bugs downstream:

  • Bug A (WARN_ONCE / panic_on_warn in skb_checksum_help): With 2 VLAN tags (22 bytes L2), csum_start = 20 passes 20 >= 20. Once
  eth_type_trans() + skb_vlan_untag() pull 22 bytes, skb_checksum_start_offset(skb) (csum_start - skb_headroom(skb)) becomes -2. In
  dev.c:3645, offset >= skb_headlen(skb) promotes signed -2 to 0xfffffffeU, firing WARN_ONCE(1, ...) and crashing panic_on_warn=1 hosts.
  • Bug B (TOCTOU L3 Header Corruption -> OOB Read in ip_do_fragment): With csum_start = 20, csum_offset = 0 on TAP/AF_PACKET, csum_start
  lands at byte 6 of struct iphdr (frag_off) or byte 0 (version/ihl on double-VLAN frames). In ip_output.c:774 and nf_conntrack_bridge.c:42,
  skb_checksum_help(skb) runs before hlen = iph->ihl * 4 is read. The 16-bit checksum write corrupts iph->ihl (e.g. from 5 [20B] to 15 [60B])
  after ip_rcv_core() already validated it, causing ip_do_fragment() to read 60 bytes out-of-bounds from skb->data.

  parent reply	other threads:[~2026-09-22 22:29 UTC|newest]

Thread overview: 19+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-22  3:03 [PATCH net v6 0/2] net: prevent partial checksums from modifying network headers Paulos Yibelo
2026-09-22  3:03 ` [PATCH net v6 1/2] net: validate virtio checksum start after network header Paulos Yibelo
2026-09-22  3:14   ` sashiko-bot
2026-09-22  5:14   ` Michael S. Tsirkin
     [not found]     ` <CAHv8Y_4OKdVkigToBmXXhhUxu+EL2iWVADZJ=LS4CGqBATgU5g@mail.gmail.com>
2026-09-22  5:55       ` Johannes Berg
2026-09-22  5:56     ` Johannes Berg
2026-09-22  8:46       ` Michael S. Tsirkin
2026-09-22 22:29   ` Willem de Bruijn [this message]
2026-09-23  1:27     ` Willem de Bruijn
2026-09-23 10:21       ` Michael S. Tsirkin
2026-09-23 10:46         ` Eric Dumazet
2026-09-24  2:02           ` Michael S. Tsirkin
2026-09-24 17:18             ` Jakub Kicinski
2026-10-01 22:52             ` Willem de Bruijn
2026-10-03 17:51               ` Michael S. Tsirkin
2026-10-03 19:10                 ` Willem de Bruijn
2026-09-25  9:05   ` netdev-bot+sashiko
2026-09-22  3:03 ` [PATCH net v6 2/2] ip: reject partial checksums covering network headers Paulos Yibelo
2026-09-22  3:14   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=willemdebruijn.kernel.259bc7096258d@gmail.com \
    --to=willemdebruijn.kernel@gmail.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=anton.ivanov@cambridgegreys.com \
    --cc=bridge@lists.linux.dev \
    --cc=coreteam@netfilter.org \
    --cc=davem@davemloft.net \
    --cc=dsahern@kernel.org \
    --cc=edumazet@google.com \
    --cc=eperezma@redhat.com \
    --cc=fw@strlen.de \
    --cc=habte.yibelo@gmail.com \
    --cc=horms@kernel.org \
    --cc=idosch@nvidia.com \
    --cc=jasowangio@gmail.com \
    --cc=johannes@sipsolutions.net \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-um@lists.infradead.org \
    --cc=mst@redhat.com \
    --cc=netdev@vger.kernel.org \
    --cc=netfilter-devel@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=pablo@netfilter.org \
    --cc=phil@nwl.cc \
    --cc=razor@blackwall.org \
    --cc=richard@nod.at \
    --cc=virtualization@lists.linux.dev \
    --cc=xuanzhuo@linux.alibaba.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.