
Rust-Exploit-PoC für Linux-Kernel-LPE CVE-2026-31431 (AF_ALG Page-Cache-Write) plus eBPF-Laufzeitabwehr, die die AF_ALG-Socket-Erstellung per LSM oder Kprobe ohne Neustart blockiert.
CVE-2026-31431 (Copy Fail) ist eine lokale Privilegieneskalations-Schwachstelle im algif_aead-Modul des Linux-Kernels (AF_ALG-Subsystem). Ein im August 2017 eingeführter Logikfehler ermöglicht es jedem unprivilegierten lokalen Benutzer, 4 kontrollierte Bytes in den Page Cache einer beliebigen lesbaren Datei über AF_ALG + splice() zu schreiben und anschließend eine manipulierte setuid-Binary auszuführen, um Root-Rechte zu erlangen. Der Exploit ist deterministisch — keine Race Conditions, keine Kernel-Offsets, kein Systemabsturz. Er betrifft alle großen Linux-Distributionen mit Kerneln von 4.14 bis 7.0-rc.
CVSS: 7.8 | Behoben in: Kernel 7.0, 6.19.12, 6.18.22 | Mainline-Fix: Commit a664bf3d603d
Quellen: copy.fail, The Hacker News, CloudLinux Advisory
crates/
├── exp/ # Exploit-PoC (Rust-Neuimplementierung)
├── copy_fail_guard/ # Userspace-eBPF-Loader — LSM-Modus
├── copy_fail_guard-ebpf/ # eBPF-LSM-Programm (nicht im Workspace)
├── copy_fail_guard_kprobe/ # Userspace-eBPF-Loader — kprobe-Modus
└── copy_fail_guard_kprobe-ebpf/ # eBPF-kprobe-Programm (nicht im Workspace)
scripts/
└── build_guard.sh # Ein-Klick-Build → Ausgabe nach dist/
Die Grundursache ist eine Kette aus drei unabhängigen Kernel-Funktionen, die auf unsichere Weise zusammenwirken:
AF_ALG-Socket — stellt die Kernel-Krypto-API unprivilegiertem Userspace zur Verfügungsplice() — überträgt Dateidaten als Page-Cache-Referenzen (nicht als Kopien) mit Zero-Copy in die Krypto-Scatterlistauthencesn-AEAD-Template — verwendet den Ausgabepuffer des Aufrufers als Scratch-Space und schreibt 4 Bytes bei dst[assoclen + cryptlen]Im Jahr 2017 machte eine In-Place-Optimierung in algif_aead.c (72548b093ee3) req->src == req->dst, wodurch Page-Cache-Seiten in die beschreibbare Ziel-Scatterlist verkettet wurden. Wenn authencesn seine Scratch-Bytes schreibt, läuft es über den Ausgabepuffer hinaus in die verketteten Page-Cache-Seiten. Der Angreifer kontrolliert:
sendmsg()Die manipulierte Seite wird nie als dirty markiert — die Datei auf der Festplatte bleibt unberührt, aber execve() liest aus dem Page Cache. Manipuliere eine setuid-Binary → Root.
Rust-Neuimplementierung des öffentlichen 732-Byte-Python-PoCs. Ziel ist /usr/bin/su, dessen Page-Cache-Seiten in einen AF_ALG-AEAD-Socket gespleißt und mit einer komprimierten Shell-Payload überschrieben werden.
Erfordert Rust 1.85+ (Edition 2024).
cargo build --release -p copy_fail
Die Binary ist nur für Linux. Auf anderen Plattformen wird sie mit einem Unsupported-Fehler beendet.
Warnung: Dies nutzt einen echten Kernel-Fehler aus. Nur auf Systemen ausführen, die dir gehören und die du kontrollierst, idealerweise eine Wegwerf-VM.
Boote eine VM mit einem verwundbaren Kernel (jede gängige Distribution mit Kernel < 7.0 / < 6.19.12 / < 6.18.22).
Kopiere die gebaute Binary in die VM und führe sie als Nicht-Root-Benutzer aus:
./target/release/copy_fail
Verwundbar: Eine Root-Shell (#) erscheint. Führe whoami aus, um root zu bestätigen.
Nicht verwundbar (gepatchter Kernel): Der AF_ALG-Vorgang schlägt fehl oder die su-Binary verhält sich normal. Du siehst einen Fehler oder eine normale su-Passwortabfrage.
Eine Laufzeit-Kernel-Verteidigung, die CVE-2026-31431 blockiert, ohne den Kernel zu aktualisieren oder neu zu starten. Sie verwendet eBPF, um die Erstellung von AF_ALG-Sockets abzufangen und so den ersten Schritt des Exploits zu unterbinden.
Zwei Modi werden bereitgestellt. Der Ein-Klick-Loader wählt automatisch den besten verfügbaren Modus:
| LSM-Modus | kprobe-Modus | |
|---|---|---|
| Wie blockiert wird | Gibt -EPERM zurück (Socket-Erstellung verweigert) | SIGKILL (Prozess wird beendet) |
| Kernel-Anforderung | ≥ 5.7 mit lsm=bpf-Boot-Parameter | ≥ 5.3, keine speziellen Parameter |
| Neustart erforderlich | Möglicherweise (wenn lsm=bpf noch nicht gesetzt) | Nein |
| Hook-Punkt | socket_create-LSM-Hook | __sys_socket-kprobe |
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Erkennt BPF-LSM-Unterstützung │
│ • LSM verfügbar → copy_fail_guard (EPERM) │
│ • LSM nicht verfügbar → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ eBPF-Programm │
│ wenn Socket-Familie == 38 (AF_ALG) │
│ → blockieren (EPERM oder SIGKILL) │
│ sonst │
│ → erlauben │
└──────────────────────────────────────────────────┘
Das Blockieren von AF_ALG hat nahezu keine Auswirkungen auf typische Systeme:
AF_ALGAF_ALG konfiguriert sind (z. B. OpenSSL mit aktiviertem afalg-Engine, einige eingebettete Krypto-Offload-Pfade)socket(AF_ALG, ...) aufruftEin-Klick-Build (empfohlen):
./scripts/build_guard.sh
Dies installiert automatisch fehlende Toolchains (nightly, bpf-linker), kompiliert alle eBPF-Programme und Userspace-Loader und gibt alles nach dist/ aus:
dist/
├── copy_fail_guard.bpf.o # eBPF-LSM-Programm
├── copy_fail_guard_kprobe.bpf.o # eBPF-kprobe-Programm
├── copy_fail_guard # Userspace-Loader (LSM)
├── copy_fail_guard_kprobe # Userspace-Loader (kprobe)
└── run_guard.sh # Auto-Auswahl: sudo ./run_guard.sh
Kopiere das dist/-Verzeichnis auf ein beliebiges Zielsystem und führe sudo ./run_guard.sh aus, um den Schutz zu aktivieren.
Manueller Build (Schritt für Schritt):
Schritt 1: Kompiliere die eBPF-Programme (muss unter Linux erfolgen):
# LSM-Variante
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# kprobe-Variante
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Schritt 2: Baue die Userspace-Loader:
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Empfohlen — wählt automatisch den besten Modus:
sudo ./dist/run_guard.sh
Manuell — einen bestimmten Modus ausführen:
# LSM-Modus (erfordert lsm=bpf)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# kprobe-Modus (funktioniert überall)
sudo GUARD_BPF_OBJ=path/to/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Drücke Ctrl-C, um das eBPF-Programm zu trennen und das normale Verhalten wiederherzustellen.
Mit laufendem Guard in einem Terminal:
# In einem anderen Terminal den Exploit versuchen:
./target/release/copy_fail
# LSM-Modus: "error: Operation not permitted"
# kprobe-Modus: "已杀死" / "Killed"
# Oder direkt mit Python testen:
python3 -c "import socket; socket.socket(38, 5, 0)"
# LSM-Modus: PermissionError: [Errno 1] Operation not permitted
# kprobe-Modus: Killed
AF_ALG-Socket-Erstellungen bedingungslos. Es gibt keinen Mechanismus, um bestimmte Prozesse per PID, Cgroup oder Befehlsname auszunehmen. Dies ist für eine zukünftige Version geplant (über eBPF-HashMap-Maps). Für die überwiegende Mehrheit der Systeme ist dies in Ordnung, da fast nichts AF_ALG verwendet.bpf-linker kompiliert werden (sie zielen auf bpfel-unknown-none und können keine normalen Workspace-Mitglieder sein).Die meisten Distributionen aktivieren BPF-LSM nicht standardmäßig. Wenn du den saubereren LSM-Modus (EPERM statt SIGKILL) möchtest:
# Aktuelle LSMs prüfen:
cat /sys/kernel/security/lsm
# Wenn "bpf" fehlt, hinzufügen:
sudo sed -i 's/^GRUB_CMDLINE_LINUX="\(.*\)"/GRUB_CMDLINE_LINUX="\1 lsm=lockdown,capability,yama,apparmor,bpf"/' /etc/default/grub
sudo update-grub && sudo reboot
Der kprobe-Modus funktioniert ohne diesen Schritt.
Nach dem Patchen des Kernels auf ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22:
# Kernel-Version bestätigen
uname -r
# Exploit erneut ausführen — er sollte keine Root-Shell mehr erzeugen
./target/release/copy_fail
Alternativ bestätigen, dass das verwundbare Modul neutralisiert ist:
modinfo algif_aead | grep filename
# Wenn eingebaut, den Initcall auf die Blacklist setzen (erfordert Neustart)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot