cloud's Blog

Security blog

View on GitHub
11 June 2026

CVE-2026-45257 (BUMSRAKETE) : Écriture dans le cache de pages FreeBSD via sendfile + kTLS

by cloud

CVE-2026-45257 est une vulnérabilité d’élévation de privilèges locaux (LPE) dans le noyau FreeBSD, découverte par le chercheur « Bumsrakete ». En enchaînant trois mécanismes du noyau — sendfile(2), kTLS RX et le decrypt AES-GCM logiciel — tout utilisateur non privilégié peut écrire des données arbitraires dans le cache de pages du noyau, et ainsi modifier n’importe quel fichier binaire système pour obtenir un shell root.

Caractéristiques principales :


TL;DR

Champ Valeur
CVE CVE-2026-45257
Alias BUMSRAKETE
Impact Importante — LPE noyau, écriture page-cache arbitraire
Composants sendfile(2) + kTLS (TCP_RXTLS_ENABLE) + AES-GCM logiciel
Type CWE-362 — Race condition / unsafe composability
Auteur Bumsrakete
Site bumsrake.de
PoC github.com/bumsrakete/bumsraketev1
Versions affectées FreeBSD 13.0 — 15.0 (amd64, arm64, riscv)
Correctif FreeBSD-SA-26:26.kTLS (2026-06-09)
Workaround sysctl kern.ipc.mb_use_ext_pgs=0

1. Contexte : le cache de pages FreeBSD

1.1 La promesse de sendfile(2)

sendfile(2) est un appel système qui permet de transférer le contenu d’un fichier vers un socket sans copie entre l’espace utilisateur et le noyau. Sous FreeBSD, cette opération utilise des mbufs M_EXTPG (external page mbufs) qui référencent directement les pages physiques du cache de pages (page cache). C’est un mécanisme de zero-copy : les données ne sont jamais dupliquées.

1.2 L’angle d’attaque : PHYS_TO_DMAP

FreeBSD utilise une région de mappage direct (DMAP) sur les architectures amd64, arm64 et riscv. La macro PHYS_TO_DMAP() convertit une adresse physique en adresse virtuelle noyau directement accessible. C’est indispensable pour les performances, mais cela signifie que toute écriture sur une page physique via DMAP est visible de tous les cadres de mapping qui pointent vers cette même page — y compris les mappings de fichiers dans d’autres processus.

Le danger devient concret lorsque le decrypt logiciel AES-GCM du kTLS écrit en place sur la page physique référencée par le mbuf M_EXTPG, car cette page est aussi la page du cache de pages du fichier original.


2. Les trois composants de la vulnérabilité

L’exploit exploite la composition non sécurisée de trois mécanismes indépendants du noyau FreeBSD :

2.1 sendfile(2) — production de mbufs M_EXTPG

sendfile(2) produit des mbufs de type M_EXTPG (external page) qui référencent directement les pages physiques du fichier dans le cache de pages. Ces mbufs sont normalement destinés à être transmis sur le wire par la couche réseau. Ils ne sont pas censés être modifiés.

/* Structure conceptuelle d'un mbuf M_EXTPG */
struct mbuf_ext_pgs {
    vm_page_t *pages;   /* Pages physiques du fichier */
    int npages;         /* Nombre de pages */
    uint8_t *data;      /* Données (offset dans la page) */
};

2.2 TCP_RXTLS_ENABLE — l’absence de privilege check

L’option de socket TCP_RXTLS_ENABLE active le decrypt TLS au niveau du noyau pour la réception (RX) sur un socket TCP. Le correctif de sécurité attendu serait un priv_check() pour vérifier que l’utilisateur a les droits nécessaires. Il n’y en a pas. Tout utilisateur non privilégié peut activer kTLS RX sur son socket.

/* Dans la fonction de gestion de TCP_RXTLS_ENABLE :
 * Aucun appel à priv_check() — n'importe qui peut activer kTLS RX */
static int
tcp_rxtls_enable(struct socket *so, ...)
{
    // MISSING: priv_check(curthread, PRIV_KTLS_RX);
    // → n'importe quel utilisateur peut passer ici
    ...
}

2.3 Le decrypt AES-GCM en place

Lorsque le noyau reçoit des données TLS sur un socket kTLS RX, le decrypt AES-GCM s’exécute en mode logiciel (software crypto) si l’accélération matérielle n’est pas disponible ou configurée. Ce decrypt écrit le plaintext directement dans le buffer mbuf via PHYS_TO_DMAP().

La combinaison est fatale : le decrypt écrit le résultat à l’adresse physique de la page du mbuf M_EXTPG, qui est aussi la page du cache de pages du fichier original.


3. Mécanisme de l’exploit

3.1 Principe général

L’attaquant :

  1. Ouvre un fichier binaire qu’il peut lire (ex: /usr/bin/su)
  2. Établit une connexion TCP loopback entre deux sockets
  3. Active kTLS RX sur le socket récepteur
  4. Utilise sendfile(2) pour transmettre le fichier + des en-têtes TLS personnalisés
  5. Les en-têtes TLS contiennent une clé AES, un salt et un IV choisis par l’attaquant
  6. Le noyau déchiffre en place : plaintext = file_bytes XOR keystream(K, IV)
  7. Puisque l’attaquant connaît K et IV, il contrôle le résultat

3.2 Écriture incrémentale

L’exploit opère par rounds successifs de 240 octets (RECORD_W) :

Round 1 : XOR keystream → écriture des octets 0-239 du shellcode
Round 2 : XOR keystream → écriture des octets 240-479 du shellcode
...
Round 36 : 36 × 240 = 8640 octets de shellcode écrits

Après 36 rounds (~1,5 secondes), le fichier /usr/bin/su contient le shellcode complet. L’attaquant n’a plus qu’à exécuter su :

/* Comportement du shellcode injecté */
setuid(0);              // Devenir root
execve("/tmp/.x", ...); // Lancer le binaire root

Le fichier /tmp/.x est un binaire déposé au préalable par l’attaquant, qui exécute un shell root (ou tout autre payload).

3.3 Bypass du flag schg

Le flag schg (system immutable, chflags schg) empêche toute modification du fichier même par root. Normalement, il faut booter en single-user pour le retirer.

L’exploit bypass ce flag car l’écriture ne passe pas par le VFS (Virtual File System) — elle écrit directement sur la page physique via PHYS_TO_DMAP(), sans jamais passer par vnode_write() ou tout autre mécanisme de contrôle d’accès du VFS. Le noyau n’a aucune idée qu’une modification est en cours.

3.4 Schéma de l’attaque

                     ┌────────────────────────────┐
                     │    Attaquant (user)          │
                     │                              │
                     │  1. open("/usr/bin/su")      │
                     │  2. socket() + loopback      │
                     │  3. TCP_RXTLS_ENABLE         │
                     │  4. sendfile() + TLS frames  │
                     └──────────┬───────────────────┘
                                │
                                ▼
               ┌────────────────────────────────┐
               │     Noyau FreeBSD               │
               │                                │
               │  sendfile → mbuf M_EXTPG       │
               │    ↓                           │
               │  kTLS RX → decrypt AES-GCM     │
               │    ↓                           │
               │  PHYS_TO_DMAP(phys_page)        │
               │    ↓                           │
               │  Écriture dans la page du       │
               │  cache de pages du fichier      │
               │    ↓                           │
               │  /usr/bin/su ← shellcode       │
               └────────────────────────────────┘
                                │
                                ▼
                     ┌────────────────────────────┐
                     │  Attaquant : "su" →        │
                     │  setuid(0) + execve("/tmp/.x")│
                     │  → SHELL ROOT              │
                     └────────────────────────────┘

4. Les trois gardes et leur échec

Le code noyau de FreeBSD comportait trois mécanismes censés prévenir ce type d’attaque. Tous les trois ont été contournés.

4.1 mb_unmapped_compress — gated sur m_len ≤ MLEN

Ce mécanisme devait compresser les mbufs non mappés lorsque leur taille était suffisamment petite pour tenir dans un mbuf standard.

/* Mécanisme de garde 1 : mb_unmapped_compress */
/* Problème : la condition est m_len ≤ MLEN (~224 octets) */
if (m->m_len <= MLEN) {
    mb_unmapped_compress(m);
}

L’exploit utilise RECORD_W = 240, soit quelques octets de plus que MLEN. La compression n’est jamais déclenchée. Le choix de 240 octets n’est pas anodin — c’est volontaire.

4.2 mb_unmapped_to_ext — remapping via sf_buf_kva = PHYS_TO_DMAP

Sur loopback, ce mécanisme devait remapper les données via sf_buf_kva() pour éviter une modification directe. Le problème est que sf_buf_kva() retourne une adresse DMAP — c’est exactement la même page physique.

/* Mécanisme de garde 2 : mb_unmapped_to_ext */
/* Problème : sur loopback, remapping via sf_buf_kva = PHYS_TO_DMAP */
/* → on reste sur la même page physique */

L’effet est nul : l’écriture se fait toujours au même endroit.

4.3 sb_mark_notready — pas de vérification M_EXTPG

Ce mécanisme devait marquer les sbufs comme « not ready » pour empêcher la modification. Mais la fonction sb_mark_notready() ne comporte aucune vérification du type de mbuf :

/* Mécanisme de garde 3 : sb_mark_notready */
/* Problème : aucun check pour M_EXTPG */
static void
sb_mark_notready(struct sockbuf *sb, struct mbuf *m)
{
    // Pas de :
    // if (m->m_flags & M_EXTPG) return (EINVAL);
    // → ne fait rien pour les mbufs M_EXTPG
}

Les trois gardes sont contournés ou inefficaces.


5. Analyse du correctif

Le correctif publié par FreeBSD le 9 juin 2026 (FreeBSD-SA-26:26.kTLS) introduit un nouvel état de crypto mbuf : KTLS_MBUF_CRYPTO_ST_SHAREDMBUF (valeur -2).

5.1 Principe du correctif

Le correctif introduit un nouvel état KTLS_MBUF_CRYPTO_ST_SHAREDMBUF (valeur -2) dans l’énumération, et deux vérifications indépendantes dans ktls_mbuf_crypto_state() :

/* Vérification 1 : mbuf M_EXTPG non anonyme (provenant d'un vnode) */
if ((mb->m_flags & M_EXTPG) != 0 &&
    (mb->m_epg_flags & EPG_FLAG_ANON) == 0)
    return (KTLS_MBUF_CRYPTO_ST_SHAREDMBUF);

/* Vérification 2 : mbuf sendfile de type EXT_SFBUF */
if ((mb->m_flags & M_EXT) != 0 &&
    mb->m_ext.ext_type == EXT_SFBUF)
    return (KTLS_MBUF_CRYPTO_ST_SHAREDMBUF);

Quand l’état SHAREDMBUF est détecté, le decrypt retourne EINVAL :

case KTLS_MBUF_CRYPTO_ST_SHAREDMBUF:
    error = EINVAL;
    break;

Le correctif inclut également un test ATF (ktls_receive_loopback_sendfile) qui vérifie qu’un fichier transmis via sendfile en boucle locale n’est pas corrompu après kTLS decrypt.

5.2 Workaround disponible

En attendant l’application du correctif, l’administrateur peut désactiver les mbufs EXTPG pour sendfile :

sysctl kern.ipc.mb_use_ext_pgs=0

Cette option revient à désactiver le zero-copy pour sendfile(2), ce qui a un impact sur les performances réseau mais élimine complètement la surface d’attaque.


6. Le site bumsrake.de — une mise en scène réfléchie

Le site bumsrake.de présente la vulnérabilité dans un style volontairement parodique : Comic Sans, rhétorique trumpiste, boutique de merch fictive, note de sévérité « 13/10 ». Le pied de page le précise explicitement : « The bug is real, the website is a parody. »

Ce choix de présentation ne doit pas masquer la gravité technique de la découverte. La vulnérabilité est bien réelle, le CVE est officiel, et le correctif a été publié par FreeBSD. Le site est un artefact de culture sécurité — un infosec roast comme la communauté sait en produire, à la manière de dc949 (la conférence DEF CON), mais avec une base technique solide.


7. Recommandations

  1. Appliquer le correctif FreeBSD-SA-26:26.kTLS immédiatement sur tous les systèmes FreeBSD en production
  2. Workaround temporaire : sysctl kern.ipc.mb_use_ext_pgs=0 si le correctif ne peut pas être appliqué immédiatement
  3. Restreindre l’accès utilisateur local aux systèmes critiques — l’exploit nécessite un shell non privilégié
  4. Surveiller les modifications des binaires système (AIDE, Tripwire, ou tout système d’intégrité de fichiers)
  5. Mettre à jour régulièrement — FreeBSD a réagi rapidement (correctif disponible en quelques jours)

8. Timeline

Date Événement
~2026-06-08 Découverte et rapport par Bumsrakete
2026-06-09 FreeBSD publie l’advisory SA-26:26.kTLS et le correctif
2026-06-09 Publication du PoC public sur GitHub
2026-06-10 Mise en ligne du site bumsrake.de
2026-06-11 Publication de cet article

Conclusion

CVE-2026-45257 (BUMSRAKETE) est une démonstration frappante de la difficulté de raisonner sur la composition de sous-systèmes dans un noyau. Pris isolément, aucun des trois mécanismes — sendfile(2), kTLS RX, decrypt logiciel AES-GCM — n’est intrinsèquement vulnérable. C’est leur combinaison qui crée la faille : un mbuf M_EXTPG produit par sendfile, un decrypt AES-GCM qui écrit en place via DMAP, et une option de socket sans vérification de privilège.

Le correctif de FreeBSD est élégant : plutôt que de réarchitecturer l’un des trois sous-systèmes, il ajoute une vérification précise sur l’état de partage du mbuf. Une solution ciblée pour un problème de composition.

L’exploit complet tient en environ 500 lignes de C, utilise AES-NI pour la génération du keystream et OpenSSL EVP pour le tag GMAC. Il compile avec :

cc -O3 -march=native -maes -msse4.1 -o bumsrakete bumsrakete.c -lcrypto

Le temps d’exécution annoncé est d’environ 1,5 secondes pour un LPE complet. C’est rapide, fiable, et difficile à détecter puisque l’écriture contourne complètement le VFS.


Références

Source URL
Site officiel BUMSRAKETE https://bumsrake.de/
GitHub PoC https://github.com/bumsrakete/bumsraketev1
FreeBSD Security Advisory SA-26:26.kTLS https://www.freebsd.org/security/advisories/FreeBSD-SA-26:26.kTLS.asc
CVE-2026-45257 https://nvd.nist.gov/vuln/detail/CVE-2026-45257

Have fun.

tags: security - freebsd - cve - exploit - lpe - kernel - ktls - sendfile - bumsrakete - pagecache