Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

net/packet: fix TOCTOU race on mmap'd vnet_hdr in tpacket_snd()

In tpacket_snd(), when PACKET_VNET_HDR is enabled, vnet_hdr points
directly into the mmap'd TX ring buffer shared with userspace. The
kernel validates the header via __packet_snd_vnet_parse() but then
re-reads all fields later in virtio_net_hdr_to_skb(). A concurrent
userspace thread can modify the vnet_hdr fields between validation
and use, bypassing all safety checks.

The non-TPACKET path (packet_snd()) already correctly copies vnet_hdr
to a stack-local variable. All other vnet_hdr consumers in the kernel
(tun.c, tap.c, virtio_net.c) also use stack copies. The TPACKET TX
path is the only caller of virtio_net_hdr_to_skb() that reads directly
from user-controlled shared memory.

Fix this by copying vnet_hdr from the mmap'd ring buffer to a
stack-local variable before validation and use, consistent with the
approach used in packet_snd() and all other callers.

Fixes: 1d036d25e560 ("packet: tpacket_snd gso and checksum offload")
Signed-off-by: Bingquan Chen <patzilla007@gmail.com>
Reviewed-by: Willem de Bruijn <willemb@google.com>
Link: https://patch.msgid.link/20260418112006.78823-1-patzilla007@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Bingquan Chen and committed by
Jakub Kicinski
2c054e17 3bfcf396

+13 -8
+13 -8
net/packet/af_packet.c
··· 2718 2718 { 2719 2719 struct sk_buff *skb = NULL; 2720 2720 struct net_device *dev; 2721 - struct virtio_net_hdr *vnet_hdr = NULL; 2721 + struct virtio_net_hdr vnet_hdr; 2722 + bool has_vnet_hdr = false; 2722 2723 struct sockcm_cookie sockc; 2723 2724 __be16 proto; 2724 2725 int err, reserve = 0; ··· 2820 2819 hlen = LL_RESERVED_SPACE(dev); 2821 2820 tlen = dev->needed_tailroom; 2822 2821 if (vnet_hdr_sz) { 2823 - vnet_hdr = data; 2824 2822 data += vnet_hdr_sz; 2825 2823 tp_len -= vnet_hdr_sz; 2826 - if (tp_len < 0 || 2827 - __packet_snd_vnet_parse(vnet_hdr, tp_len)) { 2824 + if (tp_len < 0) { 2825 + tp_len = -EINVAL; 2826 + goto tpacket_error; 2827 + } 2828 + memcpy(&vnet_hdr, data - vnet_hdr_sz, sizeof(vnet_hdr)); 2829 + if (__packet_snd_vnet_parse(&vnet_hdr, tp_len)) { 2828 2830 tp_len = -EINVAL; 2829 2831 goto tpacket_error; 2830 2832 } 2831 2833 copylen = __virtio16_to_cpu(vio_le(), 2832 - vnet_hdr->hdr_len); 2834 + vnet_hdr.hdr_len); 2835 + has_vnet_hdr = true; 2833 2836 } 2834 2837 copylen = max_t(int, copylen, dev->hard_header_len); 2835 2838 skb = sock_alloc_send_skb(&po->sk, ··· 2870 2865 } 2871 2866 } 2872 2867 2873 - if (vnet_hdr_sz) { 2874 - if (virtio_net_hdr_to_skb(skb, vnet_hdr, vio_le())) { 2868 + if (has_vnet_hdr) { 2869 + if (virtio_net_hdr_to_skb(skb, &vnet_hdr, vio_le())) { 2875 2870 tp_len = -EINVAL; 2876 2871 goto tpacket_error; 2877 2872 } 2878 - virtio_net_hdr_set_proto(skb, vnet_hdr); 2873 + virtio_net_hdr_set_proto(skb, &vnet_hdr); 2879 2874 } 2880 2875 2881 2876 skb->destructor = tpacket_destruct_skb;