CVE-2026-46321
Description
Linux kernel tun/xdp vulnerability allows local attackers to cause an OOM panic by leaking memory via crafted short frames.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Linux kernel tun/xdp vulnerability allows local attackers to cause an OOM panic by leaking memory via crafted short frames.
Vulnerability
The Linux kernel's tun_xdp_one() function fails to free an allocated page when rejecting frames shorter than ETH_HLEN. This occurs when a tun/tap device is attached as a vhost-net backend and receives TX descriptors with a length less than ETH_HLEN after accounting for the virtio-net header. Affected versions are not explicitly stated but the fix is present in the provided reference [1].
Exploitation
A local attacker with the ability to open /dev/net/tun and /dev/vhost-net can exploit this vulnerability. The attacker must attach a tun/tap device as the vhost-net backend and then repeatedly send TX descriptors whose effective length (total length minus virtio-net header) is below ETH_HLEN. This triggers the faulty code path in tun_xdp_one().
Impact
Successful exploitation leads to a memory leak, where each short frame processed leaks page-frag chunks. A tight submission loop can exhaust host memory, ultimately causing an Out-Of-Memory (OOM) panic and a denial of service on the system.
Mitigation
The vulnerability is resolved in the Linux kernel version associated with the commit 98c67be9eb9de72465a071949e84a3cdb8fab5a3 [1]. No workarounds are disclosed in the available references. The reference provided does not contain information about EOL status or KEV listing.
AI Insight generated on Jun 9, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
2Patches
8f4feb1e20058tun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index b183189f18535..f594360d66d65 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2394,8 +2394,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
69863ff2720atun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index fb9d425eff8c1..19c33d21bab94 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2459,8 +2459,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
37a1c268c2c8tun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index 8192740357a09..afba37965ce3b 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2392,8 +2392,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
98c67be9eb9dtun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index c492fda6fc15a..8154d18a2a235 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2392,8 +2392,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
37a1c268c2c8tun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index 8192740357a09..afba37965ce3b 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2392,8 +2392,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
69863ff2720atun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index fb9d425eff8c1..19c33d21bab94 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2459,8 +2459,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
98c67be9eb9dtun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index c492fda6fc15a..8154d18a2a235 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2392,8 +2392,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
f4feb1e20058tun: free page on short-frame rejection in tun_xdp_one()
1 file changed · +3 −2
drivers/net/tun.c+3 −2 modifieddiff --git a/drivers/net/tun.c b/drivers/net/tun.c index b183189f18535..f594360d66d65 100644 --- a/drivers/net/tun.c +++ b/drivers/net/tun.c @@ -2394,8 +2394,10 @@ static int tun_xdp_one(struct tun_struct *tun, bool skb_xdp = false; struct page *page; - if (unlikely(datasize < ETH_HLEN)) + if (unlikely(datasize < ETH_HLEN)) { + put_page(virt_to_head_page(xdp->data)); return -EINVAL; + } xdp_prog = rcu_dereference(tun->xdp_prog); if (xdp_prog) { -- cgit 1.3-korg
Vulnerability mechanics
Root cause
"The tun_xdp_one function fails to free allocated memory when rejecting short frames."
Attack vector
A local attacker with the ability to open `/dev/net/tun` and `/dev/vhost-net` can trigger this vulnerability. The attacker must attach a tun/tap device as a vhost-net backend and then submit TX descriptors where the data size, minus the virtio-net header, is less than `ETH_HLEN`. By repeatedly submitting such short frames in a tight loop, the attacker can exhaust host memory, leading to an OOM panic.
Affected code
The vulnerability exists in the `tun_xdp_one` function within `drivers/net/tun.c`. Specifically, the code path where `datasize` is less than `ETH_HLEN` did not free the allocated page before returning an error.
What the fix does
The patch adds a call to `put_page(virt_to_head_page(xdp->data))` within the `if (unlikely(datasize < ETH_HLEN))` block in the `tun_xdp_one` function [patch_id=5354469]. This ensures that the allocated page is freed when a frame shorter than `ETH_HLEN` is rejected, preventing the memory leak. This change aligns the error handling with the existing XDP-program error path.
Preconditions
- authThe attacker must have local access to the system.
- configThe attacker must be able to open `/dev/net/tun` and `/dev/vhost-net`.
Generated on Jun 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
4News mentions
1- Linux Kernel: 25 Vulnerabilities Disclosed in Single Batch on June 8-9, 2026Vypr Intelligence · Jun 9, 2026