Une fonction remplacée par une autre, deux fois, et un CVSS de 9.8
Avant : kfree(). Après : kfree_rcu(). C’est tout le correctif de CVE-2026-98432, répété dans deux fonctions distinctes, dans deux fichiers distincts. Si vous lisez d’abord le score CVSS, le bug paraît abstrait : 9.8, critique, sans privilèges, sans interaction de l’utilisateur. Si vous lisez d’abord le diff, l’abstraction disparaît. Le bug, c’est un seul nom de fonction, remplacé par le mauvais, à exactement deux endroits.
CVE-2026-98432 est un use-after-free dans la façon dont la pile IPv4 TCP du noyau Linux gère les options IP d’une connexion, présent depuis un commit de 2015 et désormais corrigé dans chaque branche stable maintenue.[1] Le CNA de NVD lui-même l’a noté 9.8, AV:N/AC:L/PR:N/UI:N : à distance, faible complexité, sans privilèges, sans interaction de l’utilisateur. Trois semaines après la divulgation, le bulletin de Red Hat ne nomme toujours aucune version corrigée, et le tracker d’Ubuntu classe toujours chaque version prise en charge comme vulnérable ou en cours de traitement.[2][3] Un modèle de recherche non publié d’Anthropic aurait transformé cela en un chemin zero-click fonctionnel vers root en moins d’une heure.[4] Nous reviendrons sur cette affirmation plus loin, avec précision, car un détail qui l’accompagne en ligne n’est pas ce qu’Anthropic a réellement dit.
J’ai lu le commit de correction moi-même, pas un résumé qui en serait fait. git.kernel.org a renvoyé une erreur HTTP 403 à une requête directe, donc j’ai lu ce même commit, identifié par son hash, depuis le miroir public de l’arbre stable que Greg Kroah-Hartman maintient sur GitHub.[5] Je n’ai pas reproduit cela en laboratoire. Cet article est construit en lisant des sources primaires de bout en bout, pas en exécutant un exploit, et cette distinction compte pour tout ce qui suit.
// The actual kfree -> kfree_rcu swap, per the fixing commit (dossier
// S2). Surrounding code is an illustrative reconstruction of shape, not
// a literal excerpt.
net/ipv4/af_inet.c, inet_sock_destruct():
- kfree(opt)
+ kfree_rcu(opt, rcu)
net/ipv4/tcp_ipv4.c, tcp_v4_reqsk_destructor():
- kfree(opt)
+ kfree_rcu(opt, rcu)Ce que « le libérer plus tard » est censé vous apporter
Voici l’invariant sur lequel repose une grande partie du code réseau du noyau, sans trop y réfléchir : un lecteur à l’intérieur de rcu_read_lock() part du principe que la mémoire qu’il regarde est toujours là, parce que la convention veut que les rédacteurs reportent leur libération effective jusqu’à ce que chaque lecteur ait fini, de façon démontrable. C’est le RCU, read-copy-update, la réponse du noyau à un problème difficile : comment laisser les lecteurs tourner vite et sans verrou, tout en permettant aux rédacteurs de supprimer des choses, sans qu’un lecteur ne touche jamais une mémoire déjà disparue ?
Un TCP request socket, l’objet du noyau pour une connexion semi-ouverte pendant la négociation, peut porter des options IP : des données fournies par l’attaquant ou par le réseau, attachées à l’en-tête IP du paquet SYN. Le noyau stocke ces options derrière un pointeur (ireq_opt sur le request socket, inet_opt une fois promu en socket complet) et les relit plus tard, lors de la construction du SYN-ACK ou de l’ACK, à l’intérieur de ip_options_build(). Cette lecture se fait sous RCU. Elle doit l’être : un second CPU traitant un SYN retransmis pour la même connexion peut légitimement se trouver dans cette fonction au moment même où un autre CPU démonte la connexion.[1][5]
Deux destructeurs, un raccourci, zéro période de grâce
Deux points d’appel ont rompu le contrat sur lequel repose RCU : inet_sock_destruct() dans net/ipv4/af_inet.c, et tcp_v4_reqsk_destructor() dans net/ipv4/tcp_ipv4.c ; les deux libéraient le même pointeur d’options IP avec un simple kfree(), immédiatement, au lieu du kfree_rcu() différé par RCU.[5] Un simple kfree() rend la mémoire disponible pour réutilisation à l’instant même où il s’exécute : aucune période de grâce, aucune attente que les lecteurs terminent. Tout CPU encore à l’intérieur de ip_options_build(), en train de lire le pointeur que ce destructeur vient de libérer, lit désormais une mémoire qui peut être réallouée et réécrite sous ses pieds.
Le côté lecteur utilisait RCU : peu coûteux, sans verrou, et tolérant aux libérations concurrentes tant que ces libérations sont différées. Deux points d’appel côté rédacteur n’ont pas tenu leur part du contrat. Ce décalage est exactement le genre de bug qu’un WAF, un contrôle d’authentification, ou tout autre contrôle au niveau applicatif ne verra jamais, parce qu’il vit entièrement à l’intérieur de la gestion mémoire propre du noyau, à deux appels de fonction d’une seule ligne de profondeur dans un chemin de destructeur.
// Illustrative timeline of the SYN-retransmit race (one of two trigger
// paths), reconstructed from the fixing commit's own description -- not
// a captured trace, and not exploit code.
CPU A (processing the connection) CPU B (retransmitted SYN)
---------------------------------- ----------------------------------
child socket created, inherits the
ireq_opt pointer -- not copied
tcp_v4_reqsk_destructor() runs:
kfree(ireq_opt) <-- freed now,
no grace period
still inside rcu_read_lock(),
ip_options_build() dereferences
the same pointer -- already
freed, possibly reusedLe SYN que vous retransmettez, ou le request socket que vous migrez
Le commit de correction décrit exactement deux façons d’atterrir dans cette fenêtre, et aucune n’exige rien d’inhabituel. La première : tcp_v4_syn_recv_sock() transmet les options IP d’un request socket à son socket enfant nouvellement créé sans les copier au préalable, si bien qu’un second CPU encore en train de traiter un SYN retransmis, comportement TCP tout à fait ordinaire, peut lire ces options après qu’elles ont déjà été libérées.[1][5] Les SYN retransmis se produisent constamment sur des réseaux réels, un attaquant n’a donc pas besoin d’inventer cette condition. Il doit seulement bien la chronométrer.
La seconde voie exige une condition supplémentaire : la migration de request socket TCP explicitement activée, soit via net.ipv4.tcp_migrate_req=1, soit via un programme BPF attaché utilisant SK_REUSEPORT_SELECT_OR_MIGRATE.[5] Ce n’est le réglage par défaut nulle part. Lorsqu’elle est activée, un request socket cloné partage le même pointeur d’options que l’original, et le même destructeur le libère sous les pieds de celui des deux qui l’utilise encore. Si vous exploitez une pile IPv4/IPv6 mixte, un détail compte ici : le commit de correction précise qu’IPv6 n’est pas touché par cette course à la retransmission, parce qu’IPv6 duplique les options de son request socket au lieu de les partager. Un problème de migration IPv6 apparenté est renvoyé à un correctif séparé.[1][5] Ce correctif ne concerne qu’IPv4.
Quarante-huit minutes jusqu’à un pointeur, cinq de plus jusqu’à root
Voici la partie de cette histoire qui est réellement devenue virale, et elle mérite d’être racontée exactement comme elle est. Le billet de recherche d’Anthropic elle-même, signé par son Frontier Red Team, affirme qu’un modèle de recherche interne non publié a trouvé et enchaîné des failles en un exploit distant, zero-click, contre des serveurs Linux faisant tourner un listener multipath TCP : sans identifiants, sans interaction de l’utilisateur, root à travers le réseau.[4][6][7] Le billet cite CVE-2026-98432 par son numéro. Selon les propres mots d’Anthropic, il a fallu 48 minutes pour exfiltrer un pointeur du noyau, puis cinq minutes de plus pour atteindre l’exécution de commandes shell.[4]
Un détail qui circule aux côtés de cette affirmation ne s’y trouve en réalité pas. Le nom de code « Project Glasswing » n’apparaît nulle part dans le billet d’Anthropic. Il remonte à une source secondaire non vérifiée, pas aux propres mots d’Anthropic, et je ne vais pas le répéter ici comme si Anthropic avait confirmé un nom qu’elle n’a jamais utilisé. Ce qui est corroboré de façon indépendante : deux médias secondaires rapportent le même numéro de CVE, le même score CVSS, et le même minutage de 48 puis 5 minutes, et tous deux remontent au billet d’Anthropic comme source.[6][7] L’un d’eux ajoute une phrase qui mérite qu’on s’y arrête avant la section suivante : « le dossier public de cette CVE décrit un correctif pour IPv4 TCP plutôt que pour multipath TCP, et les deux ne sont reliés que par le billet d’Anthropic. »[7]
Le bulletin de Red Hat ne dit jamais MPTCP. Le correctif non plus.
Cette phrase résume toute l’histoire, et c’est précisément là que la plupart des conseils de seconde main se trompent. Lisez le commit de correction du noyau lui-même : il décrit deux chemins de déclenchement en termes purement IPv4 TCP, retransmission de SYN et migration de request socket, sans jamais mentionner Multipath TCP.[1][5] Lisez le bulletin de Red Hat lui-même, RHSB-2026-016, et c’est la même chose : « les systèmes acceptant des connexions TCP entrantes » comme population exposée, une description générale de condition de concurrence, aucun MPTCP dans la précondition, le vecteur d’attaque ou les recommandations de mitigation.[2] Deux sources primaires. Aucune des deux ne mentionne ce que la moitié d’internet conseille de désactiver.
Voici ma propre lecture en mettant ces deux sources côte à côte, pas une phrase que l’une ou l’autre énonce explicitement, je vais donc le dire clairement : un listener MPTCP est une façon d’atteindre ce bug de manière fiable. Ce n’est pas la précondition qui définit le bug. Si un exploit précis et démontré a utilisé MPTCP comme point d’entrée choisi, et selon la section précédente c’est un détail plausible mais encore non vérifié d’un exploit rapporté, cela vous renseigne sur un chemin pratique pour gagner la course, pas sur ce que la vulnérabilité elle-même exige réellement.[4][7] Désactiver MPTCP (net.mptcp.enabled=0) retire ce seul type de listener comme surface de déclenchement. Cela ne touche pas le use-after-free sous-jacent, qui reste entièrement accessible via le traitement ordinaire des connexions TCP, sans rapport avec MPTCP.
// What the vulnerability actually requires (b), versus what one
// reported demonstration used (c). Don't let (c) stand in for (b).
(b) Real preconditions, per the kernel's own commit and Red Hat's
advisory -- neither names MPTCP:
- A listener processing IPv4 TCP SYNs and retransmitted SYNs
(ordinary behavior, any TCP listener), OR
- Request-socket migration explicitly enabled
(net.ipv4.tcp_migrate_req=1, or a BPF
SK_REUSEPORT_SELECT_OR_MIGRATE program) -- not the default.
- Precise timing to win the race window.
- No credentials, no user interaction (PR:N/UI:N).
(c) What one reported demonstration reportedly used to trigger it
reliably:
- A reachable multipath TCP (MPTCP) listener.
Disabling MPTCP removes (c). It leaves (b) exactly where it was.Corrigé dans le noyau. Pas encore dans le noyau que la plupart font tourner.
Le correctif upstream est en place, et il est daté avec précision : les branches stables sont nettes à partir de 5.10.272, 5.15.223, 6.1.190, 6.6.159, 6.12.113 et 6.18.56, avec 7.2.10 et le correctif d’origine marqué pour la branche mainline 7.3, tout cela correspondant exactement aux données fournies par l’éditeur lui-même à NVD.[1] C’est la partie facile de cette histoire. Savoir si le noyau sous votre charge de travail réelle correspond à l’un de ces numéros, c’est la partie qui demande une vérification, pas une supposition.
Le bulletin de Red Hat lui-même, mis à jour pour la dernière fois le 2026-10-30, qualifie cela d’« impact critique », mais ne nomme aucune version corrigée, aucun erratum, ni aucun score CVSS. Il indique seulement que Red Hat « accélère la publication des correctifs ».[2] Le tracker d’Ubuntu lui-même, au moment de la rédaction, classe chaque version prise en charge de 16.04 à 26.10 comme Vulnerable ou Vulnerable, en cours de traitement. Seule la 14.04, déjà hors support standard, est marquée comme saine.[3] La mitigation provisoire de Red Hat, si vous attendez une version corrigée, consiste en une règle nftables qui rejette les paquets IPv4 TCP entrants dont l’en-tête IP porte des options, ce qui laisse intactes les options ordinaires de la couche TCP et n’est explicitement pas présentée comme un substitut au correctif.[2]
Un chiffre supplémentaire compte ici, parce que les titres misent fortement sur l’urgence : au moment d’écrire ces lignes, CVE-2026-98432 ne figure pas dans le catalogue des Known Exploited Vulnerabilities de la CISA, vérifié de deux manières indépendantes, et aucune source examinée ici ne documente une exploitation confirmée dans la nature.[8][9] Ce n’est pas la même affirmation que « sans risque à dépriorisée ». Un 9.8 sans authentification et sans interaction de l’utilisateur gagne une urgence réelle à lui seul. Voici l’échelle honnête : critique et non corrigé dans la plupart des distributions, mais pas confirmé comme étant activement attaqué.
Unaffected starting at (NVD vendor data, matches the kernel's own
per-branch fixes exactly):
5.10.272 5.15.223 6.1.190 6.6.159
6.12.113 6.18.56 7.2.10 (original fix tagged for mainline 7.3)
Distro status, read from each vendor's own tracker as of this writing:
Red Hat (RHSB-2026-016): Critical impact. No fixed version or
errata named. "Expediting the release
of fixes." Last updated 2026-10-30.
Ubuntu (security tracker): every supported release, 16.04-26.10,
Vulnerable or Vulnerable, work in
progress. Only 14.04 (out of support)
is clear.Ce qui est confirmé, et ce qui n’est encore qu’une affirmation en ligne
Deux autres éléments circulent au sujet de cette CVE, et je ne vais trancher ni l’un ni l’autre, parce que le dossier ne me le permet pas. Une version des faits veut que Red Hat ait d’abord noté ceci 5.9, puis 8.1, puis 9.8, l’escalade étant en partie attribuée à la divulgation par Anthropic du minutage de l’exploit. Le bulletin actuel de Red Hat ne montre tout simplement aucun score, aucun vecteur, ni aucun historique de score, si bien que cette histoire précise de 5.9 à 8.1 puis 9.8 n’est ni confirmée ni réfutée par l’unique source primaire disponible pour la vérifier.[2]
L’autre est de savoir si une preuve de concept d’exploit réellement fonctionnelle est publique. Une source affirme que des PoC sont sur GitHub depuis le 2026-10-29 ; d’autres disent que l’exploit n’a jamais été publié. Je n’ai localisé ni vérifié de dépôt de PoC fonctionnel dans un sens ou dans l’autre. Si quelqu’un vous affirme avec assurance laquelle des deux versions est vraie, demandez-lui le dépôt, pas l’affirmation.
Ce qu’il faut vraiment vérifier cette semaine
Dans l’ordre qui compte vraiment, pas dans celui qui fait le meilleur titre.
Patch and verify, in order:
[ ] Confirm the kernel actually RUNNING, not the changelog: 5.10.272 /
5.15.223 / 6.1.190 / 6.6.159 / 6.12.113 / 6.18.56 / 7.2.10 or later.
[ ] If your distro hasn't shipped a fixed build yet, check its own
tracker directly -- don't trust a blog post's snapshot of it,
this one included. Apply Red Hat's interim nftables rule in the
meantime: drop inbound IPv4 TCP with IP header length > 5.
[ ] Check net.ipv4.tcp_migrate_req. If it's 1, or a BPF
SK_REUSEPORT_SELECT_OR_MIGRATE program is attached, the second
trigger path is open regardless of MPTCP.
[ ] Disable MPTCP if you want to (net.mptcp.enabled=0) -- a
reasonable attack-surface reduction, but record it as exactly
that, not as the fix for this CVE.
[ ] Don't use CISA's KEV list as a signal of how exposed you are
here. As of this writing this CVE isn't on it, and that's a
statement about confirmed in-the-wild use, not about your risk.Le bulletin disait « use-after-free ». Le diff disait exactement où.
Chaque bulletin sur cette CVE répète les mêmes trois mots : use-after-free, noyau, IPv4. Aucun d’eux, à lui seul, ne vous dit que l’erreur réelle était le choix de kfree() plutôt que kfree_rcu() dans deux destructeurs, ni que le conseil sur MPTCP qui circule traite la porte choisie par un exploit comme l’unique porte du bâtiment. Le diff me l’a dit. Le bulletin ne l’a pas fait, et n’essayait pas de le faire : ce n’est pas la fonction d’un bulletin.
C’est exactement l’écart que je recherche dans une revue de code sécurisée : pas si un changelog dit « corrigé », mais si le diff réel s’attaque à la cause racine ou seulement à l’unique chemin que quelqu’un a trouvé le premier par hasard. Si vous maintenez du code C ou C++ avec ce même type de schéma de propriété partagée et de durée de vie différée, via RCU ou autrement, et que vous voulez que quelqu’un lise les chemins de destructeur avec cette question en tête, c’est exactement le genre de revue que je fais. Dites-moi ce que vous faites tourner, et nous en définirons le périmètre.
Sources
- [1] NVD : page de détail de CVE-2026-98432
- [2] Red Hat : bulletin de sécurité RHSB-2026-016
- [3] Canonical (Ubuntu) : tracker de sécurité pour CVE-2026-98432
- [4] Anthropic (Frontier Red Team) : « Discovering vulnerabilities with Claude »
- [5] Arbre stable du noyau Linux : le commit de correction, lu via son miroir public sur GitHub
- [6] AI Weekly : rapport sur l’affirmation d’Anthropic concernant l’exploit enchaîné
- [7] The Hacker News : rapport sur l’affirmation d’Anthropic concernant l’exploit enchaîné
- [8] CISA : catalogue des Known Exploited Vulnerabilities, requête filtrée
- [9] CISA : catalogue des Known Exploited Vulnerabilities, flux JSON
Besoin de vérifier ce point dans votre propre produit ?
Revue de code sécuritéVous voulez apprendre à trouver vous-même ce type de vulnérabilité ?
Formation individuelle à la revue de code sécurisé