* [PATCH v2 net 0/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() @ 2026-09-28 14:42 Eric Dumazet 2026-09-28 14:42 ` [PATCH v2 net 1/2] " Eric Dumazet 2026-09-28 14:42 ` [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Eric Dumazet 0 siblings, 2 replies; 9+ messages in thread From: Eric Dumazet @ 2026-09-28 14:42 UTC (permalink / raw) To: David S . Miller, Jakub Kicinski, Paolo Abeni Cc: Simon Horman, Willem de Bruijn, Michael S . Tsirkin, netdev, edumazet This series fixes a bypass of untrusted GSO flow dissection in __virtio_net_hdr_to_skb() when VIRTIO_NET_HDR_F_NEEDS_CSUM is not set, and adds a kselftest covering VLAN-tagged GSO packets without NEEDS_CSUM: - Patch 1 initializes skb->dev and skb->network_header before calling virtio_net_hdr_*_to_skb() in tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header(), removes the '&& skb->network_header' condition and the unvalidated 'else if (gso_type)' fallback in __virtio_net_hdr_to_skb(), and moves virtio_net_hdr_match_proto() after skb_flow_dissect_flow_keys_basic() so it validates the dissected L3 protocol (keys.basic.n_proto) rather than the outer L2 protocol. - Patch 2 adds a kselftest in tools/testing/selftests/net/tun.c verifying that VLAN-tagged (802.1Q) TCPv4 GSO packets without NEEDS_CSUM (both flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID) are accepted on a TAP device, and that mismatched GSO types and truncated headers without NEEDS_CSUM are rejected with -EINVAL. v2: - Patch 1: drop the pre-dissection virtio_net_hdr_match_proto() check inside 'if (!skb->protocol)' so VLAN-tagged GSO frames without NEEDS_CSUM are not rejected before flow dissection (Michael S. Tsirkin). - Patch 1: clarify the changelog regarding why skb->network_header was 0 in those callers and why skb_reset_mac_header() is dropped in tun_get_user() for IFF_TUN (Michael S. Tsirkin). - Patch 2: add selftest in tools/testing/selftests/net/tun.c based on Michael's reproducer. - Link to v1: https://lore.kernel.org/netdev/20260927195536.2489079-1-edumazet@google.com/ Eric Dumazet (2): net: always dissect GSO packets in __virtio_net_hdr_to_skb() selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM arch/um/drivers/vector_transports.c | 1 + drivers/net/tun.c | 23 +++--- drivers/net/virtio_net.c | 2 + include/linux/virtio_net.h | 60 +++++++--------- tools/testing/selftests/net/tun.c | 107 ++++++++++++++++++++++++++++ 5 files changed, 149 insertions(+), 44 deletions(-) -- 2.56.0.rc1.315.gc6ed9934b7-goog ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 net 1/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() 2026-09-28 14:42 [PATCH v2 net 0/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() Eric Dumazet @ 2026-09-28 14:42 ` Eric Dumazet 2026-10-01 17:52 ` netdev-bot+sashiko 2026-09-28 14:42 ` [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Eric Dumazet 1 sibling, 1 reply; 9+ messages in thread From: Eric Dumazet @ 2026-09-28 14:42 UTC (permalink / raw) To: David S . Miller, Jakub Kicinski, Paolo Abeni Cc: Simon Horman, Willem de Bruijn, Michael S . Tsirkin, netdev, edumazet, Weiming Shi Commit 9e8db5913264 ("net: avoid false positives in untrusted gso validation") added a '&& skb->network_header' check before flow-dissecting GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM in __virtio_net_hdr_to_skb(), because some callers (such as tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header()) called virtio_net_hdr_*_to_skb() before initializing skb->network_header and skb->dev. Because __alloc_skb() and __build_skb_around() zero-initialize skb->network_header to 0 (unlike mac_header and transport_header which are initialized to ~0U), those four callers always had skb->network_header == 0 and bypassed flow dissection in __virtio_net_hdr_to_skb(). More generally, skb->network_header is an offset from skb->head (where 0 is also a valid offset whenever skb_headroom(skb) is 0), not a boolean flag. Whenever the 'if (gso_type && skb->network_header)' branch was skipped, the fallback 'else if (gso_type)' only pulled nh_min_len + thlen (40 bytes for TCPv4) without dissecting the packet, without validating ip_proto or n_proto, and without setting skb->transport_header. If the packet has a malformed network header, it is not rejected and a subsequent skb_probe_transport_header() also fails, leaving skb->transport_header at ~0U (0xffff). Similarly, if an IPv4 packet carries IP options (ihl > 5) or an IPv6 packet carries extension headers, pulling only nh_min_len + thlen can leave the TCP header outside skb->head. In both cases, tcp_hdrlen(skb) in skb_gso_transport_seglen() reads out-of-bounds: BUG: KASAN: slab-out-of-bounds in skb_gso_transport_seglen Read of size 2 by task poc/133 skb_gso_transport_seglen (net/core/gso.c:155) skb_gso_validate_mac_len (net/core/gso.c:270) tbf_enqueue (net/sched/sch_tbf.c:260) dev_qdisc_enqueue (net/core/dev.c:4227) __dev_queue_xmit (net/core/dev.c:4884) In addition, checking virtio_net_hdr_match_proto() only inside 'if (!skb->protocol)' before flow dissection both skipped validation when skb->protocol was pre-set by the caller and rejected VLAN-tagged frames whose outer L2 protocol is ETH_P_8021Q or ETH_P_8021AD. Fix this by: 1. Initializing skb->dev and skb->network_header (plus skb->protocol for IFF_TUN) before virtio_net_hdr_*_to_skb() in tun_get_user(), tun_xdp_one(), virtnet_receive_done(), and raw_verify_header(). In tun_get_user(), drop the redundant skb_reset_mac_header(skb) in the IFF_TUN case since __virtio_net_hdr_to_skb() unconditionally resets mac_header. 2. Removing '&& skb->network_header' and the unvalidated 'else if (gso_type)' fallback in __virtio_net_hdr_to_skb() so all GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM are flow-dissected, have their transport header pulled into linear data, and have skb->transport_header set. 3. Moving the virtio_net_hdr_match_proto() check to after skb_flow_dissect_flow_keys_basic(), validating keys.basic.n_proto against hdr_gso_type. Fixes: 9e8db5913264 ("net: avoid false positives in untrusted gso validation") Fixes: d5be7f632bad ("net: validate untrusted gso packets without csum offload") Fixes: 924a9bc362a5 ("net: check if protocol extracted by virtio_net_hdr_set_proto is correct") Reported-by: Weiming Shi <bestswngs@gmail.com> Closes: https://lore.kernel.org/netdev/20260927163117.746432-2-bestswngs@gmail.com/ Assisted-by: LLM Signed-off-by: Eric Dumazet <edumazet@kernel.org> Reviewed-by: Willem de Bruijn <willemb@google.com> --- arch/um/drivers/vector_transports.c | 1 + drivers/net/tun.c | 23 ++++++----- drivers/net/virtio_net.c | 2 + include/linux/virtio_net.h | 60 ++++++++++++----------------- 4 files changed, 42 insertions(+), 44 deletions(-) diff --git a/arch/um/drivers/vector_transports.c b/arch/um/drivers/vector_transports.c index ddd127ee96785daa4c485b2a06f078686efd2046..e1fb2a76fdff59ef3032d091be06a06fa58046cc 100644 --- a/arch/um/drivers/vector_transports.c +++ b/arch/um/drivers/vector_transports.c @@ -209,6 +209,7 @@ static int raw_verify_header( if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0) return 1; + skb_set_network_header(skb, ETH_HLEN); virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian()); return 0; } diff --git a/drivers/net/tun.c b/drivers/net/tun.c index 5a302709a68aa3308b0c850b4a2957df3260b352..242899f7fd0711c5c59ff85accbf5bd1be9c6f39 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -1897,12 +1897,7 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, &hdr)) { - atomic_long_inc(&tun->rx_frame_errors); - err = -EINVAL; - goto free_skb; - } - + skb->dev = tun->dev; switch (tun->flags & TUN_TYPE_MASK) { case IFF_TUN: if (tun->flags & IFF_NO_PI) { @@ -1927,9 +1922,8 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, } } - skb_reset_mac_header(skb); + skb_reset_network_header(skb); skb->protocol = pi.proto; - skb->dev = tun->dev; break; case IFF_TAP: if (!pskb_may_pull(skb, ETH_HLEN)) { @@ -1937,10 +1931,19 @@ static ssize_t tun_get_user(struct tun_struct *tun, struct tun_file *tfile, drop_reason = SKB_DROP_REASON_HDR_TRUNC; goto drop; } - skb->protocol = eth_type_trans(skb, tun->dev); + skb_set_network_header(skb, ETH_HLEN); break; } + 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); + /* copy skb_ubuf_info for callback when skb has no error */ if (zerocopy) { skb_zcopy_init(skb, msg_control); @@ -2600,6 +2603,8 @@ static int tun_xdp_one(struct tun_struct *tun, features = tun_vnet_hdr_guest_features(READ_ONCE(tun->vnet_hdr_sz)); tnl_hdr = (struct virtio_net_hdr_v1_hash_tunnel *)gso; + skb->dev = tun->dev; + skb_set_network_header(skb, ETH_HLEN); if (tun_vnet_hdr_tnl_to_skb(tun->flags, features, skb, tnl_hdr)) { atomic_long_inc(&tun->rx_frame_errors); kfree_skb(skb); diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c index bf82ef9874abb4094931496fb6064a12ee75b789..daab43ac92ce4276b0f8f88684991e3993022ce6 100644 --- a/drivers/net/virtio_net.c +++ b/drivers/net/virtio_net.c @@ -2515,6 +2515,8 @@ static void virtnet_receive_done(struct virtnet_info *vi, struct receive_queue * goto frame_err; } + skb->dev = dev; + skb_set_network_header(skb, ETH_HLEN); if (virtio_net_hdr_tnl_to_skb(skb, &hdr->tnl_hdr, vi->rx_tnl, vi->rx_tnl_csum, virtio_is_little_endian(vi->vdev))) { diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h index c381b916c1b54afacba5473888af589a24fe087c..d6466f96cdd00cdf059d4fa8842ba1672780e556 100644 --- a/include/linux/virtio_net.h +++ b/include/linux/virtio_net.h @@ -111,48 +111,38 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, p_off = nh_min_len + thlen; if (!pskb_may_pull(skb, p_off)) return -EINVAL; - } else { + } else if (gso_type) { /* gso packets without NEEDS_CSUM do not set transport_offset. * probe and drop if does not match one of the above types. */ - if (gso_type && skb->network_header) { - struct flow_keys_basic keys; - - if (!skb->protocol) { - __be16 protocol = dev_parse_header_protocol(skb); - - if (!protocol) - virtio_net_hdr_set_proto(skb, hdr); - else if (!virtio_net_hdr_match_proto(protocol, - hdr_gso_type)) - return -EINVAL; - else - skb->protocol = protocol; - } + struct flow_keys_basic keys; + + if (!skb->protocol) { + skb->protocol = dev_parse_header_protocol(skb); + if (!skb->protocol) + virtio_net_hdr_set_proto(skb, hdr); + } retry: - if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, - NULL, 0, 0, 0, - 0)) { - /* UFO does not specify ipv4 or 6: try both */ - if (gso_type & SKB_GSO_UDP && - skb->protocol == htons(ETH_P_IP)) { - skb->protocol = htons(ETH_P_IPV6); - goto retry; - } - return -EINVAL; + if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, + NULL, 0, 0, 0, + 0)) { + /* UFO does not specify ipv4 or 6: try both */ + if (gso_type & SKB_GSO_UDP && + skb->protocol == htons(ETH_P_IP)) { + skb->protocol = htons(ETH_P_IPV6); + goto retry; } + return -EINVAL; + } - p_off = keys.control.thoff + thlen; - if (!pskb_may_pull(skb, p_off) || - keys.basic.ip_proto != ip_proto) - return -EINVAL; + p_off = keys.control.thoff + thlen; + if (!pskb_may_pull(skb, p_off) || + keys.basic.ip_proto != ip_proto || + !virtio_net_hdr_match_proto(keys.basic.n_proto, + hdr_gso_type)) + return -EINVAL; - skb_set_transport_header(skb, keys.control.thoff); - } else if (gso_type) { - p_off = nh_min_len + thlen; - if (!pskb_may_pull(skb, p_off)) - return -EINVAL; - } + skb_set_transport_header(skb, keys.control.thoff); } if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { -- 2.56.0.rc1.315.gc6ed9934b7-goog ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 net 1/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() 2026-09-28 14:42 ` [PATCH v2 net 1/2] " Eric Dumazet @ 2026-10-01 17:52 ` netdev-bot+sashiko 2026-10-01 18:34 ` Eric Dumazet 0 siblings, 1 reply; 9+ messages in thread From: netdev-bot+sashiko @ 2026-10-01 17:52 UTC (permalink / raw) To: edumazet; +Cc: davem, kuba, pabeni, horms, willemb, mst, netdev Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider. Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0 (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). - [Medium] `__skb_flow_dissect()` sets `key_control->thoff = min_t(u16, nhoff, skb ? Pre-existing issues: - [Medium] In af_packet, `packet_parse_headers()` runs before `virtio_net_hdr_to_skb()` (`packet_snd()` lines 3130/3133;… - [Low] In the UML vector driver, `prep_skb()` sets `skb->len` to the full receive-buffer capacity (`skb_put(result, vp->max_packet)`, plus… -- net: always dissect GSO packets in __virtio_net_hdr_to_skb() This patch drops the skb->network_header condition and the unvalidated fallback in __virtio_net_hdr_to_skb(). Every GSO packet without VIRTIO_NET_HDR_F_NEEDS_CSUM is now flow-dissected and gets a transport header. The tun, virtio_net and UML vector callers now set skb->dev and skb->network_header before the conversion, and the virtio_net_hdr_match_proto() check now runs after dissection. Closes: https://lore.kernel.org/netdev/20260927163117.746432-2-bestswngs@gmail.com/ > diff --git a/arch/um/drivers/vector_transports.c b/arch/um/drivers/vector_transports.c > index ddd127ee9678..e1fb2a76fdff 100644 > --- a/arch/um/drivers/vector_transports.c > +++ b/arch/um/drivers/vector_transports.c > @@ -209,6 +209,7 @@ static int raw_verify_header( > if ((vheader->flags & VIRTIO_NET_HDR_F_DATA_VALID) > 0) > return 1; > > + skb_set_network_header(skb, ETH_HLEN); > virtio_net_hdr_to_skb(skb, vheader, virtio_legacy_is_little_endian()); [Severity: Low] This isn't a bug introduced by this patch, but raw_verify_header() now takes the dissection path. At this point, isn't skb->len still the full receive buffer capacity rather than the length actually received? prep_skb() sizes the skb up front: arch/um/drivers/vector_kern.c:prep_skb() { ... skb_put(result, vp->max_packet); result->data_len = len - vp->max_packet; ... } Both vector_legacy_rx() and vector_mmsg_rx() call verify_header() before pskb_trim() cuts the skb down to the received length: vector_legacy_rx() header_check = vp->verify_header(vp->header_rxbuffer, skb, vp); ... pskb_trim(skb, pkt_len - vp->rx_header_size); So the dissection, pskb_may_pull() of thoff + thlen and skb_set_transport_header() all work on stale buffer bytes past the received length. After the trim, can a short frame with a GSO vnet header end up with transport_header and GSO metadata beyond skb->tail? The reads stay inside the allocated linear head. Triggering this needs the host peer to send a GSO vnet header for a frame shorter than its headers. The verify-before-trim ordering predates this patch, and the old fallback also set gso_size/gso_type from the untrimmed length. > return 0; > } [ ... ] > diff --git a/include/linux/virtio_net.h b/include/linux/virtio_net.h > index c381b916c1b5..d6466f96cdd0 100644 > --- a/include/linux/virtio_net.h > +++ b/include/linux/virtio_net.h > @@ -111,48 +111,38 @@ static inline int __virtio_net_hdr_to_skb(struct sk_buff *skb, > p_off = nh_min_len + thlen; > if (!pskb_may_pull(skb, p_off)) > return -EINVAL; > - } else { > + } else if (gso_type) { > /* gso packets without NEEDS_CSUM do not set transport_offset. > * probe and drop if does not match one of the above types. > */ [ ... ] > + struct flow_keys_basic keys; > + > + if (!skb->protocol) { > + skb->protocol = dev_parse_header_protocol(skb); > + if (!skb->protocol) > + virtio_net_hdr_set_proto(skb, hdr); > + } > retry: [ ... ] > + if (!skb_flow_dissect_flow_keys_basic(NULL, skb, &keys, > + NULL, 0, 0, 0, > + 0)) { > + /* UFO does not specify ipv4 or 6: try both */ > + if (gso_type & SKB_GSO_UDP && > + skb->protocol == htons(ETH_P_IP)) { > + skb->protocol = htons(ETH_P_IPV6); > + goto retry; > } > + return -EINVAL; > + } [Severity: Medium] This is a pre-existing issue and was not introduced by this patch. The commit message mentions VLAN-tagged frames, though. For af_packet VLAN frames, does this dissector call get a protocol and a network offset that describe different headers? packet_snd() calls packet_parse_headers() before virtio_net_hdr_to_skb(). tpacket_snd() does the same through tpacket_fill_skb(): net/packet/af_packet.c:packet_parse_headers() { ... vlan_get_protocol_and_depth(skb, skb->protocol, &depth) != 0) skb_set_network_header(skb, depth); } For VLAN-tagged frames this moves network_header past the tags, but skb->protocol stays ETH_P_8021Q or ETH_P_8021AD. The call here passes data == NULL, so the dissector uses proto = skb->protocol and nhoff = skb_network_offset(skb). It then reads the inner L3 header as a vlan_hdr: net/core/flow_dissector.c:__skb_flow_dissect() { ... proto = vlan->h_vlan_encapsulated_proto; ... } That value would come from the IPv4 tot_len field or the IPv6 payload length bytes. Would VLAN GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM sent through AF_PACKET with PACKET_VNET_HDR then fail with -EINVAL? And could they be misparsed if those bytes happen to form a valid ethertype? tap_get_user() keeps network_header at ETH_HLEN while it calls tun_vnet_hdr_to_skb(), and only moves it for VLAN afterwards. That is the layout this helper expects. The af_packet network header adjustment comes from commit dfed913e8b55a ("net/af_packet: add VLAN support for AF_PACKET SOCK_RAW GSO"). These skbs already hit the same dissector call before this patch. > > - p_off = keys.control.thoff + thlen; > - if (!pskb_may_pull(skb, p_off) || > - keys.basic.ip_proto != ip_proto) > - return -EINVAL; > + p_off = keys.control.thoff + thlen; > + if (!pskb_may_pull(skb, p_off) || > + keys.basic.ip_proto != ip_proto || > + !virtio_net_hdr_match_proto(keys.basic.n_proto, > + hdr_gso_type)) > + return -EINVAL; > > - skb_set_transport_header(skb, keys.control.thoff); > - } else if (gso_type) { > - p_off = nh_min_len + thlen; > - if (!pskb_may_pull(skb, p_off)) > - return -EINVAL; > - } > + skb_set_transport_header(skb, keys.control.thoff); [Severity: Medium] Can keys.control.thoff be truncated here for large skbs? __skb_flow_dissect() ends with: net/core/flow_dissector.c:__skb_flow_dissect() { ... out: key_control->thoff = min_t(u16, nhoff, skb ? skb->len : hlen); ... } min_t(u16, ...) casts skb->len to 16 bits. When skb->len is in [65536, 65536 + nhoff), the result wraps to a small value. With this patch, tun_get_user() calls tun_vnet_hdr_tnl_to_skb() before eth_type_trans(): 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); So skb->len still includes the 14-byte Ethernet header during dissection. Take an IFF_TAP frame with skb->len = 65540 and a plain IPv4/TCP header (real nhoff of 34). That gives thoff = (u16)65540 = 4. ip_proto and n_proto are still correct, so every check above passes. pskb_may_pull() pulls only 24 bytes, and skb_set_transport_header(skb, 4) points into the destination MAC. The later skb_probe_transport_header() in tun_get_user() does nothing, because the transport header is already set. Before this patch, tun got its transport header from skb_probe_transport_header() after eth_type_trans() had pulled 14 bytes. Frames of 65536..65549 bytes were not affected. This does not look like an out-of-bounds read, since transport_header + 12 falls inside the pulled thoff + thlen bytes. But would tcp_hdrlen() in skb_gso_transport_seglen() and qdisc_pkt_len_init() then read the wrong bytes? That would give wrong qdisc pkt_len and gso_segs accounting, and a wrong tbf MAC length check. It also seems to contradict this part of the commit message: so all GSO packets without VIRTIO_NET_HDR_F_NEEDS_CSUM are flow-dissected, have their transport header pulled into linear data, and have skb->transport_header set. In this case the real TCP header at offset 34..54 can stay in page frags. This is reachable through IFF_TAP with IFF_VNET_HDR, from a vhost-net guest or an unprivileged tun owner in a user namespace. tun_alloc_skb() allows skb->len well above 64K. tun_xdp_one() does not look affected, since datasize is limited to one page. > } > > if (hdr_gso_type != VIRTIO_NET_HDR_GSO_NONE) { -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260928144254.3361044-1-edumazet%40kernel.org ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 net 1/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() 2026-10-01 17:52 ` netdev-bot+sashiko @ 2026-10-01 18:34 ` Eric Dumazet 0 siblings, 0 replies; 9+ messages in thread From: Eric Dumazet @ 2026-10-01 18:34 UTC (permalink / raw) To: netdev-bot+sashiko; +Cc: davem, kuba, pabeni, horms, willemb, mst, netdev On Thu, Oct 1, 2026 at 7:52 PM <netdev-bot+sashiko@kernel.org> wrote: > > Thank you for your contribution! Sashiko AI review found 1 potential > issue(s) to consider. > > Critical: 0 · High: 0 · Medium: 1 (1) · Low: 0 (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). > > - [Medium] `__skb_flow_dissect()` sets `key_control->thoff = min_t(u16, > nhoff, skb ? Allright. I will fix __skb_flow_dissect() in a separate patch. pw-bot: cr ^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM 2026-09-28 14:42 [PATCH v2 net 0/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() Eric Dumazet 2026-09-28 14:42 ` [PATCH v2 net 1/2] " Eric Dumazet @ 2026-09-28 14:42 ` Eric Dumazet 2026-09-28 19:17 ` Willem de Bruijn 2026-10-01 17:52 ` netdev-bot+sashiko 1 sibling, 2 replies; 9+ messages in thread From: Eric Dumazet @ 2026-09-28 14:42 UTC (permalink / raw) To: David S . Miller, Jakub Kicinski, Paolo Abeni Cc: Simon Horman, Willem de Bruijn, Michael S . Tsirkin, netdev, edumazet Add a selftest in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). Also verify that mismatched GSO types (e.g. VIRTIO_NET_HDR_GSO_TCPV6 on a VLAN-tagged IPv4 packet) and truncated headers without NEEDS_CSUM are rejected with -EINVAL. Based on a reproducer by Michael S. Tsirkin <mst@redhat.com>. Assisted-by: LLM Signed-off-by: Eric Dumazet <edumazet@kernel.org> --- tools/testing/selftests/net/tun.c | 107 ++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c index abe488bac50bb3b05df5c446836c1c3dfa9ea604..c6afafb7b957360e2873640e60a7d580c6564a66 100644 --- a/tools/testing/selftests/net/tun.c +++ b/tools/testing/selftests/net/tun.c @@ -9,6 +9,7 @@ #include <string.h> #include <unistd.h> #include <linux/if_tun.h> +#include <netinet/tcp.h> #include <sys/ioctl.h> #include <sys/socket.h> @@ -542,6 +543,112 @@ TEST_F(tun, reattach_close_delete) EXPECT_EQ(tun_delete(self->ifname), 0); } +FIXTURE(tun_vnet_gso) +{ + char ifname[IFNAMSIZ]; + int fd; +}; + +FIXTURE_SETUP(tun_vnet_gso) +{ + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; + + memset(self->ifname, 0, sizeof(self->ifname)); + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); + ASSERT_GE(self->fd, 0); +} + +FIXTURE_TEARDOWN(tun_vnet_gso) +{ + if (self->fd >= 0) + close(self->fd); +} + +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) +{ + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); + struct virtio_net_hdr vh = { 0 }; + struct tcphdr tcph = { 0 }; + uint32_t sum; + + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, + param_hwaddr_outer_dst); + + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ + memcpy(cur, vlan_tag, sizeof(vlan_tag)); + cur += sizeof(vlan_tag); + + cur += build_ipv4_header(cur, IPPROTO_TCP, + sizeof(tcph) + payload_len, + ¶m_ipaddr4_outer_src, + ¶m_ipaddr4_outer_dst); + + tcph.source = htons(12345); + tcph.dest = htons(80); + tcph.seq = htonl(1); + tcph.doff = sizeof(tcph) / 4; + tcph.ack = 1; + tcph.window = htons(65535); + memcpy(cur, &tcph, sizeof(tcph)); + memset(cur + sizeof(tcph), PKT_DATA, payload_len); + + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, + sizeof(param_ipaddr4_outer_src)); + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, + sizeof(param_ipaddr4_outer_dst)); + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); + sum += add_csum(cur, sizeof(tcph) + payload_len); + tcph.check = finish_ip_csum(sum); + memcpy(cur, &tcph, sizeof(tcph)); + cur += sizeof(tcph) + payload_len; + + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.gso_size = 1400; + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; + memcpy(buf, &vh, sizeof(vh)); + + return cur - buf; +} + +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) +{ + struct virtio_net_hdr vh; + uint8_t pkt[4096] = { 0 }; + int len, ret; + + len = build_vlan_tcpv4_gso_packet(pkt, 2800); + memcpy(&vh, pkt, sizeof(vh)); + + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ + vh.flags = 0; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ + vh.flags = VIRTIO_NET_HDR_F_DATA_VALID; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, len); + + /* Mismatched GSO type (TCPV6 on VLAN-tagged IPv4 packet) */ + vh.flags = 0; + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); + + /* Truncated TCP header without NEEDS_CSUM */ + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr); + memcpy(pkt, &vh, sizeof(vh)); + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); + ASSERT_EQ(ret, -1); + ASSERT_EQ(errno, EINVAL); +} + FIXTURE(tun_vnet_udptnl) { char ifname[IFNAMSIZ]; -- 2.56.0.rc1.315.gc6ed9934b7-goog ^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM 2026-09-28 14:42 ` [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Eric Dumazet @ 2026-09-28 19:17 ` Willem de Bruijn 2026-09-28 19:43 ` Eric Dumazet 2026-10-01 17:52 ` netdev-bot+sashiko 1 sibling, 1 reply; 9+ messages in thread From: Willem de Bruijn @ 2026-09-28 19:17 UTC (permalink / raw) To: Eric Dumazet, David S . Miller, Jakub Kicinski, Paolo Abeni Cc: Simon Horman, Willem de Bruijn, Michael S . Tsirkin, netdev, edumazet Eric Dumazet wrote: > Add a selftest in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO > packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and > flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP > device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). > > Also verify that mismatched GSO types (e.g. VIRTIO_NET_HDR_GSO_TCPV6 on > a VLAN-tagged IPv4 packet) and truncated headers without NEEDS_CSUM are > rejected with -EINVAL. > > Based on a reproducer by Michael S. Tsirkin <mst@redhat.com>. > > Assisted-by: LLM > Signed-off-by: Eric Dumazet <edumazet@kernel.org> > --- > tools/testing/selftests/net/tun.c | 107 ++++++++++++++++++++++++++++++ > 1 file changed, 107 insertions(+) > > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > index abe488bac50bb3b05df5c446836c1c3dfa9ea604..c6afafb7b957360e2873640e60a7d580c6564a66 100644 > --- a/tools/testing/selftests/net/tun.c > +++ b/tools/testing/selftests/net/tun.c > @@ -9,6 +9,7 @@ > #include <string.h> > #include <unistd.h> > #include <linux/if_tun.h> > +#include <netinet/tcp.h> > #include <sys/ioctl.h> > #include <sys/socket.h> > > @@ -542,6 +543,112 @@ TEST_F(tun, reattach_close_delete) > EXPECT_EQ(tun_delete(self->ifname), 0); > } > > +FIXTURE(tun_vnet_gso) > +{ > + char ifname[IFNAMSIZ]; > + int fd; > +}; > + > +FIXTURE_SETUP(tun_vnet_gso) > +{ > + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; > + > + memset(self->ifname, 0, sizeof(self->ifname)); > + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); > + ASSERT_GE(self->fd, 0); > +} > + > +FIXTURE_TEARDOWN(tun_vnet_gso) > +{ > + if (self->fd >= 0) > + close(self->fd); > +} > + > +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) > +{ > + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; > + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); > + struct virtio_net_hdr vh = { 0 }; > + struct tcphdr tcph = { 0 }; > + uint32_t sum; > + > + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, > + param_hwaddr_outer_dst); > + > + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ > + memcpy(cur, vlan_tag, sizeof(vlan_tag)); > + cur += sizeof(vlan_tag); > + > + cur += build_ipv4_header(cur, IPPROTO_TCP, > + sizeof(tcph) + payload_len, > + ¶m_ipaddr4_outer_src, > + ¶m_ipaddr4_outer_dst); > + > + tcph.source = htons(12345); > + tcph.dest = htons(80); > + tcph.seq = htonl(1); > + tcph.doff = sizeof(tcph) / 4; > + tcph.ack = 1; > + tcph.window = htons(65535); > + memcpy(cur, &tcph, sizeof(tcph)); > + memset(cur + sizeof(tcph), PKT_DATA, payload_len); > + > + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, > + sizeof(param_ipaddr4_outer_src)); > + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, > + sizeof(param_ipaddr4_outer_dst)); > + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); > + sum += add_csum(cur, sizeof(tcph) + payload_len); > + tcph.check = finish_ip_csum(sum); > + memcpy(cur, &tcph, sizeof(tcph)); > + cur += sizeof(tcph) + payload_len; > + > + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; > + vh.gso_size = 1400; > + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; > + memcpy(buf, &vh, sizeof(vh)); > + > + return cur - buf; > +} > + > +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) > +{ > + struct virtio_net_hdr vh; > + uint8_t pkt[4096] = { 0 }; > + int len, ret; > + > + len = build_vlan_tcpv4_gso_packet(pkt, 2800); > + memcpy(&vh, pkt, sizeof(vh)); > + > + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ This is not a GSO packet if no vh.gso_type > + vh.flags = 0; > + memcpy(pkt, &vh, sizeof(vh)); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, len); > + > + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ Same > + vh.flags = VIRTIO_NET_HDR_F_DATA_VALID; > + memcpy(pkt, &vh, sizeof(vh)); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, len); > + > + /* Mismatched GSO type (TCPV6 on VLAN-tagged IPv4 packet) */ > + vh.flags = 0; > + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV6; > + memcpy(pkt, &vh, sizeof(vh)); > + ret = write(self->fd, pkt, len); > + ASSERT_EQ(ret, -1); > + ASSERT_EQ(errno, EINVAL); > + > + /* Truncated TCP header without NEEDS_CSUM */ > + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; > + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr); > + memcpy(pkt, &vh, sizeof(vh)); > + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); > + ASSERT_EQ(ret, -1); > + ASSERT_EQ(errno, EINVAL); > +} > + > FIXTURE(tun_vnet_udptnl) > { > char ifname[IFNAMSIZ]; > -- > 2.56.0.rc1.315.gc6ed9934b7-goog > ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM 2026-09-28 19:17 ` Willem de Bruijn @ 2026-09-28 19:43 ` Eric Dumazet 2026-09-28 20:22 ` Willem de Bruijn 0 siblings, 1 reply; 9+ messages in thread From: Eric Dumazet @ 2026-09-28 19:43 UTC (permalink / raw) To: Willem de Bruijn Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, Willem de Bruijn, Michael S . Tsirkin, netdev On Mon, Sep 28, 2026 at 9:17 PM Willem de Bruijn <willemdebruijn.kernel@gmail.com> wrote: > > Eric Dumazet wrote: > > Add a selftest in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO > > packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and > > flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP > > device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). > > > > Also verify that mismatched GSO types (e.g. VIRTIO_NET_HDR_GSO_TCPV6 on > > a VLAN-tagged IPv4 packet) and truncated headers without NEEDS_CSUM are > > rejected with -EINVAL. > > > > Based on a reproducer by Michael S. Tsirkin <mst@redhat.com>. > > > > Assisted-by: LLM > > Signed-off-by: Eric Dumazet <edumazet@kernel.org> > > --- > > tools/testing/selftests/net/tun.c | 107 ++++++++++++++++++++++++++++++ > > 1 file changed, 107 insertions(+) > > > > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > > index abe488bac50bb3b05df5c446836c1c3dfa9ea604..c6afafb7b957360e2873640e60a7d580c6564a66 100644 > > --- a/tools/testing/selftests/net/tun.c > > +++ b/tools/testing/selftests/net/tun.c > > @@ -9,6 +9,7 @@ > > #include <string.h> > > #include <unistd.h> > > #include <linux/if_tun.h> > > +#include <netinet/tcp.h> > > #include <sys/ioctl.h> > > #include <sys/socket.h> > > > > @@ -542,6 +543,112 @@ TEST_F(tun, reattach_close_delete) > > EXPECT_EQ(tun_delete(self->ifname), 0); > > } > > > > +FIXTURE(tun_vnet_gso) > > +{ > > + char ifname[IFNAMSIZ]; > > + int fd; > > +}; > > + > > +FIXTURE_SETUP(tun_vnet_gso) > > +{ > > + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; > > + > > + memset(self->ifname, 0, sizeof(self->ifname)); > > + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); > > + ASSERT_GE(self->fd, 0); > > +} > > + > > +FIXTURE_TEARDOWN(tun_vnet_gso) > > +{ > > + if (self->fd >= 0) > > + close(self->fd); > > +} > > + > > +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) > > +{ > > + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; > > + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); > > + struct virtio_net_hdr vh = { 0 }; > > + struct tcphdr tcph = { 0 }; > > + uint32_t sum; > > + > > + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, > > + param_hwaddr_outer_dst); > > + > > + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ > > + memcpy(cur, vlan_tag, sizeof(vlan_tag)); > > + cur += sizeof(vlan_tag); > > + > > + cur += build_ipv4_header(cur, IPPROTO_TCP, > > + sizeof(tcph) + payload_len, > > + ¶m_ipaddr4_outer_src, > > + ¶m_ipaddr4_outer_dst); > > + > > + tcph.source = htons(12345); > > + tcph.dest = htons(80); > > + tcph.seq = htonl(1); > > + tcph.doff = sizeof(tcph) / 4; > > + tcph.ack = 1; > > + tcph.window = htons(65535); > > + memcpy(cur, &tcph, sizeof(tcph)); > > + memset(cur + sizeof(tcph), PKT_DATA, payload_len); > > + > > + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, > > + sizeof(param_ipaddr4_outer_src)); > > + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, > > + sizeof(param_ipaddr4_outer_dst)); > > + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); > > + sum += add_csum(cur, sizeof(tcph) + payload_len); > > + tcph.check = finish_ip_csum(sum); > > + memcpy(cur, &tcph, sizeof(tcph)); > > + cur += sizeof(tcph) + payload_len; > > + > > + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; > > + vh.gso_size = 1400; > > + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; > > + memcpy(buf, &vh, sizeof(vh)); > > + > > + return cur - buf; > > +} > > + > > +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) > > +{ > > + struct virtio_net_hdr vh; > > + uint8_t pkt[4096] = { 0 }; > > + int len, ret; > > + > > + len = build_vlan_tcpv4_gso_packet(pkt, 2800); > > + memcpy(&vh, pkt, sizeof(vh)); > > + > > + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ > > This is not a GSO packet if no vh.gso_type > > > + vh.flags = 0; > > + memcpy(pkt, &vh, sizeof(vh)); > > + ret = write(self->fd, pkt, len); > > + ASSERT_EQ(ret, len); > > + > > + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ > > Same build_vlan_tcpv4_gso_packet() already sets: vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; vh.gso_size = 1400; vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; memcpy(buf, &vh, sizeof(vh)); and vlan_tcpv4_gso_no_csum() copies it back into the local vh right afterwards: len = build_vlan_tcpv4_gso_packet(pkt, 2800); memcpy(&vh, pkt, sizeof(vh)); so vh.gso_type is VIRTIO_NET_HDR_GSO_TCPV4 (and vh.gso_size is 1400) for both the flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID cases. ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM 2026-09-28 19:43 ` Eric Dumazet @ 2026-09-28 20:22 ` Willem de Bruijn 0 siblings, 0 replies; 9+ messages in thread From: Willem de Bruijn @ 2026-09-28 20:22 UTC (permalink / raw) To: Eric Dumazet, Willem de Bruijn Cc: David S . Miller, Jakub Kicinski, Paolo Abeni, Simon Horman, Willem de Bruijn, Michael S . Tsirkin, netdev Eric Dumazet wrote: > On Mon, Sep 28, 2026 at 9:17 PM Willem de Bruijn > <willemdebruijn.kernel@gmail.com> wrote: > > > > Eric Dumazet wrote: > > > Add a selftest in tun.c verifying that a VLAN-tagged (802.1Q) TCPv4 GSO > > > packet without VIRTIO_NET_HDR_F_NEEDS_CSUM (both flags = 0 and > > > flags = VIRTIO_NET_HDR_F_DATA_VALID) is accepted when written to a TAP > > > device (/dev/net/tun with IFF_TAP | IFF_NO_PI | IFF_VNET_HDR). > > > > > > Also verify that mismatched GSO types (e.g. VIRTIO_NET_HDR_GSO_TCPV6 on > > > a VLAN-tagged IPv4 packet) and truncated headers without NEEDS_CSUM are > > > rejected with -EINVAL. > > > > > > Based on a reproducer by Michael S. Tsirkin <mst@redhat.com>. > > > > > > Assisted-by: LLM > > > Signed-off-by: Eric Dumazet <edumazet@kernel.org> > > > --- > > > tools/testing/selftests/net/tun.c | 107 ++++++++++++++++++++++++++++++ > > > 1 file changed, 107 insertions(+) > > > > > > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > > > index abe488bac50bb3b05df5c446836c1c3dfa9ea604..c6afafb7b957360e2873640e60a7d580c6564a66 100644 > > > --- a/tools/testing/selftests/net/tun.c > > > +++ b/tools/testing/selftests/net/tun.c > > > @@ -9,6 +9,7 @@ > > > #include <string.h> > > > #include <unistd.h> > > > #include <linux/if_tun.h> > > > +#include <netinet/tcp.h> > > > #include <sys/ioctl.h> > > > #include <sys/socket.h> > > > > > > @@ -542,6 +543,112 @@ TEST_F(tun, reattach_close_delete) > > > EXPECT_EQ(tun_delete(self->ifname), 0); > > > } > > > > > > +FIXTURE(tun_vnet_gso) > > > +{ > > > + char ifname[IFNAMSIZ]; > > > + int fd; > > > +}; > > > + > > > +FIXTURE_SETUP(tun_vnet_gso) > > > +{ > > > + int flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR; > > > + > > > + memset(self->ifname, 0, sizeof(self->ifname)); > > > + self->fd = tun_open(self->ifname, flags, 0, 0, NULL); > > > + ASSERT_GE(self->fd, 0); > > > +} > > > + > > > +FIXTURE_TEARDOWN(tun_vnet_gso) > > > +{ > > > + if (self->fd >= 0) > > > + close(self->fd); > > > +} > > > + > > > +static int build_vlan_tcpv4_gso_packet(uint8_t *buf, int payload_len) > > > +{ > > > + uint16_t vlan_tag[2] = { htons(100), htons(ETH_P_IP) }; > > > + uint8_t *cur = buf + sizeof(struct virtio_net_hdr); > > > + struct virtio_net_hdr vh = { 0 }; > > > + struct tcphdr tcph = { 0 }; > > > + uint32_t sum; > > > + > > > + cur += build_eth(cur, ETH_P_8021Q, param_hwaddr_outer_src, > > > + param_hwaddr_outer_dst); > > > + > > > + /* 802.1Q tag: VID=100, inner protocol=ETH_P_IP */ > > > + memcpy(cur, vlan_tag, sizeof(vlan_tag)); > > > + cur += sizeof(vlan_tag); > > > + > > > + cur += build_ipv4_header(cur, IPPROTO_TCP, > > > + sizeof(tcph) + payload_len, > > > + ¶m_ipaddr4_outer_src, > > > + ¶m_ipaddr4_outer_dst); > > > + > > > + tcph.source = htons(12345); > > > + tcph.dest = htons(80); > > > + tcph.seq = htonl(1); > > > + tcph.doff = sizeof(tcph) / 4; > > > + tcph.ack = 1; > > > + tcph.window = htons(65535); > > > + memcpy(cur, &tcph, sizeof(tcph)); > > > + memset(cur + sizeof(tcph), PKT_DATA, payload_len); > > > + > > > + sum = add_csum((const uint8_t *)¶m_ipaddr4_outer_src, > > > + sizeof(param_ipaddr4_outer_src)); > > > + sum += add_csum((const uint8_t *)¶m_ipaddr4_outer_dst, > > > + sizeof(param_ipaddr4_outer_dst)); > > > + sum += htons(IPPROTO_TCP) + htons(sizeof(tcph) + payload_len); > > > + sum += add_csum(cur, sizeof(tcph) + payload_len); > > > + tcph.check = finish_ip_csum(sum); > > > + memcpy(cur, &tcph, sizeof(tcph)); > > > + cur += sizeof(tcph) + payload_len; > > > + > > > + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; > > > + vh.gso_size = 1400; > > > + vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; > > > + memcpy(buf, &vh, sizeof(vh)); > > > + > > > + return cur - buf; > > > +} > > > + > > > +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) > > > +{ > > > + struct virtio_net_hdr vh; > > > + uint8_t pkt[4096] = { 0 }; > > > + int len, ret; > > > + > > > + len = build_vlan_tcpv4_gso_packet(pkt, 2800); > > > + memcpy(&vh, pkt, sizeof(vh)); > > > + > > > + /* Valid VLAN-tagged TCPv4 GSO with flags = 0 (no NEEDS_CSUM) */ > > > > This is not a GSO packet if no vh.gso_type > > > > > + vh.flags = 0; > > > + memcpy(pkt, &vh, sizeof(vh)); > > > + ret = write(self->fd, pkt, len); > > > + ASSERT_EQ(ret, len); > > > + > > > + /* Valid VLAN-tagged TCPv4 GSO with flags = DATA_VALID */ > > > > Same > > build_vlan_tcpv4_gso_packet() already sets: > > vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; > vh.gso_size = 1400; > vh.hdr_len = (cur - buf) - sizeof(vh) - payload_len; > memcpy(buf, &vh, sizeof(vh)); Missed that, sorry. I was only looking at the top of the function. > and vlan_tcpv4_gso_no_csum() copies it back into the local vh right > afterwards: > > len = build_vlan_tcpv4_gso_packet(pkt, 2800); > memcpy(&vh, pkt, sizeof(vh)); > > so vh.gso_type is VIRTIO_NET_HDR_GSO_TCPV4 (and vh.gso_size is 1400) for > both the flags = 0 and flags = VIRTIO_NET_HDR_F_DATA_VALID cases. ^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM 2026-09-28 14:42 ` [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Eric Dumazet 2026-09-28 19:17 ` Willem de Bruijn @ 2026-10-01 17:52 ` netdev-bot+sashiko 1 sibling, 0 replies; 9+ messages in thread From: netdev-bot+sashiko @ 2026-10-01 17:52 UTC (permalink / raw) To: edumazet; +Cc: davem, kuba, pabeni, horms, willemb, mst, netdev 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 'Truncated TCP header without NEEDS_CSUM' sub-case in TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum)… -- selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM This adds a selftest in tun.c that writes a VLAN-tagged TCPv4 GSO packet without VIRTIO_NET_HDR_F_NEEDS_CSUM to a TAP device with IFF_VNET_HDR. It checks that the packet is accepted with flags = 0 and with flags = VIRTIO_NET_HDR_F_DATA_VALID. It also checks that a mismatched GSO type and a truncated header are rejected with -EINVAL. > diff --git a/tools/testing/selftests/net/tun.c b/tools/testing/selftests/net/tun.c > index abe488bac50b..c6afafb7b957 100644 > --- a/tools/testing/selftests/net/tun.c > +++ b/tools/testing/selftests/net/tun.c [ ... ] > +TEST_F(tun_vnet_gso, vlan_tcpv4_gso_no_csum) > +{ [ ... ] > + /* Truncated TCP header without NEEDS_CSUM */ > + vh.gso_type = VIRTIO_NET_HDR_GSO_TCPV4; > + vh.hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr); > + memcpy(pkt, &vh, sizeof(vh)); > + ret = write(self->fd, pkt, sizeof(vh) + vh.hdr_len); > + ASSERT_EQ(ret, -1); > + ASSERT_EQ(errno, EINVAL); [Severity: Low] Would this sub-case still pass on a kernel without commit 67fbe1f8a962 ("net: always dissect GSO packets in __virtio_net_hdr_to_skb()")? The frame written here is ETH_HLEN + 4 + sizeof(struct iphdr) = 38 bytes after the vnet header. Before that fix, tun_get_user() reached the unvalidated gso_type fallback with skb->network_header still at 0. That fallback did pskb_may_pull(skb, nh_min_len + thlen), which is 40 bytes, so a 38-byte frame already failed with -EINVAL. After the fix, __virtio_net_hdr_to_skb() dissects the frame. thoff is 14 + 4 + 20 = 38: include/linux/virtio_net.h:__virtio_net_hdr_to_skb() { ... p_off = keys.control.thoff + thlen; if (!pskb_may_pull(skb, p_off) || ... return -EINVAL; ... } With p_off = 58 this also fails with -EINVAL. Both kernels therefore give the same result, and the ASSERTs never exercise the thoff-based pull. Only frame lengths from 40 to 57 bytes separate the two kernels for this VLAN packet. Take hdr_len = ETH_HLEN + 4 + sizeof(struct iphdr) + 10 = 48. The old kernel pulls 40 bytes and succeeds. Since skb->len - nh_off (8) is not greater than gso_size, no GSO is set and write() returns the full length. The fixed kernel needs 58 bytes and returns -EINVAL. Could the truncated length be picked from that range, so this sub-case catches a regression of the thoff-based pull? The TCPV6-on-IPv4 sub-case above already separates the two kernels through virtio_net_hdr_match_proto(). > +} > + > FIXTURE(tun_vnet_udptnl) > { > char ifname[IFNAMSIZ]; -- Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260928144254.3361044-1-edumazet%40kernel.org ^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-10-01 18:34 UTC | newest] Thread overview: 9+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-09-28 14:42 [PATCH v2 net 0/2] net: always dissect GSO packets in __virtio_net_hdr_to_skb() Eric Dumazet 2026-09-28 14:42 ` [PATCH v2 net 1/2] " Eric Dumazet 2026-10-01 17:52 ` netdev-bot+sashiko 2026-10-01 18:34 ` Eric Dumazet 2026-09-28 14:42 ` [PATCH v2 net 2/2] selftests: net: tun: add test for VLAN-tagged GSO without NEEDS_CSUM Eric Dumazet 2026-09-28 19:17 ` Willem de Bruijn 2026-09-28 19:43 ` Eric Dumazet 2026-09-28 20:22 ` Willem de Bruijn 2026-10-01 17:52 ` netdev-bot+sashiko
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox