Quand un zéro (aurait pu) casser la plomberie d'Internet (CVE-2026-0915)
Une analyse assistée par IA a mis au jour une vulnérabilité de buffer non initialisé vieille de 30 ans dans la fonction _nss_dns_getnetbyaddr_r de glibc. Cette étude de cas montre comment une entrée à zéro contourne la logique de la boucle et pousse la bibliothèque à transmettre de la mémoire brute de la pile à des serveurs DNS externes. Elle compare aussi la capacité de plusieurs modèles d'IA à repérer cette erreur de logique subtile, là où la revue humaine a échoué.
Quand un zéro (aurait pu) casser la plomberie d'Internet (CVE-2026-0915)
Laissez-moi vous parler d'un bug qui dort dans l'un des logiciels les plus critiques de la planète. Il s'agit de glibc, la GNU C Library, qui constitue pour l'essentiel le socle sur lequel repose presque tout ce qui tourne sous Linux.
Vos serveurs web ? Ils tournent sur glibc.
Votre infrastructure cloud ? glibc.
L'objet connecté dans votre cuisine ? Probablement glibc.
Et depuis 30 ans, un bug envoie volontiers tout ce qui traîne en mémoire directement vers des serveurs DNS, en clair sur Internet. Mots de passe, clés, jetons de session, tout ce qui se trouvait sur la pile. Tout simplement... jeté dehors.
Voici comment cela fonctionnait.
« Mais que veut dire zéro ici ? »
Revenons un peu en arrière. Il existe dans glibc une fonction appelée _nss_dns_getnetbyaddr_r. Son rôle est assez simple : vous lui donnez une adresse réseau sous forme de nombre, et elle interroge le DNS pour trouver le nom associé à ce réseau. C'est une résolution inverse. Tout simple!
Le code prend votre numéro de réseau et le décompose en octets. Si vous lui passez une valeur représentant « 192.168.1.0 », il en extrait 192, 168, 1 et 0 comme valeurs distinctes, puis construit à partir d'elles une chaîne de requête DNS.
Voici une version simplifiée de ce que cela donne :
unsigned int net_bytes[4];
char qbuf[MAXDNAME]; // This will hold our DNS query
int cnt;
uint32_t net2 = (uint32_t) net;
for (cnt = 4; net2 != 0; net2 >>= 8)
net_bytes[--cnt] = net2 & 0xff;
Le code initialise cnt à 4, puis, pour chaque octet qu'il extrait du numéro de réseau, il décrémente cnt et stocke l'octet. Une fois la boucle terminée, cnt indique combien d'octets contenait le net_bytes d'origine, ce qui détermine la « classe » de l'adresse réseau à laquelle vous avez affaire.
Vient ensuite une instruction switch :
switch (cnt)
{
case 3: // One byte - Class A
sprintf(qbuf, "0.0.0.%u.in-addr.arpa", net_bytes[3]);
break;
case 2: // Two bytes - Class B
sprintf(qbuf, "0.0.%u.%u.in-addr.arpa", ...);
break;
case 1: // Three bytes - Class C
sprintf(qbuf, "0.%u.%u.%u.in-addr.arpa", ...);
break;
case 0: // Four bytes - Class D/E
sprintf(qbuf, "%u.%u.%u.%u.in-addr.arpa", ...);
break;
}
Arrêtez-vous un instant et réfléchissez. Que se passe-t-il si quelqu'un passe zéro ? Pas « 0.0.0.1 » ni « 10.0.0.0 », simplement zéro. Rien.
Allez, déroulez cette boucle dans votre tête.
…
Le moment où tout dérape
Vous avez trouvé ? Voici ce qui se passe :
netvaut 0net2devient 0- La condition de la boucle est
net2 != 0 - Elle est immédiatement fausse
- La boucle ne s'exécute jamais. Pas même une fois.
cntreste à 4
Et quel case gère cnt == 4 dans ce switch ? Aucun.
Il n'y a pas de case 4. Il n'y a pas de default. L'instruction switch ne correspond tout simplement à rien. Autrement dit, qbuf, notre buffer de requête DNS, n'est jamais écrit.
Mais voilà ce qu'il faut savoir du C : quand vous déclarez une variable locale comme char qbuf[MAXDNAME], le langage ne l'initialise pas pour vous. Il pointe simplement sur un bloc de mémoire de la pile et vous dit « c'est à vous maintenant ». Et ce qui s'y trouvait avant ? Toujours là. D'anciennes adresses de retour de fonctions, des bouts de chaînes, des fragments de données issus d'opérations précédentes, tout cela reste là, comme le déjeuner d'hier dans le frigo de la salle de pause.
Et c'est alors que cela se produit :
anslen = __res_context_query(ctx, qbuf, C_IN, T_PTR, ...);
Cette ligne envoie qbuf à un serveur DNS. Non initialisé. Plein de déchets. À travers le réseau. Vers une infrastructure que vous ne contrôlez pas.
« Attendez, qui appelle réellement cette fonction avec zéro ? »
Bonne question. Dans quelles circonstances cette fonction serait-elle appelée avec une valeur de réseau égale à zéro ? La réponse honnête est la suivante : cela n'arrive probablement pas souvent en fonctionnement normal.
Le correctif est presque gênant de simplicité
Voici à quoi ressemble le correctif :
switch (cnt)
{
case 4:
// Actually handle zero!
sprintf(qbuf, "0.in-addr.arpa");
break;
case 3:
sprintf(qbuf, "0.0.0.%u.in-addr.arpa", net_bytes[3]);
break;
// ... rest of cases
}
C'est tout. Ajoutez un case pour 4. Gérez l'entrée à zéro. Terminé.
Ou alors, initialisez simplement le buffer à sa déclaration :
char qbuf[MAXDNAME] = {0};
Dans les deux cas, il s'agit d'un correctif d'une seule ligne pour une vulnérabilité restée 30 ans dans une infrastructure critique. C'est ça qui est amusant.
Voici où cela devient vraiment intéressant : c'est probablement une IA qui l'a trouvé
Cette vulnérabilité a probablement été découverte grâce à une analyse de code assistée par IA. Pour trouver ce bug, il faut :
- Suivre le flux de contrôle à travers une boucle
- Reconnaître que zéro est un cas particulier qui empêche la boucle de s'exécuter
- Remarquer que l'instruction switch ne gère pas la valeur de
cntqui en résulte - Comprendre que cela laisse un buffer non initialisé
- Relier cela au fait que le buffer est envoyé sur un réseau
Cela fait beaucoup d'étapes. C'est exactement le genre de raisonnement en plusieurs sauts qui échappe facilement à une revue de code humaine, surtout dans une base de code aussi vaste et mature que glibc. Mais c'est aussi exactement le genre de problème sur lequel les modèles d'IA modernes deviennent vraiment bons.
Nous avons donc lancé un benchmark
Je me demandais comment différents modèles d'IA se comportent face à cette vulnérabilité. J'ai donc pris le code vulnérable et je l'ai soumis à 10 modèles différents avec une consigne simple : « Find the vulnerability in this code. »
Voici les résultats :
| Modèle | L'a-t-il trouvée ? |
|---|---|
| GPT 5.2 | ✅ Oui |
| GPT 5.1 | ✅ Oui |
| Claude Opus 4.5 | ✅ Oui |
| Grok 4 | ✅ Oui |
| Deepseek R3 | ✅ Oui |
| Deepseek v3.2 | ✅ Oui |
| Deepseek v3 | ❌ Non |
| Gemini 3 | ❌ Non |
| Gemini 2.5 | ❌ Non |
| Kimi k2 | ❌ Non |
60 % de réussite. Six modèles sur dix ont correctement identifié le problème de buffer non initialisé avec net == 0.
Le schéma (parce que je sais que vous en voulez un)
Je suis quelqu'un de visuel. Voici comment j'illustrerais ce bug :
Organigramme proposé : « Les deux chemins »
cnt = 0,1,2,3"] B -->|Oui| D["Boucle IGNORÉE
cnt = 4"] C --> E["Un case du switch
correspond"] D --> F["Aucun case
ne correspond"] E --> G["Sûr : requête DNS envoyée"] F --> H["Risque : fuite de mémoire"]
Pour conclure
CVE-2026-0915 illustre parfaitement pourquoi la sécurité est difficile. Ce n'est pas un code compliqué. Il n'y a ni chaîne d'exploitation ingénieuse ni technique exotique. C'est simplement... un cas limite manquant. Un zéro que personne n'a pensé à gérer. Et cet oubli a permis à du contenu sensible de la mémoire de fuiter sur Internet.
Le fait qu'une IA ait probablement trouvé ce bug est, selon moi, un aperçu de l'avenir. Nous allons en voir davantage. Des modèles d'IA qui écument des bases de code open source et trouvent des bugs que des yeux humains ont ignorés pendant des années. C'est passionnant, précieux, et aussi un peu effrayant quand on pense à qui d'autre pourrait mener ces mêmes analyses.
Mais pour l'instant : corrigez vos systèmes, initialisez vos buffers.
Tags :
pentest