VsockDrop-LPE-PoC.mp4
Unprivileged local privilege escalation in the Linux kernel io_uring zerocopy
send path, reachable over AF_VSOCK. A managed-frag reference underflow frees a
still-pinned page; it is reclaimed as /usr/bin/su page-cache and its .interp
is rewritten. Data-only, no leak, no kernel execution.
Reported to security@kernel.org; fixed upstream as CVE-2026-53365. NVD rates it 5.5 as an availability issue. It should be 7.8 since it's an LPE.
No user namespaces needed, one static binary. Bug affects Linux 6.7 -> 7.0.10 (introduced in 6.7, fixed in 7.0.11). Tested on unpatched Ubuntu 22.04 HWE/24.04/26.04, Debian 13, Arch, and openSUSE Leap/Tumbleweed.
io_uring attaches SEND_ZC fixed-buffer pages to an skb without taking a
reference (__skb_fill_page_desc_noacc) and sets SKBFL_MANAGED_FRAG_REFS.
io_uring owns the pages (they are FOLL_LONGTERM-pinned), so the
network stack must never get/put them. skb_release_data() honours it:
bool skip_unref = shinfo->flags & SKBFL_MANAGED_FRAG_REFS;
if (skip_unref)
goto free_head; /* skip __skb_frag_unref() */On the vsock multi-skb path (payload > 64k) the flag is not honoured for the
frags we own, so __skb_frag_unref() -> put_page() runs on a managed page. Since
that frag never held a reference, the put_page does not balance anything, so it
decrements the pin directly. One stray put_page per send.
GUP_PIN_COUNTING_BIAS is 1024. After io_uring_register_buffers the page is
at refcount = 1 + 1024; munmap() drops the mapped ref (leaving the pin) and
sets _mapcount = -1, which also lets the page pass free_page_is_bad() later
(the init_on_alloc bypass). The pin is now the only reference:
| step | _refcount | note |
|---|---|---|
| register | 1025 | FOLL_LONGTERM pin added |
| munmap | 1024 | mapped ref dropped, _mapcount = -1 |
| send × 1024 | 0 | managed-frag underflow, 1/send -> page freed |
| su page-0 read | reused | freed PFN reclaimed as su page-cache (LIFO) |
1024 is not tuned. It is the ABI constant, which is why a fixed count works
across every kernel once the decrement is deterministic. The receiver drains exactly one send and acks before the next,
so each submit produces exactly one skb (one underflow).
- Drain the pin to
0-> the still-pinned page lands on the PCP freelist. - A single cold
preadof/usr/bin/supage 0 pops that PFN back (LIFO) as su's page-cache. The page is now aliased by both su and our fixed buffer. write_fixed/read_fixed(not a PTE, the buffer is unmapped) rewrite thePT_INTERPstring in that page to our loader path.exec("/usr/bin/su")(setuid-root) -> kernel maps our loader as the interpreter and enters it with root creds -> root shell.
make
./exploit Defensive research, disclosed upstream to security@kernel.org. Do not run on
systems you do not own: it corrupts /usr/bin/su in page-cache, and on CoW
filesystems the change can persist to disk.