From: sashiko-bot@kernel.org
To: "Michael S. Tsirkin" <mst@redhat.com>
Cc: virtualization@lists.linux.dev, "Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH net v5 4/7] net: resolve uncommon partial checksums at ingress
Date: Fri, 23 Oct 2026 12:59:06 +0000 [thread overview]
Message-ID: <sashiko-outbox-182805@kernel.org> (raw)
In-Reply-To: <342bf5331e12b2e5b940510293233bb8bce46127.1792668082.git.mst@redhat.com>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] net: virtio_net_l3_start() allows skb->protocol and eth_hdr() mismatch
- [High] net: virtio_net_hdr_tnl_to_skb() mishandles non-GSO tunnel packets
- [High] net: virtio_net_l3_start() IPv6 inference overwritten by set_proto
- [Medium] net: virtio_net_l4_start() skips IPv6 AH headers without IP6_FH_F_AUTH
--
commit 1c3b02f56c070f59dd306e2b6555bc2eacaea7e4
Author: Michael S. Tsirkin <mst@redhat.com>
net: resolve uncommon partial checksums at ingress
This commit validates virtio_net_hdr checksum offsets at ingress so that
CHECKSUM_PARTIAL is only retained when csum_start and csum_offset match a
parsed TCP or UDP header. Other non-GSO partial checksums are completed in
software immediately, while unrecognized GSO packets are dropped.
> diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h
> index 013c6c7e2dea0..52852a33a2db7 100644
> --- a/include/linux/virtio_net.h
> +++ b/include/linux/virtio_net.h
> @@ -48,6 +52,205 @@ static inline int virtio_net_hdr_set_proto(struct sk_buff *skb,
[ ... ]
> +static inline bool
> +virtio_net_l3_start(struct sk_buff *skb, int *nhoff, __be16 *proto)
> +{
> + /* skb->dev is only guaranteed to be set since 44378d02c5aa
> + * ("net: always dissect GSO packets in
> + * __virtio_net_hdr_to_skb()"), do not crash when it is not.
> + */
> + if (WARN_ON_ONCE(!skb->dev))
> + return false;
> +
> + /* A preset protocol is what the stack will use, except on
> + * loopback which re-parses the frame: loopback_xmit() calls
> + * eth_type_trans() on it.
> + */
> + if (skb->dev->type == ARPHRD_ETHER ||
> + skb->dev->type == ARPHRD_LOOPBACK) {
> + if (!pskb_may_pull(skb, ETH_HLEN))
> + return false;
> + *nhoff = ETH_HLEN;
> + *proto = skb->protocol;
> + if (!*proto || skb->dev->type == ARPHRD_LOOPBACK)
> + *proto = eth_hdr(skb)->h_proto;
> + if (eth_type_vlan(*proto)) {
> + *proto = __vlan_get_protocol(skb, *proto, nhoff);
> + if (!*proto)
> + return false;
> + }
[Severity: High]
What happens when skb->protocol and eth_hdr(skb)->h_proto disagree on an
AF_PACKET socket?
In net/packet/af_packet.c, packet_snd() and tpacket_snd() set skb->protocol
from the user-supplied sll_protocol or socket protocol before calling
virtio_net_hdr_to_skb():
if (proto != htons(ETH_P_ALL))
skb->protocol = proto;
skb->dev = dev;
...
if (vnet_hdr_sz) {
err = virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le());
On an ARPHRD_ETHER device, if userspace presets skb->protocol to
htons(ETH_P_8021Q) while eth_hdr(skb)->h_proto in the frame is
htons(ETH_P_IP), virtio_net_l3_start() ignores eth_hdr(skb)->h_proto and
calls __vlan_get_protocol(), treating the first 4 bytes of the IPv4 header
as a VLAN tag and advancing *nhoff by 4 bytes into a fake header inside the
packet. Conversely, if skb->protocol is htons(ETH_P_IP) while
eth_hdr(skb)->h_proto is htons(ETH_P_8021Q), virtio_net_ip4_parse() parses
the VLAN tag as the start of an IPv4 header.
When the frame is transmitted across virtual ARPHRD_ETHER devices that
forward via __dev_forward_skb2() in net/core/dev.c (such as veth_xmit() ->
veth_forward_skb() -> __dev_forward_skb(), macvlan, ipvlan, or netkit),
skb->protocol is overwritten from eth_hdr(skb)->h_proto:
if (likely(!ret)) {
skb->protocol = eth_type_trans(skb, dev);
skb_postpull_rcsum(skb, eth_hdr(skb), ETH_HLEN);
}
On the other hand, on ARPHRD_LOOPBACK, virtio_net_l3_start() ignores a
preset skb->protocol and validates against eth_hdr(skb)->h_proto, even
though the transmit path prior to loopback_xmit() (such as TC egress and
validate_xmit_skb() -> skb_mac_gso_segment() -> skb_network_protocol())
uses skb->protocol.
Could this allow a packet with mismatched skb->protocol and
eth_hdr(skb)->h_proto to bypass the CHECKSUM_PARTIAL offset validation in
virtio_net_csum_partial_ok()?
> + } else {
> + *nhoff = skb_network_offset(skb);
> + /* An unset network header shows up as an offset
> + * outside of the packet.
> + */
> + if (*nhoff < 0 || (unsigned int)*nhoff >= skb->len)
> + return false;
> + *proto = skb->protocol;
> + if (!*proto) {
> + const struct iphdr *iph;
> +
> + /* A packet socket bound to ETH_P_ALL leaves the
> + * protocol unset: infer it from the IP version
> + * at the network header.
> + */
> + if (!pskb_may_pull(skb, *nhoff + 1))
> + return false;
> + iph = (const struct iphdr *)(skb->data + *nhoff);
> + if (iph->version == 4)
> + *proto = htons(ETH_P_IP);
> + else if (iph->version == 6)
> + *proto = htons(ETH_P_IPV6);
> + }
> + }
> + return true;
> +}
[Severity: High]
Should virtio_net_l3_start() also update skb->protocol when it infers the
protocol from the IP version, or should virtio_net_hdr_set_proto() inspect
the IP version as well?
When a packet socket bound to ETH_P_ALL sends an IPv6 packet with
VIRTIO_NET_HDR_F_NEEDS_CSUM and VIRTIO_NET_HDR_GSO_UDP_L4 (or
VIRTIO_NET_HDR_GSO_UDP) on a non-Ethernet device without
header_ops->parse_protocol, __virtio_net_hdr_to_skb() takes the
VIRTIO_NET_HDR_F_NEEDS_CSUM branch and leaves skb->protocol as 0.
virtio_net_l3_start() then infers *proto = htons(ETH_P_IPV6) into its local
variable, and virtio_net_csum_partial_ok_at_offset() validates the packet
as IPv6. However, after virtio_net_hdr_to_skb() returns, packet_snd() and
tpacket_snd() call virtio_net_hdr_set_proto() in include/linux/virtio_net.h:
if (skb->protocol)
return 0;
switch (hdr->gso_type & ~VIRTIO_NET_HDR_GSO_ECN) {
case VIRTIO_NET_HDR_GSO_TCPV4:
case VIRTIO_NET_HDR_GSO_UDP:
case VIRTIO_NET_HDR_GSO_UDP_L4:
skb->protocol = cpu_to_be16(ETH_P_IP);
break;
Wouldn't this overwrite skb->protocol with htons(ETH_P_IP) after the packet
was validated as IPv6, causing skb_mac_gso_segment() to dispatch the IPv6
packet to inet_gso_segment()?
> +
> +/* Walk the L3 header at @nhoff: where does its transport header
> + * start, and which protocol is it? Returns false if the header
> + * is truncated, malformed or fragmented.
> + */
> +static inline bool
> +virtio_net_l4_start(struct sk_buff *skb, unsigned int nhoff, __be16 proto,
> + unsigned int *thoff, int *ip_proto)
> +{
> + if (proto == htons(ETH_P_IP)) {
> + const struct iphdr *iph = virtio_net_ip4_parse(skb, nhoff, thoff);
> +
> + if (!iph || ip_is_fragment(iph))
> + return false;
> + *ip_proto = iph->protocol;
> + return true;
> + }
> +
> + if (proto == htons(ETH_P_IPV6)) {
> + const struct ipv6hdr *iph;
> + int flags = 0;
> +
> + if (!pskb_may_pull(skb, nhoff + sizeof(*iph)))
> + return false;
> + iph = (const void *)(skb->data + nhoff);
> + if (iph->version != 6)
> + return false;
> + *thoff = nhoff + sizeof(*iph);
> + *ip_proto = iph->nexthdr;
> + if (*ip_proto != IPPROTO_TCP && *ip_proto != IPPROTO_UDP) {
> + *thoff = nhoff;
> + /* Find the transport header behind any
> + * extension headers, AH included.
> + */
> + *ip_proto = ipv6_find_hdr(skb, thoff, -1, NULL,
> + &flags);
> + if (*ip_proto < 0 || flags & IP6_FH_F_FRAG)
> + return false;
> + }
[Severity: Medium]
Should flags be initialized to IP6_FH_F_AUTH before calling ipv6_find_hdr()
in virtio_net_l4_start()?
When ipv6_find_hdr() in net/ipv6/exthdrs_core.c is called with flags = 0, it
skips NEXTHDR_AUTH headers by ipv6_authlen(hp) bytes. By contrast, packet
dissectors and netfilter helpers such as nft_set_pktinfo_ipv6() in
include/net/netfilter/nf_tables_ipv6.h stop at NEXTHDR_AUTH:
unsigned int flags = IP6_FH_F_AUTH;
int protohdr, thoff = 0;
unsigned short frag_off;
protohdr = ipv6_find_hdr(pkt->skb, &thoff, -1, &frag_off, &flags);
Because ipv6_find_hdr() only checks the first 2 bytes of the AH header, a
packet carrying NEXTHDR_AUTH can advance *thoff to any 4-byte-aligned offset
past the IPv6 header and keep CHECKSUM_PARTIAL armed across rules that stop
at NEXTHDR_AUTH.
In addition, on local IPv6 receive, ah6_input() in net/ipv6/ah6.c resets
skb->ip_summed = CHECKSUM_NONE without completing CHECKSUM_PARTIAL before
verifying the AH ICV over the inner transport header, and IPv6 GSO offload
in ipv6_gso_pull_exthdrs() does not support NEXTHDR_AUTH. Wouldn't stopping
at NEXTHDR_AUTH via IP6_FH_F_AUTH allow non-GSO AH packets to have their
checksum completed in software at ingress and reject AH GSO packets,
matching the IPv4 behavior?
[ ... ]
> @@ -368,7 +628,22 @@ virtio_net_hdr_tnl_to_skb(struct sk_buff *skb,
> skb->inner_mac_header = inner_nh + skb_headroom(skb);
> skb->transport_header = outer_th + skb_headroom(skb);
> skb->encapsulation = 1;
> - return 0;
> +
> + if (!skb_is_gso(skb))
> + skb_shinfo(skb)->gso_type &= ~(SKB_GSO_UDP_TUNNEL_CSUM |
> + SKB_GSO_UDP_TUNNEL);
> +
> + /*
> + * We know VIRTIO_NET_HDR_F_NEEDS_CSUM was set so __virtio_net_hdr_to_skb
> + * set CHECKSUM_PARTIAL.
> + */
> + inner_proto = inner_isv6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP);
> + if (virtio_net_csum_partial_ok_at_offset(skb, gso_inner_type, inner_nh,
> + inner_proto))
> + return 0;
> +
> + /* A tunnel header implies GSO, so drop. */
> + return __virtio_net_resolve_csum_partial_mismatch(skb);
> }
[Severity: High]
How are non-GSO packets with a tunnel header handled in
virtio_net_hdr_tnl_to_skb()?
In __virtio_net_hdr_to_skb(), if skb->len - nh_off <= gso_size,
shinfo->gso_size is not set, so skb_is_gso(skb) is false when control
returns to virtio_net_hdr_tnl_to_skb().
If !skb_is_gso(skb) and virtio_net_csum_partial_ok_at_offset() returns true,
virtio_net_hdr_tnl_to_skb() returns 0 while keeping CHECKSUM_PARTIAL at the
caller-chosen vhdr->inner_nh_offset + inner_th inside the UDP payload (for
example, pointing at a fake IPv4 header placed inside the UDP payload or
overlapping a VXLAN inner Ethernet header), even though GSO segmentation
will not run. If VIRTIO_NET_HDR_F_UDP_TUNNEL_CSUM was set, clearing
SKB_GSO_UDP_TUNNEL_CSUM also leaves the outer UDP checksum uncomputed.
On the other hand, if !skb_is_gso(skb) and
virtio_net_csum_partial_ok_at_offset() returns false, the comment states
that a tunnel header implies GSO so the packet is dropped, but
__virtio_net_resolve_csum_partial_mismatch() only returns -EINVAL when
skb_is_gso(skb) is true:
static inline int __virtio_net_resolve_csum_partial_mismatch(struct sk_buff *skb)
{
if (skb_is_gso(skb))
return -EINVAL;
return skb_checksum_help(skb) ?: 1;
}
When !skb_is_gso(skb), __virtio_net_resolve_csum_partial_mismatch()
completes the checksum via skb_checksum_help() and returns 1, leaving
skb->encapsulation = 1 and the unvalidated skb->inner_transport_header,
skb->inner_network_header, and skb->inner_mac_header fields set on the skb.
Could this leave bogus encapsulation metadata or arbitrary inner
CHECKSUM_PARTIAL offsets on non-GSO packets?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.v5.git.mst@redhat.com?part=4
next prev parent reply other threads:[~2026-10-23 12:59 UTC|newest]
Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-22 12:57 [PATCH net v5 0/7] more virtio-net csum hardening Michael S. Tsirkin
2026-10-22 12:57 ` [PATCH net v5 1/7] net: account for ingress-resolved virtio net hdr state Michael S. Tsirkin
2026-10-22 14:37 ` Willem de Bruijn
2026-10-23 12:59 ` sashiko-bot
2026-10-25 5:30 ` netdev-bot+sashiko
2026-10-22 12:58 ` [PATCH net v5 2/7] um: vector: trim skbs before verifying the header Michael S. Tsirkin
2026-10-23 12:59 ` sashiko-bot
2026-10-25 5:31 ` netdev-bot+sashiko
2026-10-22 12:58 ` [PATCH net v5 3/7] virtio_net: parse tunnel csum_start little-endian Michael S. Tsirkin
2026-10-23 12:59 ` sashiko-bot
2026-10-22 12:58 ` [PATCH net v5 4/7] net: resolve uncommon partial checksums at ingress Michael S. Tsirkin
2026-10-22 14:52 ` Willem de Bruijn
2026-10-22 15:49 ` Michael S. Tsirkin
2026-10-22 17:52 ` Chris Arges
2026-10-22 23:38 ` Michael S. Tsirkin
2026-10-23 1:38 ` Chris Arges
2026-10-23 12:59 ` sashiko-bot [this message]
2026-10-25 5:31 ` netdev-bot+sashiko
2026-10-22 12:58 ` [PATCH net v5 5/7] net: add a sysctl to opt out of virtio net hdr checksum validation Michael S. Tsirkin
2026-10-23 12:59 ` sashiko-bot
2026-10-25 5:31 ` netdev-bot+sashiko
2026-10-22 12:58 ` [PATCH net v5 6/7] net: add ip4_parse() Michael S. Tsirkin
2026-10-23 12:59 ` sashiko-bot
2026-10-25 5:31 ` netdev-bot+sashiko
2026-10-22 12:58 ` [PATCH net v5 7/7] virtio_net: cleanup protocol calculation Michael S. Tsirkin
2026-10-23 12:59 ` sashiko-bot
2026-10-25 5:31 ` netdev-bot+sashiko
2026-10-22 17:54 ` [PATCH net v5 0/7] more virtio-net csum hardening Chris Arges
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=sashiko-outbox-182805@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=eperezma@redhat.com \
--cc=mst@redhat.com \
--cc=sashiko-reviews@lists.linux.dev \
--cc=virtualization@lists.linux.dev \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox