ksmbd streams_xattr OOB write → local LPE (CVE-2025-37947)

Tip

Apprenez et pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Parcourez le catalogue complet de HackTricks Training pour les parcours d’évaluation (ARTA/GRTA/AzRTA) et Linux Hacking Expert (LHE).

Support HackTricks

Cette page documente une écriture hors limites déterministe dans la gestion des streams de ksmbd, qui permet une élévation de privilèges fiable dans le kernel Linux sur Ubuntu 22.04 LTS (5.15.0-153-generic), en contournant KASLR, SMEP et SMAP à l’aide de primitives standard du kernel heap (msg_msg + pipe_buffer).

  • Composant affecté: fs/ksmbd/vfs.c — ksmbd_vfs_stream_write()
  • Primitive: page-overflow OOB write au-delà d’un buffer kvmalloc() de 0x10000 octets
  • Préconditions: ksmbd en cours d’exécution avec un share authentifié et inscriptible utilisant vfs streams_xattr

Exemple smb.conf

[share]
path = /share
vfs objects = streams_xattr
writeable = yes

Cause racine (allocation clamped, memcpy à un offset non clamped)

  • La fonction calcule size = *pos + count, clamp size à XATTR_SIZE_MAX (0x10000) quand il est dépassé, et recalcule count = (*pos + count) - 0x10000, mais effectue quand même memcpy(&stream_buf[*pos], buf, count) dans un buffer de 0x10000 bytes. Si *pos ≥ 0x10000, le pointeur de destination est déjà en dehors de l’allocation, ce qui produit un OOB write de count bytes.
  • streams_xattr stocke les SMB alternate data streams à l’intérieur des POSIX extended attributes, donc le plafond de 0x10000 vient de la limite de taille d’un seul xattr Linux plutôt que d’un champ du protocole SMB. Cela rend le bug pratique uniquement lorsque le partage active explicitement vfs objects = streams_xattr et que le filesystem supporte les xattrs.

Pourquoi l’offset d’écriture compte

  • Le chemin vulnérable n’est pas juste “write plus de 64KiB”. La vérification manquante était que *pos n’était pas validé par rapport à la longueur actuelle du stream (v_len) avant l’exécution de la logique d’ajout/copie.
  • Upstream a corrigé cela en rejetant les writes où *pos >= v_len avec -EINVAL. Avant le fix, un attaquant pouvait réutiliser un handle authentifié valide vers un named stream et envoyer un SMB2 WRITE brut dont file_offset pointe déjà à la fin ou au-delà du stream existant, ce qui transforme le memcpy() après clamp en un overflow de page déterministe.
  • Le PoC public le démontre en s’authentifiant avec libsmb2, en ouvrant un chemin de stream comme 1337:, en extrayant SessionId/TreeId/FileId, puis en envoyant un SMB2 WRITE forgé avec file_offset = 0x10018 et un Length faible.
Vulnerable function snippet (ksmbd_vfs_stream_write) ```c // https://elixir.bootlin.com/linux/v5.15/source/fs/ksmbd/vfs.c#L411 static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos, size_t count) { char *stream_buf = NULL, *wbuf; size_t size; ... size = *pos + count; if (size > XATTR_SIZE_MAX) { // [1] clamp allocation, but... size = XATTR_SIZE_MAX; count = (*pos + count) - XATTR_SIZE_MAX; // [1.1] ...recompute count } wbuf = kvmalloc(size, GFP_KERNEL | __GFP_ZERO); // [2] alloc 0x10000 stream_buf = wbuf; memcpy(&stream_buf[*pos], buf, count); // [3] OOB when *pos >= 0x10000 ... kvfree(stream_buf); return err; } ```

Offset steering and OOB length

  • Exemple : définissez le file offset (pos) à 0x10018 et la longueur d’origine (count) à 8. Après le clamping, count’ = (0x10018 + 8) - 0x10000 = 0x20, mais memcpy écrit 32 bytes à partir de stream_buf[0x10018], soit 0x18 bytes au-delà de l’allocation de 16 pages.

Triggering the bug via SMB streams write

  • Utilisez la même connexion SMB authentifiée pour ouvrir un fichier sur le share et envoyer un write vers un named stream (streams_xattr). Définissez file_offset ≥ 0x10000 avec une petite longueur pour générer un OOB write déterministe d’une taille contrôlable.
  • libsmb2 peut être utilisé pour s’authentifier et construire ce type de writes via SMB2/3.
  • En pratique, réutiliser la session SMB négociée est pratique car l’exploit n’a besoin que de patcher quelques champs dynamiques dans la requête WRITE (TreeId, SessionId, FileId) puis peut transmettre directement le paquet malformé sur le même socket.

Minimal reachability (concept)

// Pseudocode: send SMB streams write with pos=0x0000010018ULL, len=8
smb2_session_login(...);
smb2_open("\\\\host\\share\\file:stream", ...);
smb2_pwrite(fd, payload, 8, 0x0000010018ULL); // yields 32-byte OOB

Comportement de l’allocator et pourquoi le page shaping est requis

  • kvmalloc(0x10000, GFP_KERNEL|__GFP_ZERO) demande une allocation order-4 (16 pages contiguës) au buddy allocator lorsque size > KMALLOC_MAX_CACHE_SIZE. Ce n’est pas un objet de cache SLUB.
  • memcpy se produit immédiatement après l’allocation ; le spraying post-allocation est inefficace. Vous devez pré-groom la mémoire physique afin qu’une cible choisie se trouve immédiatement après le bloc alloué de 16 pages.
  • Sur Ubuntu, GFP_KERNEL puise souvent dans le type de migration Unmovable dans la zone Normal. Épuisez les freelists order-3 et order-4 pour forcer l’allocator à scinder un bloc order-5 en une paire adjacente order-4 + order-3, puis placez un slab order-3 (kmalloc-cg-4k) directement après le stream buffer.

Stratégie pratique de page shaping

  • Spray ~1000–2000 objets msg_msg d’environ 4096 bytes (compatible kmalloc-cg-4k) pour remplir les slabs order-3.
  • Recevez certains messages pour créer des trous et favoriser l’adjacence.
  • Déclenchez le ksmbd OOB à plusieurs reprises jusqu’à ce que le stream buffer order-4 se place immédiatement avant un slab msg_msg. Utilisez le tracing eBPF pour confirmer les addresses et l’alignement si disponible.

Observabilité utile

# Check per-order freelists and migrate types
sudo cat /proc/pagetypeinfo | sed -n '/Node 0, zone  Normal/,/Node/p'
# Example tracer (see reference repo) to log kvmalloc addresses/sizes
sudo ./bpf-tracer.sh

Ce qu’il faut tracer pendant le tuning

  • kvmalloc_node(0x10000) confirme quand l’écriture de stream vulnérable consomme réellement une allocation d’ordre 4.
  • load_msg/kretprobe:load_msg te permet d’estimer combien d’allocations msg_msgseg sont attachées à chaque message sprayé, ce qui est utile lors du tuning des tailles de messages primary/secondary pour un build kernel spécifique.
  • Si l’exploit est porté sur une autre distro/kernel, revérifie les noms de cache, les tailles de payload msg_msg inline, les offsets anon_pipe_buf_ops, et les adresses des gadgets plutôt que de supposer que les constantes Ubuntu 22.04 LTS 5.15.0-153-generic correspondent toujours.

Plan d’exploitation (msg_msg + pipe_buffer), adapté de CVE-2021-22555

  1. Spray beaucoup de messages System V msg_msg primary/secondary (taille 4KiB pour tenir dans kmalloc-cg-4k).
  2. Déclenche le OOB de ksmbd pour corrompre le pointeur next d’un message primary afin que deux primaries partagent un secondary.
  3. Détecte la paire corrompue en taguant les files d’attente et en scannant avec msgrcv(MSG_COPY) pour trouver des tags incohérents.
  4. Libère le vrai secondary pour créer un UAF ; reprends-le avec des données contrôlées via des sockets UNIX (forge un faux msg_msg).
  5. Leak des pointeurs du kernel heap en abusant du m_ts over-read dans copy_msg pour obtenir mlist.next/mlist.prev (SMAP bypass).
  6. Avec un spray sk_buff, reconstruis un faux msg_msg cohérent avec des liens valides et libère-le normalement pour stabiliser l’état.
  7. Reprends le UAF avec des objets struct pipe_buffer ; leak anon_pipe_buf_ops pour calculer la kernel base (bypass KASLR).
  8. Spray un faux pipe_buf_operations avec release pointant vers un stack pivot/ROP gadget ; ferme les pipes pour exécuter et obtenir root.

Bypasses et notes

  • KASLR : leak anon_pipe_buf_ops, calcule la base (kbase_addr) et les adresses des gadgets.
  • SMEP/SMAP : exécute la ROP dans le contexte kernel via le flux pipe_buf_operations->release ; évite les derefs userspace jusqu’après la chaîne disable/prepare_kernel_cred/commit_creds.
  • Hardened usercopy : non applicable à ce primitive d’overflow de page ; les cibles de corruption sont des champs non-usercopy.

Fiabilité

  • Élevée une fois l’adjacence obtenue ; quelques ratés ou panic occasionnels (<10%). Ajuster les comptes de spray/free améliore la stabilité. L’écrasement des deux bits de poids faible d’un pointeur pour induire des collisions spécifiques a été rapporté comme efficace (par ex. écrire le pattern 0x0000_0000_0000_0500 dans l’overlap).

Paramètres clés à ajuster

  • Nombre de sprays msg_msg et pattern des trous
  • Offset OOB (pos) et longueur OOB résultante (count')
  • Nombre de sprays de sockets UNIX, sk_buff et pipe_buffer pendant chaque étape

Mitigations et accessibilité

  • Correctif : borner à la fois l’allocation et la destination/la longueur, ou limiter memcpy à la taille allouée ; les patches upstream suivent cela sous CVE-2025-37947.
  • Une exploitation distante nécessiterait en plus un infoleak fiable et un grooming distant du heap ; ce texte se concentre sur le LPE local.

Voir aussi

Ksmbd Attack Surface And Fuzzing Syzkaller

Références PoC et tooling

  • libsmb2 pour l’auth SMB et les écritures de streams
  • Script traceur eBPF pour logger les adresses kvmalloc et faire un histogramme des allocations (par ex. grep 4048 out-4096.txt)
  • Un PoC minimal de reachability et un exploit local complet sont publiquement disponibles (voir References)

References

Tip

Apprenez et pratiquez AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Parcourez le catalogue complet de HackTricks Training pour les parcours d’évaluation (ARTA/GRTA/AzRTA) et Linux Hacking Expert (LHE).

Support HackTricks