
Blocage de la chaîne DirtyFrag LPE Linux (CVE-2026-43284 / CVE-2026-43500) au moment de l'exécution avec un TracingPolicy Cilium Tetragon
Bloquer la chaîne d'élévation de privilèges Linux DirtyFrag (CVE-2026-43284 / CVE-2026-43500) à l'exécution avec une TracingPolicy Cilium Tetragon. La politique SIGKILL la preuve de concept publique lors de l'étape de configuration de sa socket, avant qu'elle n'atteigne l'écriture dans le cache de pages qui lui donnerait root.
Ce que c'est. Un compte-rendu de laboratoire. J'ai configuré Tetragon pour la première fois et je voulais voir s'il pouvait réellement arrêter une véritable LPE (élévation de privilèges locale) actuelle du noyau. Il le pouvait. Ce document décrit exactement ce que j'ai exécuté, ce qui s'est déclenché et, tout aussi important, les limites de ce qu'il prouve. Ce n'est pas un substitut à l'application de correctifs.
Fichiers : ce README (autonome, preuves intégrées) · block-dirtyfrag.yaml (la politique)
| Exploit | DirtyFrag - PoC publique : V4bel/dirtyfrag |
| CVE | CVE-2026-43284 (écriture dans le cache de pages xfrm-ESP), CVE-2026-43500 (écriture dans le cache de pages RxRPC) |
| Hôte | Ubuntu 24.04.4 LTS, Tetragon v1.7.0 (autonome, systemd) |
Référence - sans politique, noyau 6.8.0-88 (vulnérable) | PoC → uid=0(root) |
Avec politique - noyau 6.8.0-88 | PoC SIGKILLé à socket(AF_RXRPC) ; l'utilisateur reste uid=1000 |
Noyau corrigé 6.8.0-134 | PoC échoue seul (rc=4) ; le crochet socket se déclenche toujours, détection, pas atténuation (remarque) |
| Nature du contrôle | Contrôle compensatoire / correctif virtuel, pas un correctif du noyau |
DirtyFrag est une chaîne d'élévation de privilèges locale construite à partir de deux primitives indépendantes d'écriture dans le cache de pages du noyau Linux : l'une dans le chemin de déchiffrement en place xfrm/ESP (IPsec) (CVE-2026-43284), et l'autre dans le chemin RxRPC (CVE-2026-43500). Chacune permet à un utilisateur local non privilégié d'écrire des octets contrôlés par l'attaquant dans des pages du cache de pages en lecture seule, par exemple l'image mise en cache d'un binaire setuid-root comme /bin/su – et d'obtenir ainsi root. L'écriture se fait uniquement en mémoire ; le fichier sur le disque n'est jamais modifié, donc la surveillance de l'intégrité des fichiers ne voit rien. Il s'agit de la même classe de bugs que Dirty Pipe et Copy Fail. Les deux CVE sont enchaînées délibérément : si un chemin n'est pas disponible dans un environnement donné, l'autre fonctionne toujours.
Sur cet hôte, les espaces de noms utilisateur non privilégiés sont restreints par AppArmor (kernel.apparmor_restrict_unprivileged_userns = 1), ce qui bloque la moitié ESP de la chaîne. Il reste le chemin RxRPC, qui ouvre une socket AF_RXRPC (famille d'adresse 33) – et c'est cette étape que cette politique tue.
Le vrai correctif est un noyau patché. Tout ceci est une solution de contournement pour un hôte que vous ne pouvez pas encore corriger. Ubuntu a livré les correctifs DirtyFrag quelque temps avant ce test, il s'agit donc d'un N-day connu, pas d'un 0-day en vie – le but est de montrer ce qu'un contrôle à l'exécution peut faire sur un hôte qui, pour une raison ou une autre, exécute encore un noyau vulnérable.
Politique complète : block-dirtyfrag.yaml. Elle installe deux kprobes.
Crochet 1 – sockets de préparation (sys_socket). Le contrôle principal. Le chemin RxRPC de l'exploit doit appeler socket(AF_RXRPC, …) à chaque exécution, qu'un module noyau soit déjà chargé ou non. La politique vérifie la famille d'adresse :
AF_RXRPC) → Sigkill. Sur tout hôte qui n'est pas un client AFS, pratiquement rien de légitime n'ouvre une socket AF_RXRPC, donc un kill aveugle ici est sûr et à haute confiance.AF_ALG) → Post (audit uniquement, pas de kill). AF_ALG est l'API de chiffrement noyau côté espace utilisateur et a des utilisateurs légitimes (cryptsetup, outils libkcapi, certains workflows FIPS). Tuer sur la seule famille entraînerait des faux positifs, donc cette branche se contente de journaliser. La voie vers le contrôle est : auditer pendant un certain temps, construire une liste d'autorisation NotIn à partir des binaires effectivement observés, puis envisager de passer à Sigkill.Crochet 2 – chargement automatique de modules vulnérables (security_kernel_module_request). Défense en profondeur. Se déclenche lorsque le noyau est invité à charger automatiquement une famille de modules vulnérables (esp4, esp6, rxrpc, les alias de sockets net-pf-33/net-pf-38, les modèles de chiffrement pcbc/fcrypt). Limite : il ne se déclenche que lorsque le module n'est pas déjà résident — après la première exécution de l'exploit dans un démarrage, ces modules sont chargés et ce crochet devient silencieux. C'est une couche de démarrage à froid, pas le contrôle principal.
Ensemble : le crochet 1 attrape l'exploit que les modules soient chauds ou froids ; le crochet 2 ajoute un déclenchement plus précoce et plus spécifique sur un hôte froid.
6.8.0-88-generic, qui est vulnérable (la référence atteint root). La machine dispose également de 6.8.0-134-generic installé — le noyau corrigé — et cela a été confirmé directement : redémarrer sur -134 fait échouer la PoC seule (rc=4, pas de root), avec ou sans politique (voir la remarque ci-dessous). Pour reproduire la démonstration d'atténuation, démarrez 6.8.0-88 depuis GRUB (il est toujours installé) — sur tout noyau corrigé, il n'y a rien à atténuer.AF_ALG.lockdown,capability,landlock,yama,apparmor — pas de bpf). Donc l'application utilise Sigkill depuis la kprobe, pas un refus LSM dans le noyau. Le kill a lieu à l'appel système socket() — avant la primitive d'écriture — donc il termine le processus à une étape précoce obligatoire plutôt que de "bloquer la vulnérabilité" elle-même.Les étapes 1 à 5 proviennent toutes du même démarrage 6.8.0-88 (le noyau vulnérable).
1. Environnement - Ubuntu 24.04.4, noyau 6.8.0-88-generic, Tetragon v1.7.0 actif sous systemd, espaces utilisateur non privilégiés restreints.
$ uname -r
6.8.0-88-generic
$ tetra version
CLI version: v1.7.0
$ systemctl is-active tetragon
active
2. Préconditions (politique OFF) - point de départ propre : aucun module vulnérable résident, pas d'état xfrm, pas de politique chargée.
$ lsmod | grep -E 'esp4|esp6|rxrpc' || echo "(none loaded)"
(none loaded)
$ sudo ip xfrm state # (empty)
$ sudo tetra tp list
ID NAME STATE FILTERID NAMESPACE SENSORS KERNELMEMORY MODE NPOST NENFORCE NMONITOR
3. Référence (politique OFF) - l'exploit fonctionne. C'est ce qui prouve que le noyau est réellement vulnérable ; tout le compte-rendu repose là-dessus.
$ ./exp ; id
uid=0(root) gid=0(root) groups=0(root)
4. Charger la politique.
$ sudo tetra tp add /etc/tetragon/tetragon.tp.d/block-dirtyfrag.yaml
tracing policy "…/block-dirtyfrag.yaml" added
5. Application (politique ON) - le kill. L'exploit est terminé par signal à l'appel socket() et n'atteint jamais su :
🚀 process /home/…/dirtyfrag/exp
❓ syscall /home/…/dirtyfrag/exp __x64_sys_socket
Kernel:
0x0: __x64_sys_socket+0x5
0x0: do_syscall_64+0x7f
0x0: entry_SYSCALL_64_after_hwframe+0x78
💥 exit /home/…/dirtyfrag/exp SIGKILL
L'événement structuré confirme à la fois la correspondance et le kill – un process_kprobe sur l'appel système socket, puis un process_exit par signal pour le même PID :
// process_kprobe — la correspondance AF_RXRPC
{ "process_kprobe": {
"process": { "pid": 24083, "uid": 1000, "binary": ".../dirtyfrag/exp" },
"function_name": "__x64_sys_socket",
"args": [ { "int_arg": 33, "label": "family" } ],
"policy_name": "block-dirtyfrag",
"action": "KPROBE_ACTION_POST"
} }
// process_exit — même PID, tué par signal
{ "process_exit": {
"process": { "pid": 24083, "binary": ".../dirtyfrag/exp" },
"signal": "SIGKILL"
} }
Le action de la kprobe indique KPROBE_ACTION_POST car l'action Post est celle qui émet l'événement visible ; l'action Sigkill est celle qui produit la sortie SIGKILL séparée. Les deux événements ensemble sont la preuve, la correspondance et le kill.
Remarque : la capture brute
07contient également une chaînemessaged'une révision antérieure de la politique (avant que la brancheAF_ALGne soit séparée pour l'audit uniquement). La correspondancefamily: 33et leSIGKILLrésultant sont identiques entre les révisions ; seul ce texte diffère.
Après l'exécution sur le noyau vulnérable, l'hôte a été redémarré sur 6.8.0-134-generic (le noyau corrigé d'Ubuntu) et la PoC a été relancée :
$ ./exp # politique OFF
dirtyfrag: failed (rc=4) # l'exploit échoue seul — pas de root
$ ./exp # politique ON
Killed # SIGKILL à socket(AF_RXRPC)
Soyons précis sur ce que cela montre :
rc=4, pas de root) car le noyau est corrigé. Il n'y a rien à empêcher pour la politique. L'avant/après qui porte la revendication d'atténuation n'existe que sur le noyau vulnérable -88.socket(AF_RXRPC), donc Tetragon la SIGKILLe toujours et remonte la tentative dans le journal, sur un noyau corrigé comme sur un noyau non corrigé. Cela a une valeur en tant que détection d'une tentative et comme défense en profondeur, mais ce n'est pas la même chose que d'arrêter un exploit fonctionnel.AF_RXRPC est légitime et un kill aveugle le casserait. Vérifier par nœud.AF_ALG est en audit uniquement par conception. Une variante utilisant uniquement le chemin AF_ALG avec des modules déjà chauds serait journalisée, pas tuée, jusqu'à ce que vous promouviez cette branche en application (après listage d'autorisation).AF_RXRPC est ouverte avant la primitive d'écriture. Cet ordre est ce qui rend un kill précoce efficace ; ce n'est pas une garantie concernant tous les exploits possibles.Configuration : Tetragon v1.7.0 autonome, démarré via systemctl ; politiques chargées avec tetra tracingpolicy add.