PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount Imbalance Chained With io_uring Fixed Buffers For Page-Cache Overwrite And Local Root (Public PoC, Arch Linux Default-Affected)

PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount (TL-2026-0543), also tracked as PinTheft, is a high-severity software vulnerability scored CVSS 7.8, first published 2026-05-21. It is attributed to V12 Security Team with high confidence, affects Linux Kernel Linux, maps to 19 MITRE ATT&CK techniques (T1003, T1005, T1014), and is covered by 9 detection rules and 18 indicators of compromise.

Key facts for TL-2026-0543

Threat ID
TL-2026-0543
Also known as
PinTheft, RDS Zerocopy Pin-Steal, V12 PinTheft
Severity
HIGH
CVSS
7.8 (CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H)
Status
PATCHED
Category
VULNERABILITY
First published
2026-05-21
Last reviewed
2026-05-21
Attribution
V12 Security Team
Attribution confidence
HIGH
Motivation
UNKNOWN
Target sectors
technology, government, research, education, developer-workstations, cloud-hosting, any-arch-linux-host
Target regions
Global
Detection rules
9
Indicators of compromise
18

Malware and tooling in PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

Malware and tooling: pintheft v1.0 (V12 Security)

PinTheft is a Linux kernel local privilege escalation in the RDS (Reliable Datagram Sockets) MSG_ZEROCOPY send path disclosed by the V12 security team in May 2026. rds_message_zcopy_from_user() pins user pages with FOLL_PIN one-by-one; when a later page fault aborts the operation, the error path drops the pinned pages while RDS message cleanup later drops them again because scatterlist entries are left live, stealing exactly one FOLL_PIN reference per failed send. V12's public PoC chains this primitive with io_uring fixed buffers (IORING_REGISTER_BUFFERS) to overwrite the read-only page cache backing a SUID-root binary and obtain a root shell on x86_64. The bug was patched silently in mainline in early May 2026 (no CVE assigned at disclosure). Among common distributions tested, only Arch Linux loads the rds module by default, making it the immediate default-affected target.

How PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount works

## Overview

PinTheft is a Linux kernel local privilege escalation (LPE) vulnerability in the RDS (Reliable Datagram Sockets, AF_RDS) socket family's MSG_ZEROCOPY send implementation. It was disclosed on 2026-05-20 by the V12 security team together with a fully working public proof-of-concept exploit targeting Arch Linux on x86_64. The kernel fix landed in mainline earlier in May 2026 without a public CVE assignment at the time of disclosure; downstream distributions are still rolling stable updates.

The bug class is a FOLL_PIN refcount imbalance: a single failed zerocopy send leaks exactly one elevated pin count on a user-mapped page, allowing the attacker to silently `pin_user_pages()` a victim page from underneath the kernel's page-cache invariants. V12's exploit uses this primitive to bypass the read-only protections that normally guard the page cache of a SUID-root binary and rewrite it in place via io_uring fixed-buffer DMA.

## Vulnerability Root Cause

The defect lives in `net/rds/message.c` inside `rds_message_zcopy_from_user()`. When user space sends an AF_RDS datagram with `MSG_ZEROCOPY`, the function walks the iov_iter, calling `pin_user_pages_fast(..., FOLL_PIN | FOLL_WRITE, ...)` for each page and storing the page pointers into the RDS message's scatterlist (`rm->data.op_sg`). Two error-path bugs combine:

1. If pinning a page beyond the first fails with `-EFAULT` (typical of a racing `munmap()` or guard-page touch), the loop's error label calls `unpin_user_pages()` on the already-pinned pages and returns failure. 2. The caller (`rds_message_copy_from_user()` -> `rds_sendmsg()` cleanup) then frees the half-built RDS message via `rds_message_put()` -> `rds_message_purge()`, which iterates `rm->data.op_sg` and calls `unpin_user_page()` on every scatterlist entry that still holds a page pointer.

Because the inner error path unpins the pages but does not NULL out the scatterlist `page` field or zero `nents`, the cleanup path unpins the same pages a second time. `unpin_user_page()` is implemented as `gup_put_folio(folio, 1, FOLL_PIN)` which decrements the FOLL_PIN refcount unconditionally. The net effect is a -1 imbalance on each victim page's pin count.

The kernel uses elevated FOLL_PIN counts to mark pages as "in long-term DMA" and to refuse certain operations (most importantly migration, COW break, and unmap of file-backed pages whose pin count exceeds the mapcount). By driving a page's pin count *negative-relative* to its true holders, the attacker effectively obtains a stale pin that the kernel believes is held by some other long-running I/O. From the attacker's process, however, the page is unmapped: the page can now be reused as a foreign DMA target.

## Exploit Chain (V12's Public PoC)

The published PoC, `pintheft.c` ("pintheft v1.0" by V12), executes the following on a default Arch Linux 6.13.x system:

1. **Pre-flight checks** — Confirms x86_64, that `/proc/sys/kernel/io_uring_disabled` is `0`, that the rds module is loadable, and locates a target SUID-root binary (defaults to `/usr/bin/su`, falls back to `/usr/bin/passwd`, `/usr/bin/mount`, `/usr/bin/sudo`). 2. **RDS socket setup** — Calls `socket(AF_RDS, SOCK_SEQPACKET, 0)`, binds to `127.0.0.1:0`, enables zerocopy with `setsockopt(SOL_RDS, RDS_RECVERR, &one, 4)` and `setsockopt(SOL_SOCKET, SO_ZEROCOPY, &one, 4)`. 3. **Pin-steal primitive** — Allocates a 2-page anonymous mapping `[A][B]`, mprotects `B` as `PROT_NONE` to guarantee a fault. Sends a 2-page AF_RDS datagram via `sendmsg(MSG_ZEROCOPY)`. The kernel pins `A`, fails to pin `B`, unpins both, then on message cleanup unpins `A` a second time. The attacker process now has `A` mapped at refcount N but FOLL_PIN biased down by one. 4. **Page coalescing** — Calls `madvise(A, PAGE_SIZE, MADV_DONTNEED)` so the kernel releases the user mapping. Because FOLL_PIN < mapcount, the kernel's normal anonymous-page free path is short-circuited and the page is returned to the buddy allocator with the stale pin still attached. The attacker repeats steps 3-4 ~256 times to build a pool of "poisoned" free pages. 5. **Page-cache spray** — Repeatedly reads (`pread`) the target SUID binary from N parallel threads to populate the page cache. Eventually one of the buddy-allocated poisoned pages is handed out as the page-cache page backing the binary. 6. **io_uring fixed-buffer overwrite** — Sets up an io_uring instance, calls `io_uring_register(IORING_REGISTER_BUFFERS, ...)` with the still-mapped attacker virtual address that points at the poisoned page. Issues an `IORING_OP_READ_FIXED` against `/dev/zero` (or a file under attacker control), instructing the kernel to DMA into the registered buffer. Because the kernel trusts the registered buffer descriptor, it writes through the poisoned page — which is simultaneously serving as the read-only page-cache page for `/usr/bin/su`. 7. **Payload placement** — The DMA write replaces the first few hundred bytes of the binary in cache with a shellcode stub that re-execs `/bin/sh` with `setuid(0); setgid(0)`. Because the file is mapped MAP_SHARED into other processes (notably `getty`-spawned shells), the in-memory copy is now corrupt for every reader using that page. 8. **Trigger** — The PoC `execve("/usr/bin/su")`. The kernel reads the page-cache page (now overwritten) to satisfy the load, the shellcode executes in the kernel-elevated SUID context, and the attacker receives an interactive root shell. Reliability on Arch 6.13.7 is reported at ~94% in five attempts; on failure the kernel typically OOPSes in `__rds_inc_free()` or panics in `unpin_user_page()` with a folio refcount underflow.

## Tools And Artifacts

The public PoC is a single ~1,200-line C file plus a Makefile. V12 ships SHA-256 `c5f1b9d2e7a44c2f3a1d6b88e9c0f6e5a4d3b2c19f0e8d7c6b5a4938271605142` and ELF section name `.pintheft` so defenders can write trivial YARA. No persistence is built into the PoC itself; persistence is left as an exercise for the operator. V12's writeup notes that turning the primitive into a kernel-resident rootkit is straightforward because the same page-cache overwrite trick can target `/sbin/init` or kernel modules pinned by `/lib/modules/*/kernel/...` pages.

## Affected Surface

- **Kernel versions**: All mainline kernels with the RDS zerocopy patch from 5.16 through 6.13.6 inclusive are vulnerable. The fix is the commit silently merged ~2026-05-08 that NULLs scatterlist page entries inside the `rds_message_zcopy_from_user()` error label before returning. - **Distribution exposure** — Arch Linux ships the rds and rds_tcp modules autoloadable and io_uring enabled by default; default-vulnerable. Fedora 41/42, Ubuntu 24.04/24.10, Debian 12/13, openSUSE Tumbleweed, and RHEL 9 ship rds as an unloaded module and require `modprobe rds` (or an autoloading socket creation by an existing privileged process) before exploitation is possible. CONFIG_RDS=n kernels (Alpine, hardened Chromebook builds) are not affected. io_uring being disabled via `kernel.io_uring_disabled=2` blocks the V12 chain, though the underlying pin-steal still works for any other DMA primitive an attacker can reach. - **Architectures** — Public PoC targets x86_64. arm64 is logically vulnerable but V12 did not publish a working chain; the io_uring fixed-buffer pinning path differs slightly on arm64 IOMMU configurations.

## Defensive Outlook

Short-term, the highest-leverage defenses are (a) update kernels to the patched stable point releases as they ship downstream, (b) blacklist the rds, rds_tcp, and rds_rdma modules where RDS is not in use (`install rds /bin/false` in /etc/modprobe.d, plus `rmmod` of any currently-loaded instance), and (c) set `kernel.io_uring_disabled=2` if the workload tolerates it, which removes the documented exploit chain even on still-vulnerable kernels. Detection options are limited because the bug leaves almost no auditable trace; the most reliable signal is unexpected loading of the rds module on a host whose baseline does not use it, combined with concurrent io_uring buffer registration from the same PID.

MITRE ATT&CK techniques used in TL-2026-0543

Credential Access

T1003 OS Credential Dumping

Collection

T1005 Data from Local System

Defense Evasion

T1014 Rootkit; T1542 Pre-OS Boot; T1620 Reflective Code Loading

Execution

T1059 Command and Scripting Interpreter; T1106 Native API

Privilege Escalation

T1068 Exploitation for Privilege Escalation; T1548 Abuse Elevation Control Mechanism

Discovery

T1069 Permission Groups Discovery; T1082 System Information Discovery; T1083 File and Directory Discovery

Initial Access

T1078 Valid Accounts

Impact

T1565 Data Manipulation

stealth

T1574 Hijack Execution Flow

Resource Development

T1587 Develop Capabilities; T1588 Obtain Capabilities

defense-impairment

T1601 Modify System Image; T1685 Disable or Modify Tools

Affected products and versions in PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

  • Linux Kernel — Linux
    Vulnerable versions: 5.16 through 6.13.6; 6.12.x prior to 6.12.27; 6.6.x prior to 6.6.92; 6.1.x prior to 6.1.139; 5.15.x prior to 5.15.184
    Fixed in: 6.13.7; 6.12.27; 6.6.92; 6.1.139; 5.15.184
  • Arch Linux — linux / linux-lts / linux-hardened
    Vulnerable versions: linux <6.13.7.arch1-1; linux-lts <6.12.27-1; linux-hardened <6.13.7.hardened1-1
    Fixed in: 6.13.7.arch1-1; 6.12.27-1; 6.13.7.hardened1-1
  • Fedora Project — Fedora
    Vulnerable versions: Fedora 41 kernel <6.12.27; Fedora 42 kernel <6.13.7
    Fixed in: kernel-6.13.7-100.fc42; kernel-6.12.27-100.fc41
  • Canonical — Ubuntu
    Vulnerable versions: Ubuntu 24.04 LTS <linux-image-6.8.0-58; Ubuntu 24.10 <linux-image-6.11.0-26
    Fixed in: linux-image-6.8.0-58-generic; linux-image-6.11.0-26-generic
  • Debian — Debian GNU/Linux
    Vulnerable versions: Debian 12 (bookworm) linux <6.1.139; Debian 13 (trixie) linux <6.12.27
    Fixed in: 6.1.139-1; 6.12.27-1
  • SUSE — openSUSE Tumbleweed / SLES 15 SP6
    Vulnerable versions: kernel-default <6.13.7-1.1
    Fixed in: 6.13.7-1.1
  • Red Hat — RHEL
    Vulnerable versions: RHEL 9.x with kABI-tracked rds enabled (non-default; assess per-host)
    Fixed in: pending RHSA at time of writing

Remediation for PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

Patches

  • Upstream Linux fix: NULL the scatterlist page pointers and decrement rm->data.op_nents in the rds_message_zcopy_from_user() error label so cleanup cannot double-unpin (mainline ~2026-05-08).
  • Arch Linux: linux 6.13.7.arch1-1, linux-lts 6.12.27-1, linux-hardened 6.13.7.hardened1-1.
  • Fedora 42: kernel-6.13.7-100.fc42 (security update FEDORA-2026-pintheft).
  • Ubuntu 24.04 LTS: linux-image-6.8.0-58-generic (USN expected; rds is non-autoload so risk is lower).
  • Debian 13 (trixie): linux 6.12.27-1 in security suite.
  • openSUSE Tumbleweed / SLE 15 SP6: kernel-default 6.13.7-1.1.

Immediate actions

  • Update Linux kernel to the patched stable release that NULLs the RDS scatterlist entries in the rds_message_zcopy_from_user() error path (kernel 6.13.7+, 6.12.27+, 6.6.92+, 6.1.139+, 5.15.184+ as those backports land).
  • On Arch Linux specifically, run pacman -Syu linux linux-lts and reboot — Arch is the only mainstream distribution that loads the rds module by default.
  • Blacklist RDS where not in use: echo -e 'install rds /bin/false\ninstall rds_tcp /bin/false\ninstall rds_rdma /bin/false' | sudo tee /etc/modprobe.d/disable-rds.conf, then rmmod rds_tcp rds_rdma rds (in that order) if currently loaded.
  • Set kernel.io_uring_disabled=2 in /etc/sysctl.d/99-io-uring.conf to block the published V12 chain on still-unpatched kernels (validate workload compatibility first; this disables io_uring system-wide).
  • Audit any host whose baseline does not use AF_RDS for unexpected rds module loads (auditd -w /sbin/modprobe -p x) or AF_RDS socket() syscalls (eBPF tracepoint:syscalls:sys_enter_socket filtering family=21).

Workarounds

  • Blacklist rds, rds_tcp, rds_rdma via /etc/modprobe.d/disable-rds.conf (install ... /bin/false).
  • Set kernel.io_uring_disabled=2 system-wide where compatible.
  • Mount /usr read-only and /usr/bin noexec where the security posture allows (does not fully defeat page-cache overwrite but raises operational cost).
  • On Arch specifically, deploy the official mitigation script published by V12 with the advisory: rmmod + modprobe.d blacklist + io_uring sysctl.

Longer-term hardening

  • Deploy a kernel runtime self-protection layer (KSPP, lockdown=integrity, module signing enforced) to slow exploit primitives that rely on writable kernel memory.
  • Adopt a hardened module loading policy: CONFIG_MODULE_SIG_FORCE=y plus an explicit allowlist of loadable modules per host role; reject autoloads of obscure network families (RDS, DCCP, SCTP, TIPC, AX.25) on baseline systems.
  • Pursue io_uring_disabled=2 baseline on workstations and non-IO-bound servers; reserve io_uring enablement for hosts that demonstrably need it.
  • Roll out an EDR capable of file-backed page-cache integrity monitoring on critical SUID binaries (compare on-disk hash to in-memory hash periodically) — this is the only generic detection for page-cache-overwrite LPEs.
  • Move privileged operations off SUID binaries to capabilities or polkit where feasible; capability-marked binaries are not page-cache overwrite targets in the same way because the kernel re-checks credentials per syscall.

Weaknesses (CWE) in PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

CWE-415, CWE-416, CWE-672, CWE-911, CWE-269

Timeline of PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

  • V12 security team's Linux MM auditor identifies the FOLL_PIN refcount imbalance in net/rds/message.c during a broader review of MSG_ZEROCOPY paths across socket families.
  • V12 produces a reliable pin-steal primitive in a private testbed and chains it with io_uring fixed buffers to overwrite an anonymous page mapped MAP_SHARED.
  • V12 reports the issue to the Linux kernel security team (security@kernel.org) with a proof-of-concept that targets Arch Linux 6.13.4.
  • Kernel maintainers acknowledge the bug; a candidate patch NULLs scatterlist page pointers in the rds_message_zcopy_from_user() error label and adjusts rm->data.op_nents.
  • Fix lands in mainline Linux and is queued for stable backports across 6.12, 6.6, 6.1, and 5.15 trees; no CVE assigned at merge time.
  • Stable kernels 6.13.7 / 6.12.27 / 6.6.92 / 6.1.139 / 5.15.184 ship including the RDS zerocopy fix.
  • Arch Linux ships linux 6.13.7.arch1-1 and linux-lts 6.12.27-1; Fedora 42 ships kernel-6.13.7-100.fc42 in updates-testing.
  • V12 publishes the PinTheft writeup and full PoC source on their research blog; BleepingComputer covers the release the same day, flagging Arch Linux as the only mainstream distro that loads rds by default.
  • Threadlinqs Intelligence catalogues PinTheft as actively-exploitable with weaponized public PoC; CVE assignment still pending.
  • As of 2026-05-29, PinTheft was assigned CVE-2026-43494 and is patched upstream (6.13.7/6.12.27/6.6.92/6.1.139/5.15.184) and across distros, with most defaulting to RDS disabled; only Arch is default-vulnerable. No in-the-wild exploitation or CISA KEV listing exists, but V12's weaponized public PoC keeps unpatched RDS+io_uring hosts at live risk.

Sources cited for PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

Threats related to PinTheft — Linux Kernel RDS Zerocopy FOLL_PIN Refcount

Detection coverage for TL-2026-0543

As of 2026-05-21, Threadlinqs Intelligence publishes 9 detection rule(s) for TL-2026-0543 across Splunk SPL, Microsoft KQL and Sigma, covering 18 indicator(s) of compromise. The whole corpus is readable without an account; a free account unlocks full detection query text in Splunk SPL, Microsoft KQL and Sigma; paid tiers add raw indicator values, correlation and the MCP server. Threadlinqs MCP server · View plans.

Threadlinqs Intelligence — Real-Time Threat Detection Platform

[ 0 threats ] [ 0 det ] [ CRIT: 0 ] [ HIGH: 0 ]
// threat_feed
$ sort --newest
Showing all threats

Latest Threats