# VsockDrop https://github.com/user-attachments/assets/e75d9b66-52d6-4d6a-b251-a87846efab10 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. ## Root cause `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: ```c 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.** ## Primitive `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). ## Exploitation 1. Drain the pin to `0` -> the still-pinned page lands on the PCP freelist. 2. A single **cold** `pread` of `/usr/bin/su` page 0 pops that PFN back (LIFO) as su's page-cache. The page is now aliased by both su and our fixed buffer. 3. `write_fixed`/`read_fixed` (not a PTE, the buffer is unmapped) rewrite the `PT_INTERP` string in that page to our loader path. 4. `exec("/usr/bin/su")` (setuid-root) -> kernel maps our loader as the interpreter and enters it with root creds -> root shell. ## Build & run ```sh make ./exploit ``` ## Disclosure 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.