
Exploit PoC en Rust pour LPE du noyau Linux CVE-2026-31431 (écriture page-cache AF_ALG) plus défense d'exécution eBPF bloquant la création de sockets AF_ALG via LSM ou kprobe sans redémarrage.
CVE-2026-31431 (Copy Fail) est une vulnérabilité d'élévation de privilèges locale dans le module algif_aead du noyau Linux (sous-système AF_ALG). Un défaut logique introduit en août 2017 permet à tout utilisateur local non privilégié d'écrire 4 octets contrôlés dans le cache de pages de n'importe quel fichier lisible via AF_ALG + splice(), puis d'exécuter un binaire setuid corrompu pour obtenir les droits root. L'exploit est déterministe — aucune condition de course, aucun décalage du noyau, aucun crash système. Il affecte toutes les principales distributions Linux fournissant des noyaux de 4.14 à 7.0-rc.
CVSS : 7.8 | Corrigé dans : noyau 7.0, 6.19.12, 6.18.22 | Correctif principal : commit a664bf3d603d
Sources : copy.fail, The Hacker News, Avis de CloudLinux
crates/
├── exp/ # Exploit PoC (réimplémentation en Rust)
├── copy_fail_guard/ # Chargeur eBPF en espace utilisateur — mode LSM
├── copy_fail_guard-ebpf/ # Programme eBPF LSM (hors workspace)
├── copy_fail_guard_kprobe/ # Chargeur eBPF en espace utilisateur — mode kprobe
└── copy_fail_guard_kprobe-ebpf/ # Programme eBPF kprobe (hors workspace)
scripts/
└── build_guard.sh # Compilation en une commande → sortie dans dist/
La cause racine est une chaîne de trois fonctionnalités indépendantes du noyau interagissant de manière non sécurisée :
AF_ALG — expose l'API crypto du noyau à l'espace utilisateur non privilégiésplice() — transfère les données de fichiers en zéro-copie comme références au cache de pages (pas des copies) dans le scatterlist cryptoauthencesn — utilise le tampon de sortie de l'appelant comme espace de travail, écrivant 4 octets à dst[assoclen + cryptlen]En 2017, une optimisation en place dans algif_aead.c (72548b093ee3) a fait en sorte que req->src == req->dst, enchaînant les pages du cache de pages dans le scatterlist de destination inscriptible. Lorsque authencesn écrit ses octets de travail, il dépasse le tampon de sortie dans les pages du cache de pages enchaînées. L'attaquant contrôle :
La page corrompue n'est jamais marquée comme sale — le fichier sur disque n'est pas touché, mais execve() lit depuis le cache de pages. Corrompre un binaire setuid → root.
Réimplémentation en Rust du PoC Python public de 732 octets. Cible /usr/bin/su, épisse ses pages du cache de pages dans un socket AEAD AF_ALG, et les écrase avec une charge utile shell compressée.
Nécessite Rust 1.85+ (édition 2024).
cargo build --release -p copy_fail
Le binaire est exclusivement Linux. Sur d'autres plateformes, il se termine avec une erreur Unsupported.
Avertissement : Ceci exploite une véritable faille du noyau. À exécuter uniquement sur des systèmes que vous possédez et contrôlez, idéalement une VM jetable.
Démarrez une VM exécutant un noyau vulnérable (toute distribution grand public avec un noyau < 7.0 / < 6.19.12 / < 6.18.22).
Copiez le binaire compilé dans la VM et exécutez-le en tant qu'utilisateur non root :
./target/release/copy_fail
Vulnérable : un shell root (#) apparaît. Exécutez whoami pour confirmer root.
Non vulnérable (noyau corrigé) : l'opération AF_ALG échoue ou le binaire su se comporte normalement. Vous verrez une erreur ou une invite de mot de passe su normale.
Une défense du noyau en temps réel qui bloque CVE-2026-31431 sans mettre à niveau le noyau ni redémarrer. Il utilise eBPF pour intercepter la création de sockets AF_ALG, coupant ainsi la première étape de l'exploit.
Deux modes sont fournis. Le chargeur en une commande sélectionne automatiquement le meilleur mode disponible :
| Mode LSM | Mode kprobe | |
|---|---|---|
| Comment il bloque | Renvoie -EPERM (création de socket refusée) | SIGKILL (processus tué) |
| Exigence du noyau | ≥ 5.7 avec paramètre de démarrage lsm=bpf | ≥ 5.3, aucun paramètre spécial |
| Nécessite un redémarrage pour activer | Peut-être (si lsm=bpf n'est pas déjà défini) | Non |
| Point d'accroche | Hook LSM socket_create | kprobe __sys_socket |
┌──────────────────────────────────────────────────┐
│ run_guard.sh │
│ • Détecte le support BPF LSM │
│ • LSM disponible → copy_fail_guard (EPERM) │
│ • LSM indisponible → copy_fail_guard_kprobe (KILL)│
└──────────────┬───────────────────────────────────┘
│
┌──────────────▼───────────────────────────────────┐
│ Programme eBPF │
│ si famille de socket == 38 (AF_ALG) │
│ → bloquer (EPERM ou SIGKILL) │
│ sinon │
│ → autoriser │
└──────────────────────────────────────────────────┘
Bloquer AF_ALG a un impact quasi nul sur les systèmes typiques :
AF_ALGAF_ALG (par ex. OpenSSL avec le moteur afalg activé, certains chemins de déchargement crypto embarqués)socket(AF_ALG, ...)Compilation en une commande (recommandée) :
./scripts/build_guard.sh
Ceci installe automatiquement les chaînes d'outils manquantes (nightly, bpf-linker), compile tous les programmes eBPF et les chargeurs en espace utilisateur, et génère tout dans dist/ :
dist/
├── copy_fail_guard.bpf.o # Programme eBPF LSM
├── copy_fail_guard_kprobe.bpf.o # Programme eBPF kprobe
├── copy_fail_guard # Chargeur en espace utilisateur (LSM)
├── copy_fail_guard_kprobe # Chargeur en espace utilisateur (kprobe)
└── run_guard.sh # Sélection automatique : sudo ./run_guard.sh
Copiez le répertoire dist/ sur n'importe quelle machine cible et exécutez sudo ./run_guard.sh pour activer la protection.
Compilation manuelle (étape par étape) :
Étape 1 : Compiler les programmes eBPF (doit être fait sous Linux) :
# Variante LSM
cd crates/copy_fail_guard-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
# Variante kprobe
cd crates/copy_fail_guard_kprobe-ebpf
cargo +nightly build --target bpfel-unknown-none -Z build-std=core --release
Étape 2 : Compiler les chargeurs en espace utilisateur :
cargo build --release -p copy_fail_guard -p copy_fail_guard_kprobe
Recommandé — sélectionne automatiquement le meilleur mode :
sudo ./dist/run_guard.sh
Manuel — exécutez un mode spécifique :
# Mode LSM (nécessite lsm=bpf)
sudo GUARD_BPF_OBJ=chemin/vers/copy_fail_guard.bpf.o RUST_LOG=info ./copy_fail_guard
# Mode kprobe (fonctionne partout)
sudo GUARD_BPF_OBJ=chemin/vers/copy_fail_guard_kprobe.bpf.o RUST_LOG=info ./copy_fail_guard_kprobe
Appuyez sur Ctrl-C pour détacher le programme eBPF et restaurer le comportement normal.
Avec la protection active dans un terminal :
# Dans un autre terminal, essayez l'exploit :
./target/release/copy_fail
# Mode LSM : "error: Operation not permitted"
# Mode kprobe : "已杀死" / "Killed"
# Ou testez directement avec Python :
python3 -c "import socket; socket.socket(38, 5, 0)"
# Mode LSM : PermissionError: [Errno 1] Operation not permitted
# Mode kprobe : Killed
AF_ALG en espace utilisateur sans condition. Il n'existe aucun mécanisme pour exempter des processus spécifiques par PID, cgroup ou nom de commande. Ceci est prévu pour une version future (via des cartes eBPF HashMap). Pour la grande majorité des systèmes, cela est acceptable car presque rien n'utilise AF_ALG.bpf-linker (elles ciblent bpfel-unknown-none et ne peuvent pas être des membres normaux du workspace).La plupart des distributions n'activent pas BPF LSM par défaut. Si vous souhaitez le mode LSM plus propre (EPERM au lieu de SIGKILL) :
# Vérifiez les LSM actuels :
cat /sys/kernel/security/lsm
# Si "bpf" est absent, ajoutez-le :
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
Le mode kprobe fonctionne sans cette étape.
Après avoir corrigé le noyau à ≥ 7.0 / ≥ 6.19.12 / ≥ 6.18.22 :
# Confirmez la version du noyau
uname -r
# Réexécutez l'exploit — il ne devrait plus produire de shell root
./target/release/copy_fail
Alternativement, confirmez que le module vulnérable est neutralisé :
modinfo algif_aead | grep filename
# S'il est intégré, mettez en liste noire l'initcall (nécessite un redémarrage)
sudo grubby --update-kernel=ALL --args="initcall_blacklist=algif_aead_init"
sudo reboot