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

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

ini
[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)
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;
}

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)

c
// 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

bash
# 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

  1. Spray de nombreux messages System V msg_msg primaires/secondaires (taille 4KiB pour tenir dans kmalloc-cg-4k).
  2. Déclencher l'OOB de ksmbd pour corrompre le pointeur next d'un message primary afin que deux primary partagent un même secondary.
  3. Détecter la paire corrompue en taguant les queues et en scannant avec msgrcv(MSG_COPY) pour trouver des tags mismatched.
  4. 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).
  5. 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).
  6. 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.
  7. 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).
  8. 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

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