* [PATCH net 0/3] net: virtio: validate outer protocol of UDP tunnel GSO packets
@ 2026-11-03 20:31 Eric Dumazet
2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet
` (2 more replies)
0 siblings, 3 replies; 13+ messages in thread
From: Eric Dumazet @ 2026-11-03 20:31 UTC (permalink / raw)
To: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni
Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin,
Eugenio Pérez, Hassan Sultan, virtualization
virtio_net_hdr_tnl_to_skb() does not check that a UDP tunnel GSO
packet really has an outer IPv4/IPv6 header matching the declared
tunnel type. A guest, or any tap writer, can send a frame with another
outer ethertype (NSH in the report), and the GSO stack then builds
segments with skb->data before skb->head. The host crashes in
skb_under_panic() as soon as a header is pushed.
Patch 1 adds the missing check. Ethernet frames can carry VLAN tags
before the outer IP header; for IFF_TUN, skb->protocol is provided
by the caller.
Patch 2 fixes a selftest helper computing UDP checksums over a length
in network byte order.
Patch 3 adds tun selftests: valid outer IPv4 frames (plain and VLAN
tagged) are accepted, mismatched or non-IP outer headers are rejected.
Eric Dumazet (3):
net: virtio: validate outer protocol of UDP tunnel GSO packets
selftests: net: tuntap: fix UDP checksum length in
build_udp_packet_csum()
selftests: net: tun: test outer protocol of UDP tunnel GSO packets
include/linux/virtio_net.h | 28 ++++
tools/testing/selftests/net/tun.c | 130 +++++++++++++++++++
tools/testing/selftests/net/tuntap_helpers.h | 2 +-
3 files changed, 159 insertions(+), 1 deletion(-)
base-commit: 647b81ddefc6364290e67bf33c89d54971f8995e
--
2.53.0
^ permalink raw reply [flat|nested] 13+ messages in thread* [PATCH net 1/3] net: virtio: validate outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 [PATCH net 0/3] net: virtio: validate outer protocol of UDP tunnel GSO packets Eric Dumazet @ 2026-11-03 20:31 ` Eric Dumazet 2026-11-04 2:01 ` Willem de Bruijn ` (2 more replies) 2026-11-03 20:31 ` [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() Eric Dumazet 2026-11-03 20:31 ` [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet 2 siblings, 3 replies; 13+ messages in thread From: Eric Dumazet @ 2026-11-03 20:31 UTC (permalink / raw) To: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin, Eugenio Pérez, Hassan Sultan, virtualization virtio_net_hdr_tnl_to_skb() trusts the virtio header to describe a UDP tunnel over IPv4 or IPv6, but never checks that the frame actually carries such an outer header. Because VIRTIO_NET_HDR_F_NEEDS_CSUM is mandatory for tunnel GSO, __virtio_net_hdr_to_skb() skips the flow dissector, so virtio_net_hdr_match_proto() is never applied either. A guest (or a tap writer) can then inject a frame with an arbitrary outer ethertype, for example ETH_P_NSH, with SKB_GSO_UDP_TUNNEL set. The GSO stack then runs nsh_gso_segment() on top of the UDP tunnel segmentation, which assumes a different header layout. The result is segments with skb->data before skb->head, and a host crash in skb_under_panic() as soon as a later layer pushes a header: skbuff: skb_under_panic: ... put:4 ... dev:gretap1 kernel BUG at net/core/skbuff.c:215! skb_push __gre_xmit gre_tap_xmit ... tun_get_user Require the outer L3 protocol to match the declared tunnel type: for Ethernet frames, look at the ethertype after optional VLAN tags, and check that the outer network header fits before outer_th_offset. For IFF_TUN, skb->protocol is already set by the caller. Fixes: a2fb4bc4e2a6 ("net: implement virtio helpers to handle UDP GSO tunneling.") Reported-by: Hassan Sultan <hassansultan@google.com> Signed-off-by: Eric Dumazet <edumazet@kernel.org> Cc: Willem de Bruijn <willemb@google.com> Cc: Michael S. Tsirkin <mst@redhat.com> --- include/linux/virtio_net.h | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index d6466f96cdd0..d3f97d71d81c 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -282,6 +282,31 @@ static inline unsigned int virtio_l3min(bool is_ipv6) return is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr); } +/* The outer network header of a UDP tunnel GSO packet must match the + * tunnel type declared in the virtio header, otherwise the GSO stack + * would parse the packet as something else (e.g. NSH). + */ +static inline bool virtio_net_tnl_outer_proto_ok(const struct sk_buff *skb, + bool outer_isv6, + unsigned int outer_th) +{ + __be16 expected = outer_isv6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP); + __be16 proto = skb->protocol; + int nh_off = ETH_HLEN; + + /* L3 device (IFF_TUN): the caller already set the protocol. */ + if (proto) + return proto == expected; + + /* Ethernet frame, possibly VLAN tagged. */ + proto = eth_hdr(skb)->h_proto; + if (eth_type_vlan(proto)) + proto = __vlan_get_protocol(skb, proto, &nh_off); + + return proto == expected && + nh_off + virtio_l3min(outer_isv6) <= outer_th; +} + static inline int virtio_net_hdr_tnl_to_skb(struct sk_buff *skb, const struct virtio_net_hdr_v1_hash_tunnel *vhdr, @@ -338,6 +363,9 @@ virtio_net_hdr_tnl_to_skb(struct sk_buff *skb, if (ret) return ret; + if (!virtio_net_tnl_outer_proto_ok(skb, outer_isv6, outer_th)) + return -EINVAL; + /* In case of USO, the inner protocol is still unknown and * `inner_isv6` is just a guess, additional parsing is needed. * The previous validation ensures that accessing an ipv4 inner -- 2.53.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH net 1/3] net: virtio: validate outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet @ 2026-11-04 2:01 ` Willem de Bruijn 2026-11-04 4:49 ` Eric Dumazet 2026-11-04 20:31 ` sashiko-bot 2026-11-05 8:32 ` netdev-bot+sashiko 2 siblings, 1 reply; 13+ messages in thread From: Willem de Bruijn @ 2026-11-04 2:01 UTC (permalink / raw) To: Eric Dumazet, netdev, David S. Miller, Jakub Kicinski, Paolo Abeni, mst Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin, Eugenio Pérez, Hassan Sultan, virtualization Eric Dumazet wrote: > virtio_net_hdr_tnl_to_skb() trusts the virtio header to describe a > UDP tunnel over IPv4 or IPv6, but never checks that the frame actually > carries such an outer header. Because VIRTIO_NET_HDR_F_NEEDS_CSUM is > mandatory for tunnel GSO, __virtio_net_hdr_to_skb() skips the flow > dissector, so virtio_net_hdr_match_proto() is never applied either. > > A guest (or a tap writer) can then inject a frame with an arbitrary > outer ethertype, for example ETH_P_NSH, with SKB_GSO_UDP_TUNNEL set. > The GSO stack then runs nsh_gso_segment() on top of the UDP tunnel > segmentation, which assumes a different header layout. The result is > segments with skb->data before skb->head, and a host crash in > skb_under_panic() as soon as a later layer pushes a header: > > skbuff: skb_under_panic: ... put:4 ... dev:gretap1 > kernel BUG at net/core/skbuff.c:215! > skb_push > __gre_xmit > gre_tap_xmit > ... > tun_get_user > > Require the outer L3 protocol to match the declared tunnel type: > for Ethernet frames, look at the ethertype after optional VLAN tags, > and check that the outer network header fits before outer_th_offset. > For IFF_TUN, skb->protocol is already set by the caller. > > Fixes: a2fb4bc4e2a6 ("net: implement virtio helpers to handle UDP GSO tunneling.") > Reported-by: Hassan Sultan <hassansultan@google.com> > Signed-off-by: Eric Dumazet <edumazet@kernel.org> > Cc: Willem de Bruijn <willemb@google.com> > Cc: Michael S. Tsirkin <mst@redhat.com> Thanks for the fix. It overlaps with commit "net: resolve uncommon partial checksums at ingress" https://lore.kernel.org/netdev/93d0e37c99711bebdf60c120af03010455c2f2e2.1793276116.git.mst@redhat.com/ There are a bunch of subtle issues here. That series is up to v7. Two specific issues are that - skb->protocol in general cannot be trusted, and - non-L3 sources cannot be assumed Ethernet Would it make sense to take the relevant parts from the quoted commit, which has hopefully resolved those edge cases, and send that on its own? The rest of the series can come later. > --- > include/linux/virtio_net.h | 28 ++++++++++++++++++++++++++++ > 1 file changed, 28 insertions(+) > > diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h > index d6466f96cdd0..d3f97d71d81c 100644 > --- a/include/linux/virtio_net.h > +++ b/include/linux/virtio_net.h > @@ -282,6 +282,31 @@ static inline unsigned int virtio_l3min(bool is_ipv6) > return is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr); > } > > +/* The outer network header of a UDP tunnel GSO packet must match the > + * tunnel type declared in the virtio header, otherwise the GSO stack > + * would parse the packet as something else (e.g. NSH). > + */ > +static inline bool virtio_net_tnl_outer_proto_ok(const struct sk_buff *skb, > + bool outer_isv6, > + unsigned int outer_th) > +{ > + __be16 expected = outer_isv6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP); > + __be16 proto = skb->protocol; > + int nh_off = ETH_HLEN; > + > + /* L3 device (IFF_TUN): the caller already set the protocol. */ > + if (proto) > + return proto == expected; > + > + /* Ethernet frame, possibly VLAN tagged. */ > + proto = eth_hdr(skb)->h_proto; > + if (eth_type_vlan(proto)) > + proto = __vlan_get_protocol(skb, proto, &nh_off); > + > + return proto == expected && > + nh_off + virtio_l3min(outer_isv6) <= outer_th; > +} > + > static inline int > virtio_net_hdr_tnl_to_skb(struct sk_buff *skb, > const struct virtio_net_hdr_v1_hash_tunnel *vhdr, > @@ -338,6 +363,9 @@ virtio_net_hdr_tnl_to_skb(struct sk_buff *skb, > if (ret) > return ret; > > + if (!virtio_net_tnl_outer_proto_ok(skb, outer_isv6, outer_th)) > + return -EINVAL; > + > /* In case of USO, the inner protocol is still unknown and > * `inner_isv6` is just a guess, additional parsing is needed. > * The previous validation ensures that accessing an ipv4 inner > -- > 2.53.0 > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net 1/3] net: virtio: validate outer protocol of UDP tunnel GSO packets 2026-11-04 2:01 ` Willem de Bruijn @ 2026-11-04 4:49 ` Eric Dumazet 0 siblings, 0 replies; 13+ messages in thread From: Eric Dumazet @ 2026-11-04 4:49 UTC (permalink / raw) To: Willem de Bruijn Cc: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni, mst, Simon Horman, Willem de Bruijn, Eugenio Pérez, Hassan Sultan, virtualization Le mer. 4 nov. 2026 à 03:01, Willem de Bruijn <willemdebruijn.kernel@gmail.com> a écrit : > > Eric Dumazet wrote: > > virtio_net_hdr_tnl_to_skb() trusts the virtio header to describe a > > UDP tunnel over IPv4 or IPv6, but never checks that the frame actually > > carries such an outer header. Because VIRTIO_NET_HDR_F_NEEDS_CSUM is > > mandatory for tunnel GSO, __virtio_net_hdr_to_skb() skips the flow > > dissector, so virtio_net_hdr_match_proto() is never applied either. > > > > A guest (or a tap writer) can then inject a frame with an arbitrary > > outer ethertype, for example ETH_P_NSH, with SKB_GSO_UDP_TUNNEL set. > > The GSO stack then runs nsh_gso_segment() on top of the UDP tunnel > > segmentation, which assumes a different header layout. The result is > > segments with skb->data before skb->head, and a host crash in > > skb_under_panic() as soon as a later layer pushes a header: > > > > skbuff: skb_under_panic: ... put:4 ... dev:gretap1 > > kernel BUG at net/core/skbuff.c:215! > > skb_push > > __gre_xmit > > gre_tap_xmit > > ... > > tun_get_user > > > > Require the outer L3 protocol to match the declared tunnel type: > > for Ethernet frames, look at the ethertype after optional VLAN tags, > > and check that the outer network header fits before outer_th_offset. > > For IFF_TUN, skb->protocol is already set by the caller. > > > > Fixes: a2fb4bc4e2a6 ("net: implement virtio helpers to handle UDP GSO tunneling.") > > Reported-by: Hassan Sultan <hassansultan@google.com> > > Signed-off-by: Eric Dumazet <edumazet@kernel.org> > > Cc: Willem de Bruijn <willemb@google.com> > > Cc: Michael S. Tsirkin <mst@redhat.com> > > Thanks for the fix. It overlaps with > > commit "net: resolve uncommon partial checksums at ingress" > https://lore.kernel.org/netdev/93d0e37c99711bebdf60c120af03010455c2f2e2.1793276116.git.mst@redhat.com/ > > There are a bunch of subtle issues here. That series is up to v7. > > Two specific issues are that > - skb->protocol in general cannot be trusted, and > - non-L3 sources cannot be assumed Ethernet Agreed on both. With IFF_TUN and a tun_pi header, skb->protocol is whatever userspace put in pi.proto, and v1 would accept an IPv4 tunnel frame whose IP version says otherwise. > > Would it make sense to take the relevant parts from the quoted commit, > which has hopefully resolved those edge cases, and send that on its own? > The rest of the series can come later. Done in v2: patch 1 now uses virtio_net_l3_start(), virtio_net_l4_start(), virtio_net_ip4_parse() and virtio_net_tnl_offsets_ok() from Michael's v7 4/5, unchanged except that ipv6_find_hdr() stops at AH, as you suggested in your v7 review. The checksum validation/completion parts and the non-GSO carve-out are left to Michael's series. Michael, I added your Co-developed-by and Signed-off-by, since the code is yours; please let me know if you prefer a different attribution. v2 also adds IFF_TUN tests with a mismatching tun_pi protocol, and addresses your comments on patches 2 and 3. One side note: virtio_net_hdr_tnl_to_skb() uses outer_l3min = ETH_HLEN + virtio_l3min(outer_isv6) for IFF_TUN too, so tunnel GSO frames without an Ethernet header are rejected unless the outer IP header carries at least 14 bytes of options (the tests pad with IPv4 options for that reason). This predates the series, I left it alone for now. Thanks! pw-bot: cr ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net 1/3] net: virtio: validate outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet 2026-11-04 2:01 ` Willem de Bruijn @ 2026-11-04 20:31 ` sashiko-bot 2026-11-05 8:32 ` netdev-bot+sashiko 2 siblings, 0 replies; 13+ messages in thread From: sashiko-bot @ 2026-11-04 20:31 UTC (permalink / raw) To: Eric Dumazet; +Cc: virtualization, Michael S. Tsirkin, Eugenio Perez Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [High] net: virtio: unreliable L3 check in virtio_net_tnl_outer_proto_ok() -- commit 02f420dee7a050b3832ecf552c44f9c2eeaaddc1 Author: Eric Dumazet <edumazet@kernel.org> net: virtio: validate outer protocol of UDP tunnel GSO packets This commit validates that the outer L3 protocol of a UDP tunnel GSO packet matches the tunnel type declared in the virtio header in virtio_net_hdr_tnl_to_skb(). > diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h > index d6466f96cdd00..d3f97d71d81cc 100644 > --- a/include/linux/virtio_net.h > +++ b/include/linux/virtio_net.h > @@ -282,6 +282,31 @@ static inline unsigned int virtio_l3min(bool is_ipv6) > return is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr); > } > > +/* The outer network header of a UDP tunnel GSO packet must match the > + * tunnel type declared in the virtio header, otherwise the GSO stack > + * would parse the packet as something else (e.g. NSH). > + */ > +static inline bool virtio_net_tnl_outer_proto_ok(const struct sk_buff *skb, > + bool outer_isv6, > + unsigned int outer_th) > +{ > + __be16 expected = outer_isv6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP); > + __be16 proto = skb->protocol; > + int nh_off = ETH_HLEN; > + > + /* L3 device (IFF_TUN): the caller already set the protocol. */ > + if (proto) > + return proto == expected; [Severity: High] Can testing if (proto) here be bypassed in tun_get_user() in two ways? First, on an IFF_TAP device with IFF_NAPI_FRAGS enabled, tun_get_user() allocates the skb via tun_napi_alloc_frags() -> napi_get_frags(). When a prior packet was merged by GRO (GRO_MERGED_FREE in napi_frags_finish()), napi_reuse_skb() caches napi->skb for reuse without clearing skb->protocol (which napi_frags_skb() set to the previous frame's eth->h_proto, such as htons(ETH_P_IP)). In drivers/net/tun.c:tun_get_user(), tun_vnet_hdr_tnl_to_skb() runs before eth_type_trans() updates skb->protocol for the new frame: if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { atomic_long_inc(&tun->rx_frame_errors); err = -EINVAL; goto free_skb; } if ((tun->flags & TUN_TYPE_MASK) == IFF_TAP) skb->protocol = eth_type_trans(skb, tun->dev); With a reused napi->skb still carrying skb->protocol == htons(ETH_P_IP), virtio_net_tnl_outer_proto_ok() takes the if (proto) branch and returns true without inspecting eth_hdr(skb)->h_proto or checking nh_off + virtio_l3min(outer_isv6) <= outer_th, and eth_type_trans() then sets skb->protocol to the new frame's actual EtherType (such as ETH_P_NSH). Second, on an IFF_TUN device without IFF_NO_PI, tun_get_user() copies struct tun_pi from userspace and sets skb->protocol = pi.proto: skb_reset_network_header(skb); skb->protocol = pi.proto; break; If userspace passes pi.proto == 0, proto is 0, so virtio_net_tnl_outer_proto_ok() falls through to the Ethernet path and checks eth_hdr(skb)->h_proto inside the L3 payload, leaving skb->protocol == 0 with SKB_GSO_UDP_TUNNEL set if those bytes match expected. Since all callers set skb->network_header before calling virtio_net_hdr_tnl_to_skb() (to 0 via skb_reset_network_header() for IFF_TUN, and to ETH_HLEN via skb_set_network_header() for IFF_TAP and virtio_net), would checking !skb_network_offset(skb) instead of if (proto) avoid both issues? -- Sashiko AI review · https://sashiko.dev/#/patchset/20261103203113.550604-1-edumazet@kernel.org?part=1 ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net 1/3] net: virtio: validate outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet 2026-11-04 2:01 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot @ 2026-11-05 8:32 ` netdev-bot+sashiko 2 siblings, 0 replies; 13+ messages in thread From: netdev-bot+sashiko @ 2026-11-05 8:32 UTC (permalink / raw) To: edumazet Cc: netdev, davem, kuba, pabeni, horms, willemb, mst, eperezma, hassansultan, virtualization Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider. Critical: 0 · High: 1 · Medium: 1 · Low: 1 If you believe none of the issues are legitimate, please reply to each issue of Medium severity or higher and explain why the AI is wrong. If any of them are legitimate and you plan to respin, please let patchwork know by including "pw-bot: cr" as a separate line at the end of your reply (one such reply per series is enough). - [High] The validation in virtio_net_tnl_outer_proto_ok() (include/linux/virtio_net.h) can be bypassed on IFF_TAP devices that… - [Medium] virtio_net_tnl_outer_proto_ok() only checks the outer ethertype (or skb->protocol) and that a minimum-size IPv4/IPv6 header… - [Low] virtio_net_tnl_outer_proto_ok() treats skb->protocol == 0 as meaning "Ethernet frame", and its comment says that for IFF_TUN "the… -- net: virtio: validate outer protocol of UDP tunnel GSO packets This patch adds virtio_net_tnl_outer_proto_ok(), called from virtio_net_hdr_tnl_to_skb(). The new helper requires the outer L3 protocol of a UDP tunnel GSO packet to match the tunnel type declared in the virtio header. For Ethernet frames it checks the ethertype after optional VLAN tags, plus the outer network header size against outer_th_offset. For IFF_TUN it relies on skb->protocol, which the caller has already set. > diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h > index d6466f96cdd00..d3f97d71d81cc 100644 > --- a/include/linux/virtio_net.h > +++ b/include/linux/virtio_net.h > @@ -282,6 +282,31 @@ static inline unsigned int virtio_l3min(bool is_ipv6) > return is_ipv6 ? sizeof(struct ipv6hdr) : sizeof(struct iphdr); > } > > +/* The outer network header of a UDP tunnel GSO packet must match the > + * tunnel type declared in the virtio header, otherwise the GSO stack > + * would parse the packet as something else (e.g. NSH). > + */ > +static inline bool virtio_net_tnl_outer_proto_ok(const struct sk_buff *skb, > + bool outer_isv6, > + unsigned int outer_th) > +{ > + __be16 expected = outer_isv6 ? htons(ETH_P_IPV6) : htons(ETH_P_IP); > + __be16 proto = skb->protocol; > + int nh_off = ETH_HLEN; > + > + /* L3 device (IFF_TUN): the caller already set the protocol. */ > + if (proto) > + return proto == expected; [Severity: High] Can a stale skb->protocol on an IFF_TAP device get past this check? On the IFF_TAP path, tun_get_user() does not clear skb->protocol before it calls tun_vnet_hdr_tnl_to_skb(). eth_type_trans() only sets it afterwards: drivers/net/tun.c:tun_get_user() { ... if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { ... } With IFF_NAPI_FRAGS, tun_napi_alloc_frags() gets its skb from napi_get_frags(&tfile->napi), which returns the cached napi->skb. If GRO merged the previous frame with GRO_MERGED_FREE, that skb was recycled through: napi_frags_finish() napi_reuse_skb() napi_reuse_skb() resets dev, pkt_type, encapsulation, ip_summed, gso_type and gso_size. It does not reset skb->protocol, which napi_frags_skb() had set to the previous frame's eth->h_proto (for example ETH_P_IP). On the next write, __virtio_net_hdr_to_skb() leaves skb->protocol alone on the NEEDS_CSUM path. virtio_net_tnl_outer_proto_ok() then sees htons(ETH_P_IP), takes the "L3 device" early return, and never reads the real ethertype (for example ETH_P_NSH). eth_type_trans() then sets skb->protocol to ETH_P_NSH on an skb that carries SKB_GSO_UDP_TUNNEL. If that skb is forwarded (bridge to gretap) and segmented in software, wouldn't nsh_gso_segment() run and hit the same skb_under_panic() described in the commit message? The setup needs a tap opened with IFF_TAP | IFF_NAPI_FRAGS (CAP_NET_ADMIN). GRO also has to hold packets across writes, for example with napi_defer_hard_irqs / gro_flush_timeout configured. Once that is in place, a guest using vhost-net controls the frame contents. Could the helper avoid inferring L2 vs L3 from skb->protocol? For example, the caller could pass an explicit L2/L3 flag, or Ethernet callers could always parse eth_hdr(). > + > + /* Ethernet frame, possibly VLAN tagged. */ > + proto = eth_hdr(skb)->h_proto; > + if (eth_type_vlan(proto)) > + proto = __vlan_get_protocol(skb, proto, &nh_off); [Severity: Low] Does IFF_TUN always set a non-zero skb->protocol, as the comment above assumes? For IFF_TUN without IFF_NO_PI, tun_get_user() copies the protocol directly from the userspace struct tun_pi: drivers/net/tun.c:tun_get_user() { ... skb_reset_network_header(skb); skb->protocol = pi.proto; ... } If pi.proto is 0, the L3 packet lands in this Ethernet branch. eth_hdr(skb)->h_proto then reads bytes 12-13 of the IPv4/IPv6 header as an ethertype, and the sender chooses those source address bytes. They can also match a VLAN TPID and be parsed further by __vlan_get_protocol(). With an IPv4 saddr that starts with 0x0800, the frame is accepted with skb->protocol still 0 and SKB_GSO_UDP_TUNNEL set. The practical impact looks small: no L3 handler takes protocol 0, and skb_mac_gso_segment() would return -EPROTONOSUPPORT. Still, the write is accepted when it presumably should fail with -EINVAL. This is the same skb->protocol based L2/L3 test as above. > + > + return proto == expected && > + nh_off + virtio_l3min(outer_isv6) <= outer_th; [Severity: Medium] Is checking only the ethertype and a minimum L3 size enough to stop GSO from parsing the packet as something other than a UDP tunnel? Nothing checks that the outer iph->protocol (or the IPv6 nexthdr chain) is UDP. Nothing checks that the real outer header length (ihl, extension headers) ends at outer_th either. GSO ignores the virtio outer_th and dispatches from the IP header itself: net/ipv4/af_inet.c:inet_gso_segment() { ... ops = rcu_dereference(inet_offloads[proto]); ... } For example, a frame with ethertype ETH_P_IP, iph->protocol = IPPROTO_GRE and VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4 passes this check. gre_gso_segment() then tests only skb->encapsulation, which virtio_net_hdr_tnl_to_skb() sets, and not SKB_GSO_GRE: net/ipv4/gre_offload.c:gre_gso_segment() { ... if (!skb->encapsulation) goto out; ... } With IPPROTO_TCP the frame would reach tcp4_gso_segment(), because gso_type also carries the inner SKB_GSO_TCPV4 bit. With ihl > 5, or with IPv6 extension headers, __skb_udp_tunnel_segment() derives tnl_hlen from the transport header that GSO recomputed: int tnl_hlen = skb_inner_mac_header(skb) - skb_transport_header(skb); That value differs from the outer_th layout validated here and can be smaller than a udphdr. The UDP len/check writes could then land on the inner IP header. I could not show a crash from this: pskb_may_pull() catches a negative tnl_hlen, and gre_gso_segment() recomputes its own offsets. Would it make sense to also require the outer L4 protocol to be UDP, and the outer L3 header to end exactly at outer_th? > +} > + [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103203113.550604-1-edumazet%40kernel.org ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() 2026-11-03 20:31 [PATCH net 0/3] net: virtio: validate outer protocol of UDP tunnel GSO packets Eric Dumazet 2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet @ 2026-11-03 20:31 ` Eric Dumazet 2026-11-04 2:10 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot 2026-11-03 20:31 ` [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet 2 siblings, 2 replies; 13+ messages in thread From: Eric Dumazet @ 2026-11-03 20:31 UTC (permalink / raw) To: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin, Eugenio Pérez, Hassan Sultan, virtualization build_udp_packet_csum() passes udph->len, which is in network byte order, as the number of bytes to checksum. On little-endian hosts this reads up to 64KB past the UDP header and computes a wrong checksum. Existing callers did not crash only because they use packet buffers of MAX_VNET_TUNNEL_PACKET_SZ bytes. Use ntohs(udph->len), as tap.c does in its own build_udp_packet(). Fixes: a942fcd72e97 ("selftest: tun: Introduce tuntap_helpers.h header for TUN/TAP testing") Signed-off-by: Eric Dumazet <edumazet@kernel.org> --- tools/testing/selftests/net/tuntap_helpers.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/testing/selftests/net/tuntap_helpers.h b/tools/testing/selftests/net/tuntap_helpers.h index d6c0437136ec..ac9b2da57a6d 100644 --- a/tools/testing/selftests/net/tuntap_helpers.h +++ b/tools/testing/selftests/net/tuntap_helpers.h @@ -335,7 +335,7 @@ static inline void build_udp_packet_csum(uint8_t *buf, int family, sum += htons(IPPROTO_UDP) + udph->len; if (!csum_off) - sum += add_csum(buf, udph->len); + sum += add_csum(buf, ntohs(udph->len)); udph->check = finish_ip_csum(sum); } -- 2.53.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() 2026-11-03 20:31 ` [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() Eric Dumazet @ 2026-11-04 2:10 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot 1 sibling, 0 replies; 13+ messages in thread From: Willem de Bruijn @ 2026-11-04 2:10 UTC (permalink / raw) To: Eric Dumazet, netdev, David S. Miller, Jakub Kicinski, Paolo Abeni Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin, Eugenio Pérez, Hassan Sultan, virtualization Eric Dumazet wrote: > build_udp_packet_csum() passes udph->len, which is in network byte > order, as the number of bytes to checksum. On little-endian hosts this > reads up to 64KB past the UDP header and computes a wrong checksum. > > Existing callers did not crash only because they use packet buffers > of MAX_VNET_TUNNEL_PACKET_SZ bytes. > > Use ntohs(udph->len), as tap.c does in its own build_udp_packet(). > > Fixes: a942fcd72e97 ("selftest: tun: Introduce tuntap_helpers.h header for TUN/TAP testing") > Signed-off-by: Eric Dumazet <edumazet@kernel.org> Reviewed-by: Willem de Bruijn <willemb@google.com> > --- > tools/testing/selftests/net/tuntap_helpers.h | 2 +- > 1 file changed, 1 insertion(+), 1 deletion(-) > > diff --git a/tools/testing/selftests/net/tuntap_helpers.h b/tools/testing/selftests/net/tuntap_helpers.h > index d6c0437136ec..ac9b2da57a6d 100644 > --- a/tools/testing/selftests/net/tuntap_helpers.h > +++ b/tools/testing/selftests/net/tuntap_helpers.h > @@ -335,7 +335,7 @@ static inline void build_udp_packet_csum(uint8_t *buf, int family, > sum += htons(IPPROTO_UDP) + udph->len; > > if (!csum_off) > - sum += add_csum(buf, udph->len); > + sum += add_csum(buf, ntohs(udph->len)); > > udph->check = finish_ip_csum(sum); If respinning: we probably should also do CSUM_MANGLED_0 conversion here. And to zero udph->check first. All existing callers do. But the calloc in patch 3/3 does not. > } > -- > 2.53.0 > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() 2026-11-03 20:31 ` [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() Eric Dumazet 2026-11-04 2:10 ` Willem de Bruijn @ 2026-11-04 20:31 ` sashiko-bot 1 sibling, 0 replies; 13+ messages in thread From: sashiko-bot @ 2026-11-04 20:31 UTC (permalink / raw) To: Eric Dumazet; +Cc: virtualization, Michael S. Tsirkin, Eugenio Perez > build_udp_packet_csum() passes udph->len, which is in network byte > order, as the number of bytes to checksum. On little-endian hosts this > reads up to 64KB past the UDP header and computes a wrong checksum. > > Existing callers did not crash only because they use packet buffers > of MAX_VNET_TUNNEL_PACKET_SZ bytes. > > Use ntohs(udph->len), as tap.c does in its own build_udp_packet(). > > Fixes: a942fcd72e97 ("selftest: tun: Introduce tuntap_helpers.h header for TUN/TAP testing") > Signed-off-by: Eric Dumazet <edumazet@kernel.org> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261103203113.550604-1-edumazet@kernel.org?part=2 ^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 [PATCH net 0/3] net: virtio: validate outer protocol of UDP tunnel GSO packets Eric Dumazet 2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet 2026-11-03 20:31 ` [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() Eric Dumazet @ 2026-11-03 20:31 ` Eric Dumazet 2026-11-04 2:19 ` Willem de Bruijn ` (2 more replies) 2 siblings, 3 replies; 13+ messages in thread From: Eric Dumazet @ 2026-11-03 20:31 UTC (permalink / raw) To: netdev, David S. Miller, Jakub Kicinski, Paolo Abeni Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin, Eugenio Pérez, Hassan Sultan, virtualization Add a tun_vnet_udptnl_proto fixture writing single 4in4 Geneve GSO frames to a tap device with UDP tunnel offloads enabled: - outer_proto_ok: an outer IPv4 header, with or without an 802.1Q tag, is accepted. - outer_proto_bad: an outer ethertype that does not match the declared tunnel type (IPv6 on an IPv4 tunnel, or NSH, with or without a VLAN tag) is rejected with EINVAL. Without the previous virtio_net patch, outer_proto_bad fails because the kernel accepts the frames. Signed-off-by: Eric Dumazet <edumazet@kernel.org> --- tools/testing/selftests/net/tun.c | 130 ++++++++++++++++++++++++++++++ 1 file changed, 130 insertions(+) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index 34de2580e32e..47a24b9c477b 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -683,6 +683,136 @@ TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) ASSERT_EQ(ret, len); } +#ifndef ETH_P_NSH +#define ETH_P_NSH 0x894F +#endif + +FIXTURE(tun_vnet_udptnl_proto) +{ + char ifname[IFNAMSIZ]; + uint8_t *pkt; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_udptnl_proto) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + int features = TUN_F_CSUM | TUN_F_UDP_TUNNEL_GSO | + TUN_F_UDP_TUNNEL_GSO_CSUM | TUN_F_USO4 | TUN_F_USO6; + + self->fd = -1; + if (unshare(CLONE_NEWNET)) + SKIP(return, "Cannot create network namespace: %s", + strerror(errno)); + + self->pkt = calloc(1, MAX_VNET_TUNNEL_PACKET_SZ); + ASSERT_NE(self->pkt, NULL); + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, TUN_VNET_TNL_SIZE, features, + param_hwaddr_outer_src); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_udptnl_proto) +{ + if (self->fd >= 0) + close(self->fd); + free(self->pkt); +} + +/* Build a 4in4 Geneve GSO frame whose outer ethertype is @l3proto, + * optionally behind an 802.1Q tag. + */ +static int build_udptnl_frame(uint8_t *buf, bool vlan, uint16_t l3proto, + int payload_len) +{ + struct virtio_net_hdr_v1_hash_tunnel *vh = (void *)buf; + int vlan_len = vlan ? 4 : 0; + int hlen = ETH_HLEN + vlan_len + UDP_TUNNEL_GENEVE_4IN4_HDRLEN; + uint8_t *outer_udph, *cur = buf; + int pktlen; + + cur += build_virtio_net_hdr_v1_hash_tunnel(cur, true, hlen, 1400, + AF_INET, AF_INET); + vh->outer_th_offset += vlan_len; + vh->inner_nh_offset += vlan_len; + vh->hash_hdr.hdr.csum_start += vlan_len; + + if (vlan) { + uint16_t vlan_tag[2] = { htons(100), htons(l3proto) }; + + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_dst, + param_hwaddr_outer_src); + memcpy(cur, vlan_tag, sizeof(vlan_tag)); + cur += sizeof(vlan_tag); + } else { + cur += build_eth(cur, l3proto, param_hwaddr_outer_dst, + param_hwaddr_outer_src); + } + + pktlen = UDP_TUNNEL_GENEVE_4IN4_HDRLEN - sizeof(struct iphdr) + + payload_len; + cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen, + ¶m_ipaddr4_outer_dst, + ¶m_ipaddr4_outer_src); + + outer_udph = cur; + cur += build_udp_header(cur, UDP_SRC_PORT, VN_PORT, + pktlen - sizeof(struct udphdr)); + cur += build_geneve_header(cur, VN_ID); + cur += build_eth(cur, ETH_P_IP, param_hwaddr_inner_dst, + param_hwaddr_inner_src); + cur += build_ipv4_header(cur, IPPROTO_UDP, + sizeof(struct udphdr) + payload_len, + ¶m_ipaddr4_inner_dst, + ¶m_ipaddr4_inner_src); + cur += build_udp_packet(cur, UDP_DST_PORT, UDP_SRC_PORT, payload_len, + AF_INET, false); + build_udp_packet_csum(outer_udph, AF_INET, false); + + return cur - buf; +} + +TEST_F(tun_vnet_udptnl_proto, outer_proto_ok) +{ + uint8_t *pkt = self->pkt; + int len, ret; + + len = build_udptnl_frame(pkt, false, ETH_P_IP, 2800); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* The outer IPv4 header can follow a VLAN tag. */ + len = build_udptnl_frame(pkt, true, ETH_P_IP, 2800); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); +} + +TEST_F(tun_vnet_udptnl_proto, outer_proto_bad) +{ + uint8_t *pkt = self->pkt; + int len, ret; + + /* Outer ethertype does not match the declared IPv4 tunnel. */ + len = build_udptnl_frame(pkt, false, ETH_P_IPV6, 2800); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* Outer header is not IP at all. */ + len = build_udptnl_frame(pkt, false, ETH_P_NSH, 2800); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* Same, behind a VLAN tag. */ + len = build_udptnl_frame(pkt, true, ETH_P_NSH, 2800); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; -- 2.53.0 ^ permalink raw reply related [flat|nested] 13+ messages in thread
* Re: [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 ` [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet @ 2026-11-04 2:19 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot 2026-11-05 8:32 ` netdev-bot+sashiko 2 siblings, 0 replies; 13+ messages in thread From: Willem de Bruijn @ 2026-11-04 2:19 UTC (permalink / raw) To: Eric Dumazet, netdev, David S. Miller, Jakub Kicinski, Paolo Abeni Cc: Eric Dumazet, Simon Horman, Willem de Bruijn, Michael S. Tsirkin, Eugenio Pérez, Hassan Sultan, virtualization Eric Dumazet wrote: > Add a tun_vnet_udptnl_proto fixture writing single 4in4 Geneve GSO > frames to a tap device with UDP tunnel offloads enabled: > > - outer_proto_ok: an outer IPv4 header, with or without an 802.1Q tag, > is accepted. > - outer_proto_bad: an outer ethertype that does not match the declared > tunnel type (IPv6 on an IPv4 tunnel, or NSH, with or without a VLAN > tag) is rejected with EINVAL. > > Without the previous virtio_net patch, outer_proto_bad fails because > the kernel accepts the frames. > > Signed-off-by: Eric Dumazet <edumazet@kernel.org> > --- > tools/testing/selftests/net/tun.c | 130 ++++++++++++++++++++++++++++++ > 1 file changed, 130 insertions(+) > > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > index 34de2580e32e..47a24b9c477b 100644 > --- a/tools/testing/selftests/net/tun.c > +++ b/tools/testing/selftests/net/tun.c > @@ -683,6 +683,136 @@ TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) > ASSERT_EQ(ret, len); > } > > +#ifndef ETH_P_NSH > +#define ETH_P_NSH 0x894F > +#endif > + > +FIXTURE(tun_vnet_udptnl_proto) > +{ > + char ifname[IFNAMSIZ]; > + uint8_t *pkt; > + int fd; > +}; > + > +FIXTURE_SETUP(tun_vnet_udptnl_proto) > +{ > + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; > + int features = TUN_F_CSUM | TUN_F_UDP_TUNNEL_GSO | > + TUN_F_UDP_TUNNEL_GSO_CSUM | TUN_F_USO4 | TUN_F_USO6; > + > + self->fd = -1; > + if (unshare(CLONE_NEWNET)) > + SKIP(return, "Cannot create network namespace: %s", > + strerror(errno)); > + > + self->pkt = calloc(1, MAX_VNET_TUNNEL_PACKET_SZ); > + ASSERT_NE(self->pkt, NULL); > + > + memset(self->ifname, 0, sizeof(self->ifname)); > + self->fd = tun_open(self->ifname, flags, TUN_VNET_TNL_SIZE, features, > + param_hwaddr_outer_src); > + ASSERT_GE(self->fd, 0); > +} > + > +FIXTURE_TEARDOWN(tun_vnet_udptnl_proto) > +{ > + if (self->fd >= 0) > + close(self->fd); > + free(self->pkt); > +} > + > +/* Build a 4in4 Geneve GSO frame whose outer ethertype is @l3proto, > + * optionally behind an 802.1Q tag. > + */ > +static int build_udptnl_frame(uint8_t *buf, bool vlan, uint16_t l3proto, > + int payload_len) > +{ > + struct virtio_net_hdr_v1_hash_tunnel *vh = (void *)buf; > + int vlan_len = vlan ? 4 : 0; > + int hlen = ETH_HLEN + vlan_len + UDP_TUNNEL_GENEVE_4IN4_HDRLEN; > + uint8_t *outer_udph, *cur = buf; > + int pktlen; > + zero buffer. It is zeroed by calloc, but reused for multiple packets. > + cur += build_virtio_net_hdr_v1_hash_tunnel(cur, true, hlen, 1400, > + AF_INET, AF_INET); > + vh->outer_th_offset += vlan_len; > + vh->inner_nh_offset += vlan_len; > + vh->hash_hdr.hdr.csum_start += vlan_len; > + > + if (vlan) { > + uint16_t vlan_tag[2] = { htons(100), htons(l3proto) }; > + > + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_dst, > + param_hwaddr_outer_src); > + memcpy(cur, vlan_tag, sizeof(vlan_tag)); > + cur += sizeof(vlan_tag); > + } else { > + cur += build_eth(cur, l3proto, param_hwaddr_outer_dst, > + param_hwaddr_outer_src); > + } > + > + pktlen = UDP_TUNNEL_GENEVE_4IN4_HDRLEN - sizeof(struct iphdr) + > + payload_len; > + cur += build_ipv4_header(cur, IPPROTO_UDP, pktlen, > + ¶m_ipaddr4_outer_dst, > + ¶m_ipaddr4_outer_src); > + > + outer_udph = cur; > + cur += build_udp_header(cur, UDP_SRC_PORT, VN_PORT, > + pktlen - sizeof(struct udphdr)); > + cur += build_geneve_header(cur, VN_ID); > + cur += build_eth(cur, ETH_P_IP, param_hwaddr_inner_dst, > + param_hwaddr_inner_src); > + cur += build_ipv4_header(cur, IPPROTO_UDP, > + sizeof(struct udphdr) + payload_len, > + ¶m_ipaddr4_inner_dst, > + ¶m_ipaddr4_inner_src); > + cur += build_udp_packet(cur, UDP_DST_PORT, UDP_SRC_PORT, payload_len, > + AF_INET, false); > + build_udp_packet_csum(outer_udph, AF_INET, false); > + > + return cur - buf; > +} > + > +TEST_F(tun_vnet_udptnl_proto, outer_proto_ok) > +{ > + uint8_t *pkt = self->pkt; > + int len, ret; > + > + len = build_udptnl_frame(pkt, false, ETH_P_IP, 2800); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, len); > + > + /* The outer IPv4 header can follow a VLAN tag. */ > + len = build_udptnl_frame(pkt, true, ETH_P_IP, 2800); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, len); > +} > + > +TEST_F(tun_vnet_udptnl_proto, outer_proto_bad) > +{ > + uint8_t *pkt = self->pkt; > + int len, ret; > + > + /* Outer ethertype does not match the declared IPv4 tunnel. */ > + len = build_udptnl_frame(pkt, false, ETH_P_IPV6, 2800); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, -1); > + ASSERT_EQ(errno, EINVAL); > + > + /* Outer header is not IP at all. */ > + len = build_udptnl_frame(pkt, false, ETH_P_NSH, 2800); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, -1); > + ASSERT_EQ(errno, EINVAL); > + > + /* Same, behind a VLAN tag. */ > + len = build_udptnl_frame(pkt, true, ETH_P_NSH, 2800); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, -1); > + ASSERT_EQ(errno, EINVAL); > +} > + > FIXTURE(tun_vnet_udptnl) > { > char ifname[IFNAMSIZ]; > -- > 2.53.0 > ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 ` [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet 2026-11-04 2:19 ` Willem de Bruijn @ 2026-11-04 20:31 ` sashiko-bot 2026-11-05 8:32 ` netdev-bot+sashiko 2 siblings, 0 replies; 13+ messages in thread From: sashiko-bot @ 2026-11-04 20:31 UTC (permalink / raw) To: Eric Dumazet; +Cc: virtualization, Michael S. Tsirkin, Eugenio Perez > Add a tun_vnet_udptnl_proto fixture writing single 4in4 Geneve GSO > frames to a tap device with UDP tunnel offloads enabled: > > - outer_proto_ok: an outer IPv4 header, with or without an 802.1Q tag, > is accepted. > - outer_proto_bad: an outer ethertype that does not match the declared > tunnel type (IPv6 on an IPv4 tunnel, or NSH, with or without a VLAN > tag) is rejected with EINVAL. > > Without the previous virtio_net patch, outer_proto_bad fails because > the kernel accepts the frames. > > Signed-off-by: Eric Dumazet <edumazet@kernel.org> Sashiko has reviewed this patch and found no issues. -- Sashiko AI review · https://sashiko.dev/#/patchset/20261103203113.550604-1-edumazet@kernel.org?part=3 ^ permalink raw reply [flat|nested] 13+ messages in thread
* Re: [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets 2026-11-03 20:31 ` [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet 2026-11-04 2:19 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot @ 2026-11-05 8:32 ` netdev-bot+sashiko 2 siblings, 0 replies; 13+ messages in thread From: netdev-bot+sashiko @ 2026-11-05 8:32 UTC (permalink / raw) To: edumazet Cc: netdev, davem, kuba, pabeni, horms, willemb, mst, eperezma, hassansultan, virtualization Thank you for your contribution! Sashiko AI review found more severe issue(s) in other patches of this series. This mail is only an FYI, in case you want to address the nit picks below when you respin. Low priority / nit picks for your consideration: - [Low] The new tests cover only part of the fix they are meant to validate. - [Low] virtio_net_tnl_outer_proto_ok() in include/linux/virtio_net.h was added by the series commit "net: virtio: validate outer protocol of… - [Low] Both tun_vnet_udptnl_proto tests build several frames in the same calloc'd buffer (self->pkt) without clearing it in between. -- selftests: net: tun: test outer protocol of UDP tunnel GSO packets This adds a tun_vnet_udptnl_proto fixture that writes single 4in4 Geneve GSO frames to a tap device with UDP tunnel offloads enabled. outer_proto_ok expects outer IPv4 frames, optionally VLAN tagged, to be accepted. outer_proto_bad expects IPv6 or NSH outer ethertypes to be rejected with EINVAL. > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > index 34de2580e32ec..47a24b9c477bb 100644 > --- a/tools/testing/selftests/net/tun.c > +++ b/tools/testing/selftests/net/tun.c > @@ -683,6 +683,136 @@ TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum_64k) [ ... ] > +FIXTURE_SETUP(tun_vnet_udptnl_proto) > +{ > + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; [Severity: Low] This fixture only opens IFF_TAP devices. That means the IFF_TUN branch of virtio_net_tnl_outer_proto_ok() from the previous patch is never run. Does the assumption behind that branch hold? include/linux/virtio_net.h:virtio_net_tnl_outer_proto_ok() { ... /* L3 device (IFF_TUN): the caller already set the protocol. */ if (proto) return proto == expected; /* Ethernet frame, possibly VLAN tagged. */ proto = eth_hdr(skb)->h_proto; ... } The previous patch's commit message also says "For IFF_TUN, skb->protocol is already set by the caller." For IFF_TUN without IFF_NO_PI, tun_get_user() in drivers/net/tun.c copies struct tun_pi from userspace. It stores the value as-is before tun_vnet_hdr_tnl_to_skb() runs: skb_reset_network_header(skb); skb->protocol = pi.proto; pi.proto can be 0 here. In that case, would the helper take the Ethernet branch for an L3 packet? The mac header was reset to the start of the outer IP header. So h_proto would be read from the first two bytes of the IPv4 saddr, and nh_off would be measured from ETH_HLEN. For example, a 4in4 frame with an outer saddr of 8.0.x.x and outer_th >= 34 (IPv4 options) would pass the check. The skb would then go into the stack with SKB_GSO_UDP_TUNNEL set and skb->protocol == 0, which the comment says can't happen. With other saddr values the same packet would be rejected. The downstream impact looks limited, because skb_mac_gso_segment() returns -EINVAL when skb_network_protocol() gives 0. Even so, should the helper or its comment handle this case? Would an IFF_TUN case in this fixture be useful? [ ... ] > +static int build_udptnl_frame(uint8_t *buf, bool vlan, uint16_t l3proto, > + int payload_len) > +{ > + struct virtio_net_hdr_v1_hash_tunnel *vh = (void *)buf; > + int vlan_len = vlan ? 4 : 0; > + int hlen = ETH_HLEN + vlan_len + UDP_TUNNEL_GENEVE_4IN4_HDRLEN; > + uint8_t *outer_udph, *cur = buf; > + int pktlen; > + > + cur += build_virtio_net_hdr_v1_hash_tunnel(cur, true, hlen, 1400, > + AF_INET, AF_INET); > + vh->outer_th_offset += vlan_len; [Severity: Low] outer_th_offset is always shifted by vlan_len, and the outer family is always AF_INET. Given that, do these tests reach all of the checks added by "net: virtio: validate outer protocol of UDP tunnel GSO packets"? virtio_net_tnl_outer_proto_ok() ends with: return proto == expected && nh_off + virtio_l3min(outer_isv6) <= outer_th; The length clause can only fail when a VLAN tag moves nh_off to 18 or more. Without a tag it is the same as the existing outer_l3min check. No case here sends a VLAN tagged IPv4 frame with outer_th_offset left at 34. If that clause were removed or miscomputed (for example in a stable backport), no test would fail. Every frame also declares VIRTIO_NET_HDR_GSO_UDP_TUNNEL_IPV4. So the outer_isv6 side of expected is never tested either, for example an IPv4 ethertype on a declared IPv6 tunnel. Separately, the commit message says outer_proto_bad covers: (IPv6 on an IPv4 tunnel, or NSH, with or without a VLAN tag) outer_proto_bad actually tests IPv6 untagged, NSH untagged and NSH behind a VLAN tag. IPv6 behind a VLAN tag is not tested. Should the message be narrowed, or should that case be added? [ ... ] > +TEST_F(tun_vnet_udptnl_proto, outer_proto_ok) > +{ > + uint8_t *pkt = self->pkt; > + int len, ret; > + > + len = build_udptnl_frame(pkt, false, ETH_P_IP, 2800); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, len); > + > + /* The outer IPv4 header can follow a VLAN tag. */ > + len = build_udptnl_frame(pkt, true, ETH_P_IP, 2800); [Severity: Low] Is the VLAN tagged frame here well formed? self->pkt is calloc'd once in FIXTURE_SETUP and is not cleared between frames. The helpers in tuntap_helpers.h expect a zeroed buffer. build_ipv4_header() never writes tos or frag_off. It also sums over whatever is already in the check field: iph->check = build_ip_csum(buf, iph->ihl << 2, 0); build_udp_packet_csum() also sums over the existing udph->check. The existing tun_vnet_udptnl test clears its buffer first in send_gso_packet(): memset(pkt, 0, sizeof(pkt)); In this test, the second frame's outer IPv4 header starts 4 bytes after the first frame's. As a result: - frag_off holds the previous outer IPv4 checksum - tos holds the low byte of the previous IP ID - the checksum is computed starting from stale saddr bytes The inner IPv4 header and the inner UDP checksum are shifted and corrupted in the same way. This only passes today because the kernel path checks just the offsets and the ethertype. If that path ever validates the outer IPv4 header, for example by rejecting fragments for GSO, outer_proto_ok would fail for an unrelated reason. Would a memset at the start of build_udptnl_frame() avoid this? [ ... ] -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261103203113.550604-1-edumazet%40kernel.org ^ permalink raw reply [flat|nested] 13+ messages in thread
end of thread, other threads:[~2026-11-05 8:32 UTC | newest] Thread overview: 13+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-11-03 20:31 [PATCH net 0/3] net: virtio: validate outer protocol of UDP tunnel GSO packets Eric Dumazet 2026-11-03 20:31 ` [PATCH net 1/3] " Eric Dumazet 2026-11-04 2:01 ` Willem de Bruijn 2026-11-04 4:49 ` Eric Dumazet 2026-11-04 20:31 ` sashiko-bot 2026-11-05 8:32 ` netdev-bot+sashiko 2026-11-03 20:31 ` [PATCH net 2/3] selftests: net: tuntap: fix UDP checksum length in build_udp_packet_csum() Eric Dumazet 2026-11-04 2:10 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot 2026-11-03 20:31 ` [PATCH net 3/3] selftests: net: tun: test outer protocol of UDP tunnel GSO packets Eric Dumazet 2026-11-04 2:19 ` Willem de Bruijn 2026-11-04 20:31 ` sashiko-bot 2026-11-05 8:32 ` netdev-bot+sashiko
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.