On Fri, Oct 23, 2026 at 07:37:20PM +0800, Guikun Yang wrote:
> When VHOST_NET_F_VIRTIO_NET_HDR is negotiated, handle_rx() builds the
> virtio-net header itself and writes it into the guest buffer.  Since
> commit bbca931fce26 ("vhost/net: enable gso over UDP tunnel support.")
> vhost_net_set_features() raises vhost_hlen to
> sizeof(struct virtio_net_hdr_v1_hash_tunnel) as soon as one of the UDP
> tunnel GSO features is enabled, but handle_rx() still copies only
> sizeof(struct virtio_net_hdr) (10 bytes) and then writes num_buffers at
> offset 10.
> 
> The remaining 12 bytes of the 24 byte header (hash_value_lo,
> hash_value_hi, hash_report, padding, outer_th_offset and
> inner_nh_offset) are therefore never initialized, and the guest sees
> whatever its RX buffer contained before in fields whose layout the
> negotiated feature set defines.
> 
> Reproduced on 7.3.0-rc7 with a userspace vhost-net "guest" that fills its
> RX buffer with 0xaa and negotiates VHOST_NET_F_VIRTIO_NET_HDR,
> VIRTIO_F_VERSION_1 and VIRTIO_NET_F_GUEST_UDP_TUNNEL_GSO: offsets 0..9
> hold the vnet header, num_buffers is at 10..11, the payload correctly
> starts at offset 24, and offsets 12..23 still contain 0xaa.  Without the
> tunnel features the payload starts at offset 12 and nothing is left
> uninitialized.
> 
> Copy the whole vhost_hlen out of a zero initialized header of the
> largest layout, and rewind the iterator so that num_buffers keeps its
> position right after the legacy header.
> 
> Fixes: bbca931fce26 ("vhost/net: enable gso over UDP tunnel support.")
> Signed-off-by: Guikun Yang <[email protected]>


Which backend did you use? Sounds like you did not set
TUN_VNET_TNL_SIZE or did not set TUNSETOFFLOAD?
That's the issue then.


> ---
>  drivers/vhost/net.c | 24 ++++++++++++++++++------
>  1 file changed, 18 insertions(+), 6 deletions(-)
> 
> diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c
> --- a/drivers/vhost/net.c
> +++ b/drivers/vhost/net.c
> @@ -1161,9 +1161,15 @@ static void handle_rx(struct vhost_net *net)
>               .msg_controllen = 0,
>               .msg_flags = MSG_DONTWAIT,
>       };
> -     struct virtio_net_hdr hdr = {
> -             .flags = 0,
> -             .gso_type = VIRTIO_NET_HDR_GSO_NONE
> +     /*
> +      * The negotiated header is struct virtio_net_hdr when only the legacy
> +      * layout is used, but struct virtio_net_hdr_v1_hash_tunnel (or the
> +      * v1/v1_hash layouts) when the corresponding features are enabled:
> +      * size it for the largest layout and let vhost_hlen select how much
> +      * of it is sent.
> +      */
> +     struct virtio_net_hdr_v1_hash_tunnel hdr = {
> +             .hash_hdr.hdr.gso_type = VIRTIO_NET_HDR_GSO_NONE,
>       };
>       size_t total_len = 0;
>       int err, mergeable;
> @@ -1260,17 +1266,26 @@ static void handle_rx(struct vhost_net *net)
>               }
>               /* Supply virtio_net_hdr if VHOST_NET_F_VIRTIO_NET_HDR */
>               if (unlikely(vhost_hlen)) {
> -                     if (copy_to_iter(&hdr, sizeof(hdr),
> -                                      &fixup) != sizeof(hdr)) {
> +                     /*
> +                      * vhost_hlen is never larger than the header above.
> +                      * Copy all of it: the fields vhost does not provide
> +                      * (hash and UDP tunnel information) have to be zeroed
> +                      * instead of being left with the previous content of
> +                      * the guest buffer.
> +                      */
> +                     if (copy_to_iter(&hdr, vhost_hlen, &fixup) != 
> vhost_hlen) {
>                               vq_err(vq, "Unable to write vnet_hdr "
>                                      "at addr %p\n", vq->iov->iov_base);
>                               goto out;
>                       }
> +                     /* num_buffers follows the legacy header */
> +                     iov_iter_revert(&fixup,
> +                                     vhost_hlen - sizeof(struct 
> virtio_net_hdr));
>               } else {
>                       /* Header came from socket; we'll need to patch
>                        * ->num_buffers over if VIRTIO_NET_F_MRG_RXBUF
>                        */
> -                     iov_iter_advance(&fixup, sizeof(hdr));
> +                     iov_iter_advance(&fixup, sizeof(struct virtio_net_hdr));
>               }
>               /* TODO: Should check and handle checksum. */
>  
> --
> 2.39.5


Reply via email to