CVE-2026-43500: rxrpc Shared Fragment In-Place Decryption RCE
Linux rxrpc fails to unshare skbs with externally-owned paged fragments before AEAD decryption, enabling in-place crypto corruption via splice-loopback. CVSS 7.8.
A critical vulnerability has been discovered in Linux's networking code that could allow hackers to take complete control of a computer remotely, without needing the owner's permission.
Think of it like a security guard checking whether a visitor has permission to enter a building. The guard looks at one ID card, sees it's legitimate, and lets them through—but fails to notice the person is actually carrying stolen goods hidden in their bag. Similarly, Linux's rxrpc subsystem (part of the kernel that handles network communication) was only doing a partial security check before accepting incoming network packets.
The problem is that the code wasn't properly verifying that certain packets hadn't been tampered with. An attacker can exploit this by sending specially crafted network messages that appear legitimate but contain malicious code. Because the verification is incomplete, the kernel accidentally processes these bad packets and could allow the attacker to execute arbitrary commands with kernel-level privileges—essentially giving them the keys to the entire system.
This vulnerability primarily affects Linux servers and computers that are exposed directly to the internet. If your personal computer runs Windows or macOS, you're safe. Linux users running servers, cloud infrastructure, or certain distributions are at risk.
Here's what you should do: First, check if you run Linux—most regular computer users don't. If you do, check your system for updates and install them immediately when available. Second, if you're responsible for any websites or online services, contact your hosting provider to confirm they're patching their systems. Third, if you work in IT security, review your network architecture to minimize direct internet exposure to vulnerable systems.
The good news is this vulnerability hasn't been actively used by criminals yet, giving organizations a window to patch before attackers catch on.
Want the full technical analysis? Click "Technical" above.
CVE-2026-43500 is a kernel memory corruption vulnerability in the Linux rxrpc subsystem. The DATA-packet handler (rxrpc_input_call_event()) and RESPONSE handler (rxrpc_verify_response()) gate their skb-unshare logic exclusively on skb_cloned(). An skb that is not cloned but carries externally-owned paged fragments — set via SKBFL_SHARED_FRAG through splice()/vmsplice() into a UDP socket, or via a chained skb_frag_list — bypasses this gate entirely and falls through to the in-place decryption path. That path feeds the frag pages directly into the AEAD/skcipher scatter-gather list via skb_to_sgvec(), performing cryptographic operations on pages the kernel does not exclusively own.
Root cause: The skb unshare guard in rxrpc's crypto path checks only skb_cloned(), missing the skb_has_frag_list() and skb_has_shared_frag() conditions that also indicate externally-owned pages, allowing in-place AEAD decryption to corrupt userspace-accessible memory.
Trigger vector:splice() or vmsplice() into a UDP socket feeding rxrpc, causing __ip_append_data() to set SKBFL_SHARED_FRAG
Confirmed affected: Linux kernel through 6.17.x; tested on linux-aws 6.17.0-1013-aws (Ubuntu 24.04.4 LTS, AWS Graviton arm64)
Root Cause Analysis
The vulnerable gate in both handlers follows the same pattern. Here is the DATA handler path in rxrpc_input_call_event() as it existed before the fix:
/* net/rxrpc/input.c — rxrpc_input_call_event(), pre-patch */
static void rxrpc_input_call_event(struct rxrpc_call *call, struct sk_buff *skb)
{
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA) {
/*
* BUG: Only unshares when skb_cloned() is true.
* An skb with SKBFL_SHARED_FRAG (splice path) or
* a frag_list chain is NOT cloned — it passes this
* gate and goes straight to in-place decryption.
*/
if (skb_cloned(skb)) { // BUG: incomplete unshare guard
skb = skb_unshare(skb, GFP_ATOMIC);
if (!skb)
return;
}
/* Falls through to security op with shared pages still attached */
call->security->verify_packet(call, skb, /*...*/);
/* -> rxkad_verify_packet()
* -> rxkad_verify_packet_1() / _2()
* -> skb_to_sgvec(skb, sg, ...) // maps shared frag pages into SGL
* -> crypto_skcipher_decrypt(req) // in-place decrypt onto shared pages
*/
}
}
The same missing check appears in rxrpc_verify_response() for RESPONSE packets:
/* net/rxrpc/conn_service.c — rxrpc_verify_response(), pre-patch */
static int rxrpc_verify_response(struct rxrpc_connection *conn,
struct sk_buff *skb)
{
// BUG: skb_has_shared_frag() and skb_has_frag_list() not checked
if (skb_cloned(skb)) { // BUG: incomplete guard
skb = skb_unshare(skb, GFP_ATOMIC);
if (!skb)
return -ENOMEM;
}
return conn->security->verify_response(conn, skb, /*...*/);
/* -> rxkad_verify_response()
* -> skb_to_sgvec() -> crypto_skcipher_decrypt()
* in-place on externally-owned pages
*/
}
The skb_to_sgvec() call builds a scatterlist directly from the skb's frag array. When those frags were injected via splice(), the backing pages are shared with a pipe buffer the attacker still holds a reference to. The AEAD/skcipher then decrypts — writes — onto those pages.
The upstream PoC (dirtyfrag) and its arm64 port (dirtyfrag-arm64) demonstrate two exploitation vectors. On x86_64 both the ESP/xfrm path and the rxrpc path are viable for privilege escalation. On arm64, flush_dcache_page() is non-trivial and the rxrpc path kernel-oopses (see Architecture Delta below).
EXPLOIT CHAIN (x86_64, rxrpc path — corrupts /etc/passwd):
1. Attacker opens AF_RXRPC socket bound over UDP; configures rxkad security level
RXRPC_SECURITY_ENCRYPT on a loopback connection.
2. Construct a pipe with vmsplice(): map a page containing crafted plaintext,
splice it into the UDP socket's send queue. __ip_append_data() sets
SKBFL_SHARED_FRAG on the resulting skb — attacker retains pipe-buffer ref.
3. Kernel receives the DATA packet on the rxrpc loopback path.
rxrpc_input_call_event() evaluates:
skb_cloned(skb) == false (refcount == 1, not cloned)
→ unshare gate NOT taken
→ skb with SKBFL_SHARED_FRAG pages proceeds to crypto
4. rxkad_verify_packet() calls skb_to_sgvec(skb, sg, offset, len).
The scatterlist entries point directly to attacker-spliced pages.
5. crypto_skcipher_decrypt(req) performs PCBC decryption IN PLACE on those
shared pages. Attacker's pipe buffer now contains the decrypted (modified)
page data — effective write primitive to any page reachable via splice.
6. Target the pipe buffer at a page backing /etc/passwd or /usr/bin/su
in the page cache (dirty-page-cache technique). The in-place decrypt
writes attacker-controlled plaintext to the file's page cache entry.
7. msync() / fsync() flushes the corrupted cache page to disk.
/etc/passwd now contains a root-equivalent entry. LPE achieved.
Memory Layout
SKB STRUCTURE — SHARED FRAG CASE (splice path):
sk_buff:
+0x00 data → linear head (normal)
+0x?? skb_shinfo ptr → skb_shared_info below
skb_shared_info:
/* +0x00 */ __u8 nr_frags = 1
/* +0x01 */ __u8 flags = SKBFL_SHARED_FRAG (0x01) ← SET by splice
/* +0x08 */ skb_frag_t frags[1]:
page → struct page* (pipe_buffer.page — attacker holds ref)
offset = 0x0
size = 0x1000
/* +0x??*/ struct sk_buff *frag_list = NULL
skb_cloned() evaluation:
skb->cloned = 0 → skb_cloned() returns FALSE
skb_has_shared_frag() = TRUE ← NOT checked pre-patch
skb_has_frag_list() = FALSE
AFTER skb_to_sgvec() builds scatterlist:
sg[0].page_link → pipe_buffer.page ← attacker still has fd to pipe
sg[0].offset = 0
sg[0].length = 0x1000
AFTER crypto_skcipher_decrypt():
pipe_buffer.page contents OVERWRITTEN with PCBC-decrypted data
If page_cache_get_page(inode, index) returns same page → file corrupted
arm64 CRASH PATH (rxrpc path — oops, not exploit):
pc : flush_dcache_page+0x18/0x58
lr : skcipher_walk_done+0xbc/0x260
Call trace:
skcipher_walk_done+0xbc/0x260
crypto_pcbc_encrypt+0xe8/0x1c8 [pcbc]
crypto_skcipher_encrypt+0x48/0xb8
rxkad_secure_packet+0x108/0x270 [rxrpc]
rxrpc_send_data+0x264/0x550 [rxrpc]
...
Cause: flush_dcache_page() on arm64 dereferences struct page* metadata
for real dcache maintenance. When page refcount has been manipulated
through the splice/vmsplice chain, the page struct may be in an
inconsistent state → translation fault → oops.
On x86_64: flush_dcache_page() is a no-op (hardware-coherent caches).
On arm64: flush_dcache_page() = real dcache line flush via page PFN.
Patch Analysis
The fix extends the unshare guard to cover all three sharing conditions. Both rxrpc_input_call_event() and rxrpc_verify_response() receive identical treatment:
/* BEFORE (vulnerable) — net/rxrpc/input.c and net/rxrpc/conn_service.c */
if (skb_cloned(skb)) {
skb = skb_unshare(skb, GFP_ATOMIC);
if (!skb)
return; /* or return -ENOMEM */
}
/* AFTER (patched) — extends gate to all externally-owned-page conditions */
if (skb_cloned(skb) ||
skb_has_frag_list(skb) || // catches chained frag_list skbs
skb_has_shared_frag(skb)) { // catches splice SKBFL_SHARED_FRAG path
skb = skb_unshare(skb, GFP_ATOMIC);
if (!skb)
return; /* or return -ENOMEM */
}
/* skb is now a fully linearized, exclusively-owned copy.
* skb_to_sgvec() maps kernel-owned pages only.
* In-place AEAD decrypt is safe.
*/
skb_unshare() internally calls skb_copy() which allocates a new skb with a fresh linear data area, copies all fragment data into it, and drops the reference to the shared pages. The original externally-owned pages are untouched by the subsequent crypto operation.
Detection and Indicators
There is no in-kernel audit trail for the vulnerable path by default. Relevant detection signals:
Kernel oops on arm64 with flush_dcache_page in the trace and rxkad_secure_packet / rxrpc_send_data above it — indicates attempted rxrpc exploit path (oopses rather than escalates).
Unexpected page-cache modifications to /etc/passwd or setuid binaries detectable via inotify/fanotify on the affected inode.
splice()/vmsplice() calls into UDP sockets combined with AF_RXRPC socket activity from an unprivileged process — correlate via execve/socket/splice syscall audit records.
LKRG / kernel integrity monitors will flag unexpected writes to read-only page cache entries if configured for inode integrity checking.
Patch: Apply the upstream kernel fix that extends the unshare gate in both rxrpc_input_call_event() and rxrpc_verify_response() to include skb_has_frag_list() and skb_has_shared_frag(). Verify with grep -n "skb_has_shared_frag\|skb_has_frag_list" net/rxrpc/ post-patch.
Mitigation (no patch available): Disable AF_RXRPC if not required: install rxrpc /bin/false in /etc/modprobe.d/. This prevents socket creation entirely.
arm64 note: The rxrpc path oopses rather than escalates on arm64, but the oops itself is a denial-of-service. The ESP/xfrm path (CVE-2026-43284, also addressed by dirtyfrag) requires user namespace with uid_map write capability — restrict via kernel.unprivileged_userns_clone=0 where applicable to neutralize that vector.
AppArmor/seccomp: Restrict splice and vmsplice syscalls for untrusted processes; block AF_RXRPC socket creation in container profiles.