ksmbd streams_xattr OOB write → local LPE (CVE-2025-37947)
Reading time: 7 minutes
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
Cette page documente une out-of-bounds write déterministe dans la gestion des streams de ksmbd qui permet une élévation de privilèges fiable du noyau Linux sur Ubuntu 22.04 LTS (5.15.0-153-generic), contournant KASLR, SMEP et SMAP en utilisant des primitives de heap noyau standard (msg_msg + pipe_buffer).
- Composant affecté : fs/ksmbd/vfs.c — ksmbd_vfs_stream_write()
- Primitive : page-overflow OOB write dépassant un tampon kvmalloc() de 0x10000 octets
- Conditions préalables : ksmbd en cours d'exécution avec un partage authentifié et inscriptible utilisant vfs streams_xattr
Exemple smb.conf
[share]
path = /share
vfs objects = streams_xattr
writeable = yes
Cause racine (allocation limitée, memcpy à un offset non limité)
- La fonction calcule size = *pos + count, limite size à XATTR_SIZE_MAX (0x10000) si cette valeur est dépassée, et recalcule count = (*pos + count) - 0x10000, mais effectue toujours memcpy(&stream_buf[*pos], buf, count) dans un tampon de 0x10000 octets. Si *pos ≥ 0x10000, le pointeur de destination est déjà en dehors de l'allocation, produisant un OOB write de count octets.
Extrait de la fonction vulnérable (ksmbd_vfs_stream_write)
// 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;
}
Contrôle de l'offset et longueur OOB
- Exemple : définir file offset (pos) à 0x10018 et original length (count) à 8. Après la troncature, count' = (0x10018 + 8) - 0x10000 = 0x20, mais memcpy écrit 32 octets à partir de stream_buf[0x10018], c.-à-d. 0x18 octets au-delà de l'allocation de 16 pages.
Déclencher le bug via SMB streams write
- Utilisez la même connexion SMB authentifiée pour ouvrir un fichier sur le partage et effectuer une write vers un named stream (streams_xattr). Réglez file_offset ≥ 0x10000 avec une petite longueur pour générer un OOB write déterministe de taille contrôlable.
- libsmb2 peut être utilisé pour s'authentifier et produire de telles writes sur SMB2/3.
Atteignabilité minimale (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
Allocator behavior and why page shaping is required
- kvmalloc(0x10000, GFP_KERNEL|__GFP_ZERO) requests an order-4 (16 contiguous pages) allocation from the buddy allocator when size > KMALLOC_MAX_CACHE_SIZE. This is not a SLUB cache object.
- memcpy occurs immediately after allocation; post-allocation spraying is ineffective. You must pre-groom physical memory so that a chosen target lies immediately after the allocated 16-page block.
- On Ubuntu, GFP_KERNEL often pulls from the Unmovable migrate type in zone Normal. Exhaust order-3 and order-4 freelists to force the allocator to split an order-5 block into an adjacent order-4 + order-3 pair, then park an order-3 slab (kmalloc-cg-4k) directly after the stream buffer.
Practical page shaping strategy
- Spray ~1000–2000 msg_msg objects of ~4096 bytes (fits kmalloc-cg-4k) to populate order-3 slabs.
- Receive some messages to punch holes and encourage adjacency.
- Trigger the ksmbd OOB repeatedly until the order-4 stream buffer lands immediately before a msg_msg slab. Use eBPF tracing to confirm addresses and alignment if available.
Useful observability
# 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
Plan d'exploitation (msg_msg + pipe_buffer), adapté de CVE-2021-22555
- Spray de nombreux messages System V msg_msg primaires/secondaires (taille 4KiB pour tenir dans kmalloc-cg-4k).
- Déclencher l'OOB de ksmbd pour corrompre le pointeur next d'un message primary afin que deux primary partagent un même secondary.
- Détecter la paire corrompue en taguant les queues et en scannant avec msgrcv(MSG_COPY) pour trouver des tags mismatched.
- Free le vrai secondary pour créer un UAF ; le récupérer avec des données contrôlées via UNIX sockets (construire un fake msg_msg).
- Leak des pointeurs du kernel heap en abusant d'un over-read de m_ts dans copy_msg pour obtenir mlist.next/mlist.prev (bypass SMAP).
- Avec un spray d'sk_buff, reconstruire un fake msg_msg cohérent avec des liens valides et le free normalement pour stabiliser l'état.
- Récupérer le UAF avec des objets struct pipe_buffer ; leak anon_pipe_buf_ops pour calculer la base du kernel (défaire KASLR).
- Spray un fake pipe_buf_operations avec release pointant vers un stack pivot/gadget ROP ; fermer les pipes pour exécuter et obtenir le root.
Bypasses and notes
- KASLR: leak anon_pipe_buf_ops, compute base (kbase_addr) and gadget addresses.
- SMEP/SMAP: execute ROP in kernel context via pipe_buf_operations->release flow; avoid userspace derefs until after disable/prepare_kernel_cred/commit_creds chain.
- Hardened usercopy: not applicable to this page overflow primitive; corruption targets are non-usercopy fields.
Reliability
- Élevée une fois que l'adjacence est atteinte ; échecs ou panics occasionnels (<10%). Ajuster les comptes de spray/free améliore la stabilité. Écraser les deux LSBs d'un pointeur pour induire des collisions spécifiques a été rapporté efficace (par ex., écrire le pattern 0x0000_0000_0000_0500 dans le chevauchement).
Paramètres clés à ajuster
- Nombre de sprays msg_msg et motif des holes
- Offset OOB (pos) et longueur OOB résultante (count')
- Nombre de sprays UNIX socket, sk_buff et pipe_buffer à chaque étape
Mitigations et atteignabilité
- Fix : clamp both allocation and destination/length or bound memcpy against the allocated size ; les patches upstream sont suivis comme CVE-2025-37947.
- L'exploitation distante nécessiterait en plus un infoleak fiable et un remote heap grooming ; ce write-up se concentre sur le LPE local.
References PoC and tooling
- libsmb2 for SMB auth and streams writes
- eBPF tracer script to log kvmalloc addresses and histogram allocations (e.g., grep 4048 out-4096.txt)
- Minimal reachability PoC and full local exploit are publicly available (see References)
References
- ksmbd - Exploiting CVE-2025-37947 (3/3) — Doyensec
- libsmb2
- KSMBD-CVE-2025-37947: proof-of-concept.c
- KSMBD-CVE-2025-37947: CVE-2025-37947.c (full exploit)
- bpf-tracer.sh
tip
Apprenez et pratiquez le hacking AWS :
HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP :
HackTricks Training GCP Red Team Expert (GRTE)
Apprenez et pratiquez le hacking Azure :
HackTricks Training Azure Red Team Expert (AzRTE)
Soutenir HackTricks
- Vérifiez les plans d'abonnement !
- Rejoignez le 💬 groupe Discord ou le groupe telegram ou suivez-nous sur Twitter 🐦 @hacktricks_live.
- Partagez des astuces de hacking en soumettant des PR au HackTricks et HackTricks Cloud dépôts github.
HackTricks