CVE-2026-31635 (DirtyDecrypt) Linux Kernel Privilege Escalation Explained
| July 29, 2026
Key Takeaways
- DirtyDecrypt is a local privilege escalation flaw in the RxGK security layer of the Linux kernel's AF_RXRPC stack.
- The kernel decrypts unauthenticated token data in place, inside memory that may be shared with the page cache.
- splice and MSG_SPLICE_PAGES place real page-cache pages of a victim file directly into the attacker's packet.
- Because the attacker owns the key and every alternate cipher block, CBC decryption becomes a write of chosen bytes.
- Overwriting a readable SUID-root binary's cached contents and running it gives an unprivileged user root access.
- The Picus Platform simulates CVE-2026-31635 attacks so teams can validate security controls against exploitation of this vulnerability.
CVE-2026-31635 (DirtyDecrypt), also known as DirtyCBC, is a local privilege escalation vulnerability in the RxGK security layer of the Linux kernel's AF_RXRPC networking stack.
An unprivileged user on an affected machine can rewrite the cached contents of a root-owned SUID binary and then run it to obtain a root shell.
This post explains what DirtyDecrypt is, why it happens at the code level, how attackers exploit it, and how to simulate it to validate your security controls against this threat.
CVE-2026-31635 (DirtyDecrypt) at a Glance
|
Field |
Detail |
|
CVE ID |
CVE-2026-31635 |
|
Codename |
DirtyDecrypt, DirtyCBC |
|
Affected product |
Linux kernel, AF_RXRPC networking stack, YFS-RxGK security class (security class 6) |
|
Affected component |
net/rxrpc/rxgk.c, net/rxrpc/rxgk_app.c, net/rxrpc/rxgk_common.h [1] |
|
Vulnerability type |
Local privilege escalation through in-place decryption of unauthenticated data into shared pages |
|
Impact |
An unprivileged local user writes chosen bytes into the page cache of a readable SUID-root file and executes it as root |
What Is CVE-2026-31635 (DirtyDecrypt)?
DirtyDecrypt is an arbitrary page-cache write reachable by any local user, caused by the kernel performing an in-place cryptographic transform on network data that has not yet been authenticated, in memory that may be shared with the rest of the system.
The vulnerable sequence is this:
- rxgk_verify_response() receives the inbound RESPONSE packet and parses it.
- rxgk_extract_token() locates the encrypted token inside it.
- rxgk_decrypt_skb() describes the relevant byte range of the socket buffer to the crypto layer by building a scatterlist with skb_to_sgvec().
- crypto_krb5_decrypt() decrypts that scatterlist, with the source and the destination pointing at the same memory.
Whenever that scatterlist covers page-cache pages that arrived through MSG_SPLICE_PAGES, decrypting the packet rewrites the file's cached contents.
An attacker exploits this by splicing the page-cache pages of a readable SUID-root binary into the packet.
How Does CVE-2026-31635 (DirtyDecrypt) Work?
The attack runs over a sequence of inbound RESPONSE packets, each carrying one batch of chosen bytes. Because the kernel decrypts the token inside each packet in place and verifies the HMAC only afterwards, an unprivileged user who controls both the key and the packet's backing pages can steer what the decryption writes [1].
Below we explain five steps that make the attack possible:
Step 1: The Attacker Supplies the Key
The kernel looks up a server key for the incoming connection and derives the token encryption key from it, so decryption is fully predictable to whoever owns that key:
|
// The key comes from a keyring the local user populated, so an unprivileged attacker chooses the secret K behind it. server_key = rxrpc_look_up_server_security(conn, skb, kvno, enctype); // The Kerberos context is derived from that key's secret. rxgk_set_up_token_cipher(server_key, &token_enc, enctype, &krb5, GFP_NOFS); // Decryption then runs over the skb, in place. ret = rxgk_decrypt_skb(krb5, token_enc, skb, &ticket_offset, &ticket_len, &ec); |
Because the attacker installed the rxrpc_s secret K themselves, they can reproduce the kernel's key derivation in userspace:
|
Ke = DK(K, RXGK_SERVER_ENC_TOKEN) |
Step 2: splice() Places Page-Cache Pages
Cipher Block Chaining (CBC) decryption XORs each decrypted block with the previous ciphertext block, so the block sitting immediately in front of a target block determines the bytes written over it.
The attacker therefore needs one of their own blocks in front of every target block, and builds the RESPONSE body in a pipe that alternates the two:
|
vmsplice(attacker buffer) -> pipe # attacker-controlled block splice(target file page) -> pipe # a real page-cache page of the victim file vmsplice(attacker buffer) -> pipe # and repeat, interleaved splice(target file page) -> pipe splice(pipe -> UDP socket) # sent with MSG_SPLICE_PAGES: references, not copies |
Step 3: The Scatterlist Exposes the Victim’s File to the Crypto Layer
skb_to_sgvec() builds an index of where the packet's bytes physically live. It walks the socket buffer (skb) and fills in a scatterlist array, one entry per contiguous run of bytes. Each entry records three things: a pointer to a struct page, an offset into that page, and a length. The function returns how many entries it populated.
This way, skb_to_sgvec() walks the fragments and describes them to the crypto API. So the resulting scatterlist alternates attacker pages with victim pages :
wire[0] = attacker-controlled anonymous page
wire[1] = target file page-cache block
wire[2] = attacker-controlled anonymous page
wire[3] = target file page-cache block
...
wire[tail] = attacker-controlled authenticator and tag bytes
At this point the crypto layer holds a writable handle to a file the attacker cannot write to.
Step 4: CBC Block Chaining Converts Decryption Into a Chosen Write
CBC decrypts each 16-byte block by running the block through the cipher and then XOR-ing the result with the previous ciphertext block:
|
P[i] = AES_DEC(Ke, C[i]) XOR C[i-1] |
That dependency hands control to an attacker who owns every odd-numbered block in the chain. For a victim block sitting at position 2i+1, the preceding block 2i is attacker data, so the plaintext the kernel writes over the victim block is:
|
P[2i+1] = AES_DEC(Ke, target_block_i) XOR attacker_buf_i # Note: The wire index and the pair index are different counters: attacker block i lands at wire position 2i, and target block i at 2i+1. So, attacker_buf_i = C[2i] target_block_i = C[2i+1] |
Every term on the right except attacker_buf_i is already known to the attacker: Ke was derived in Step 1, and target_block_i is just the current contents of a file they can read.
So they solve for the one unknown and get exactly the bytes they want:
|
attacker_buf_i = AES_DEC(Ke, target_block_i) XOR chosen_plaintext_i |
Step 5: Authentication Runs After the Write
RxGK wraps AES-CTS-CBC with HMAC-SHA1 using the Kerberos krb5enc construction. The order of operations in the vulnerable code is:
|
plaintext = AES_CTS_CBC_DECRYPT(ciphertext) write plaintext back to the destination scatterlist // damage happens here verify HMAC // too late if HMAC fails: abort connection |
The connection aborts, nothing is rolled back, and the chosen bytes stay in the page cache.
As a result, an attacker can aim this at a readable SUID-root binary, overwrite its cached instructions, and run it to gain root.
How Picus Simulates CVE-2026-31635 (DirtyDecrypt) Attacks?
We also strongly suggest simulating CVE-2026-31635 attacks to test the effectiveness of your security controls against real-life cyber attacks using the Picus Platform. You can also test your defenses against other vulnerability exploitation attacks, such as regreSSHion, Citrix Bleed, and Follina, within minutes with a 14-day free trial of the Picus Platform.
Picus Threat Library includes the following threats for the CVE-2026-31635 attacks:
|
Threat ID |
Threat Name |
Attack Module |
|
65850 |
Linux Kernel DirtyDecrypt Elevation of Privilege Vulnerability Threat |
Network Infiltration |
|
80295 |
Linux Kernel DirtyDecrypt Elevation of Privilege Vulnerability Threat |
E-mail Infiltration |
Start simulating emerging threats today and get actionable mitigation insights with a 14-day free trial of the Picus Platform.
References
[1] D. Labs, “DirtyCBC: When Linux Kernel Decrypt-Before-MAC Turns Authenticated Encryption Into a Page-Cache Write,” Delphos Labs. Accessed: Jul. 27, 2026. [Online]. Available: https://delphoslabs.com/blog/36142374-e1fe-80a9-9456-d3c64df81bd5/linux-rxgk-decrypt-mac
