Contexte de la vulnérabilité
Dans le code source original de BSD, le compteur d’attachements de mémoire partagée (shm_nattch) est déclaré comme un short signé 16 bits. Sa valeur maximale est donc 32 767. En créant 127 segments SysV et en forçant 259 forks successifs, le compteur dépasse ce plafond et « wrappe » à -32 643. Cette sous‑débordement d’entier conduit à un use‑after‑free lorsqu’une opération de IPC_RMID est exécutée en concurrence avec l’attachement d’un nouveau segment, ouvrant la porte à une élévation de privilèges locale.
Mécanisme d’exploitation
L’exploitation s’appuie sur plusieurs étapes mesurées :
Map 127 SysV shared memory segments
unmap them via munmap()
fork 259 processes → counter = -32643
Le processus de heap grooming utilise les métriques exposées par sysctl(KERN_MALLOCSTATS) (kb_total, kb_totalfree) pour détecter l’allocation de nouvelles pages. Un processus déclenche IPC_RMID pendant qu’un autre attache un segment dont la taille a été manipulée, créant ainsi la condition de course.
Parallèlement, une socket IP est configurée avec IP_MULTICAST_TTL = 2, ce qui force l’allocation d’une structure struct ip_moptions de 16 octets. Les deux mots de 16 bits de cette structure contiennent les champs imo_num_memberships et imo_max_memberships. Un out‑of‑bounds write de valeur -1 écrase ces deux champs, puis la fermeture de la socket déclenche deux décréments atomiques sur le PTE auto‑référentiel, passant la page de user à supervisor et permettant l’exécution de code noyau via un pointeur prévisible.
Après la prise de contrôle, le code restaure le pointeur corrompu et ouvre un shell root, même avec les protections KARL, SMAP et SMEP activées.
Analyse des mitigations
OpenBSD a publié les errata 026 et 018 qui corrigent respectivement le dépassement de compteur et l’écriture hors limites. Au‑delà de ces correctifs, plusieurs stratégies sont proposées :
- Safe‑unlinking : vérification des invariants des listes doublement chaînées avant tout
TAILQ_REMOVE. FreeBSD et NetBSD implémentent déjà cette technique danssys/queue.h. - Randomisation du PTE auto‑référentiel : utilisation d’un octet aléatoire (
.openbsd.randomdata) pour choisir le slot PML4 (320 + (random & 127)), déplaçant la base du PTE entre0xffffa00000000000et0xffffdf8000000000à intervalles de 512 GiB. - Aléa du allocateur noyau : randomisation de l’adresse de base au démarrage et réordonnancement aléatoire des allocations à l’exécution. Le coût en performance est limité, car la zone d’aléa peut être réutilisée.
- Sanitizers du compilateur : l’array‑bounds sanitizer (sous‑ensemble d’UBSan) détecte les accès hors limites, tandis que les Alloc Tokens isolent les allocations contenant des pointeurs, rendant les débordements non exploitables.
Ces mesures ciblent les primitives d’exploitation utilisées dans le scénario : le compteur partagé, le pointeur du PTE et les structures contenant des fonctions. En les rendant imprévisibles ou en bloquant les écritures non‑validées, l’attaque devient non viable.
Implications et limites
La découverte montre que des bugs datant de la première version de BSD peuvent encore être exploités sur des systèmes modernes, même avec les protections les plus avancées. Cependant, l’exploitation dépend d’une configuration très précise (nombre exact de segments, forks, valeur de IP_MULTICAST_TTL) et d’une connaissance fine du layout mémoire, ce qui limite la portée à un attaquant local très motivé. La mise en œuvre des mitigations proposées réduit fortement la surface d’attaque, mais aucune solution ne garantit l’élimination totale des vulnérabilités liées à des compteurs signés ou à des structures prédictibles.