From: sashiko-bot@kernel.org
To: "Eric Dumazet" <edumazet@kernel.org>
Cc: virtualization@lists.linux.dev,
"Michael S. Tsirkin" <mst@redhat.com>,
"Eugenio Perez" <eperezma@redhat.com>
Subject: Re: [PATCH net v2 1/3] net: virtio: validate outer protocol of UDP tunnel GSO packets
Date: Thu, 05 Nov 2026 05:01:52 +0000 [thread overview]
Message-ID: <sashiko-outbox-198573@kernel.org> (raw)
In-Reply-To: <20261104050131.629967-2-edumazet@kernel.org>
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] net: virtio: L2 check bypass in virtio_net_l3_start() via TUNSETLINK
--
commit 546d87e0158fb3d32de1c338228f7d9a8f02bb4e
Author: Eric Dumazet <edumazet@kernel.org>
net: virtio: validate outer protocol of UDP tunnel GSO packets
This commit validates the outer L2/L3/L4 headers of UDP tunnel GSO packets in
virtio_net_hdr_tnl_to_skb() so that frames carrying mismatched outer protocols
or offsets are rejected before GSO segmentation.
Link: https://lore.kernel.org/netdev/93d0e37c99711bebdf60c120af03010455c2f2e2.1793276116.git.mst@redhat.com/
> diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h
> index d6466f96cdd00..b9c8babb1fd73 100644
> --- a/include/linux/virtio_net.h
> +++ b/include/linux/virtio_net.h
[ ... ]
> +/* Where does the L3 header start, and what protocol is it?
> + * Handles Ethernet and VLAN headers; other devices use the
> + * network header offset the caller set up.
> + */
> +static inline bool
> +virtio_net_l3_start(struct sk_buff *skb, int *nhoff, __be16 *proto)
> +{
> + /* skb->dev should always be set here already,
> + * but let's not crash when it is not.
> + */
> + if (WARN_ON_ONCE(!skb->dev))
> + return false;
> +
> + if (skb->dev->type == ARPHRD_ETHER ||
> + skb->dev->type == ARPHRD_LOOPBACK) {
> + __be16 h_proto;
> +
> + if (!pskb_may_pull(skb, ETH_HLEN))
> + return false;
> + h_proto = eth_hdr(skb)->h_proto;
> + /* skb->protocol is a sender claim, and parts of the
> + * stack parse the packet per it: when it disagrees with
> + * the frame we do not know which view the packet will
> + * be parsed as, so do not validate.
> + */
> + if (skb->protocol && skb->protocol != h_proto)
> + return false;
> + *nhoff = ETH_HLEN;
> + *proto = h_proto;
> + if (eth_type_vlan(*proto)) {
> + *proto = __vlan_get_protocol(skb, *proto, nhoff);
> + if (!*proto)
> + return false;
> + }
> + } else {
> + const struct iphdr *iph;
> +
> + *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;
> + /* No L2 header to cross-check with: take the protocol
> + * 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);
> + else
> + return false;
> + if (skb->protocol && skb->protocol != *proto)
> + return false;
> + }
> + return true;
> +}
[Severity: High]
Can a tap writer bypass the Ethernet header check in virtio_net_l3_start() by
changing tun->dev->type via the TUNSETLINK ioctl?
In drivers/net/tun.c, __tun_chr_ioctl() allows changing tun->dev->type while
the device is down without changing tun->flags:
drivers/net/tun.c:__tun_chr_ioctl() {
case TUNSETLINK:
...
tun->dev->type = (int) arg;
tun->dev->addr_len = tun_get_addr_len(tun->dev->type);
}
Meanwhile, tun_get_user() (and tun_xdp_one()) decides whether the frame starts
with an Ethernet header based on (tun->flags & TUN_TYPE_MASK) == IFF_TAP,
setting the network header to ETH_HLEN before calling
tun_vnet_hdr_tnl_to_skb(), and only populating skb->protocol via
eth_type_trans() afterwards:
drivers/net/tun.c:tun_get_user() {
switch (tun->flags & TUN_TYPE_MASK) {
...
case IFF_TAP:
...
skb_set_network_header(skb, ETH_HLEN);
break;
}
if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) {
...
}
if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP)
skb->protocol = eth_type_trans(skb, tun->dev);
}
If an IFF_TAP device has tun->dev->type set to another type such as
ARPHRD_NONE via TUNSETLINK, virtio_net_l3_start() takes the else branch, reads
*nhoff = skb_network_offset(skb) (which is ETH_HLEN), derives *proto from the
IP version nibble at offset 14, and skips the skb->protocol check because
skb->protocol is still 0.
The Ethernet header at offset 0 is never inspected by virtio_net_l3_start(),
so eth_type_trans() will subsequently set skb->protocol from the unvalidated
eth_hdr(skb)->h_proto (for example ETH_P_NSH) on a packet with
SKB_GSO_UDP_TUNNEL set, triggering the same skb_under_panic() during GSO
segmentation.
Conversely, on an IFF_TUN device whose tun->dev->type is changed to
ARPHRD_ETHER via TUNSETLINK, virtio_net_l3_start() validates a 14-byte
Ethernet header at offset 0 and an outer IP header at offset 14, but
tun_get_user() does not call eth_type_trans() and resets skb->network_header
to offset 0, leaving the outer network header pointing at the fake Ethernet
header.
Should tun_get_user() and tun_xdp_one() set skb->protocol before calling
tun_vnet_hdr_tnl_to_skb(), or should virtio_net_l3_start() avoid relying on
skb->dev->type matching the caller's L2 framing?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261104050131.629967-1-edumazet@kernel.org?part=1
next prev parent reply other threads:[~2026-11-05 5:01 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-04 5:01 [PATCH net v2 0/3] net: virtio: validate outer protocol of UDP tunnel GSO packets Eric Dumazet
2026-11-04 5:01 ` [PATCH net v2 1/3] " Eric Dumazet
2026-11-04 15:32 ` Willem de Bruijn
2026-11-04 15:36 ` Michael S. Tsirkin
2026-11-05 5:01 ` sashiko-bot [this message]
2026-11-05 10:08 ` netdev-bot+sashiko
2026-11-05 11:52 ` Eric Dumazet
2026-11-04 5:01 ` [PATCH net v2 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() Eric Dumazet
2026-11-05 5:01 ` sashiko-bot
2026-11-04 5:01 ` [PATCH net v2 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet
2026-11-04 15:36 ` Willem de Bruijn
2026-11-05 5:01 ` sashiko-bot
2026-11-05 10:08 ` netdev-bot+sashiko
2026-11-05 11:54 ` Eric Dumazet
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-198573@kernel.org \
--to=sashiko-bot@kernel.org \
--cc=edumazet@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 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.