|
| 1 | +# ksmbd streams_xattr OOB write → local LPE (CVE-2025-37947) |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +This page documents a deterministic out-of-bounds write in ksmbd streams handling that enables a reliable Linux kernel privilege escalation on Ubuntu 22.04 LTS (5.15.0-153-generic), bypassing KASLR, SMEP, and SMAP using standard kernel heap primitives (msg_msg + pipe_buffer). |
| 6 | + |
| 7 | +- Affected component: fs/ksmbd/vfs.c — ksmbd_vfs_stream_write() |
| 8 | +- Primitive: page-overflow OOB write past a 0x10000-byte kvmalloc() buffer |
| 9 | +- Preconditions: ksmbd running with an authenticated, writable share using vfs streams_xattr |
| 10 | + |
| 11 | +Example smb.conf |
| 12 | + |
| 13 | +```ini |
| 14 | +[share] |
| 15 | + path = /share |
| 16 | + vfs objects = streams_xattr |
| 17 | + writeable = yes |
| 18 | +``` |
| 19 | + |
| 20 | +Root cause (allocation clamped, memcpy at unclamped offset) |
| 21 | +- The function computes size = *pos + count, clamps size to XATTR_SIZE_MAX (0x10000) when exceeded, and recomputes count = (*pos + count) - 0x10000, but still performs memcpy(&stream_buf[*pos], buf, count) into a 0x10000-byte buffer. If *pos ≥ 0x10000 the destination pointer is already outside the allocation, producing an OOB write of count bytes. |
| 22 | + |
| 23 | +<details> |
| 24 | +<summary>Vulnerable function snippet (ksmbd_vfs_stream_write)</summary> |
| 25 | + |
| 26 | +```c |
| 27 | +// https://elixir.bootlin.com/linux/v5.15/source/fs/ksmbd/vfs.c#L411 |
| 28 | +static int ksmbd_vfs_stream_write(struct ksmbd_file *fp, char *buf, loff_t *pos, size_t count) |
| 29 | +{ |
| 30 | + char *stream_buf = NULL, *wbuf; |
| 31 | + size_t size; |
| 32 | + ... |
| 33 | + size = *pos + count; |
| 34 | + if (size > XATTR_SIZE_MAX) { // [1] clamp allocation, but... |
| 35 | + size = XATTR_SIZE_MAX; |
| 36 | + count = (*pos + count) - XATTR_SIZE_MAX; // [1.1] ...recompute count |
| 37 | + } |
| 38 | + wbuf = kvmalloc(size, GFP_KERNEL | __GFP_ZERO); // [2] alloc 0x10000 |
| 39 | + stream_buf = wbuf; |
| 40 | + memcpy(&stream_buf[*pos], buf, count); // [3] OOB when *pos >= 0x10000 |
| 41 | + ... |
| 42 | + kvfree(stream_buf); |
| 43 | + return err; |
| 44 | +} |
| 45 | +``` |
| 46 | +
|
| 47 | +</details> |
| 48 | +
|
| 49 | +Offset steering and OOB length |
| 50 | +- Example: set file offset (pos) to 0x10018 and original length (count) to 8. After clamping, count' = (0x10018 + 8) - 0x10000 = 0x20, but memcpy writes 32 bytes starting at stream_buf[0x10018], i.e., 0x18 bytes beyond the 16-page allocation. |
| 51 | +
|
| 52 | +Triggering the bug via SMB streams write |
| 53 | +- Use the same authenticated SMB connection to open a file on the share and issue a write to a named stream (streams_xattr). Set file_offset ≥ 0x10000 with a small length to generate a deterministic OOB write of controllable size. |
| 54 | +- libsmb2 can be used to authenticate and craft such writes over SMB2/3. |
| 55 | +
|
| 56 | +Minimal reachability (concept) |
| 57 | +```c |
| 58 | +// Pseudocode: send SMB streams write with pos=0x0000010018ULL, len=8 |
| 59 | +smb2_session_login(...); |
| 60 | +smb2_open("\\\\host\\share\\file:stream", ...); |
| 61 | +smb2_pwrite(fd, payload, 8, 0x0000010018ULL); // yields 32-byte OOB |
| 62 | +``` |
| 63 | + |
| 64 | +Allocator behavior and why page shaping is required |
| 65 | +- 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. |
| 66 | +- 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. |
| 67 | +- 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. |
| 68 | + |
| 69 | +Practical page shaping strategy |
| 70 | +- Spray ~1000–2000 msg_msg objects of ~4096 bytes (fits kmalloc-cg-4k) to populate order-3 slabs. |
| 71 | +- Receive some messages to punch holes and encourage adjacency. |
| 72 | +- 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. |
| 73 | + |
| 74 | +Useful observability |
| 75 | +```bash |
| 76 | +# Check per-order freelists and migrate types |
| 77 | +sudo cat /proc/pagetypeinfo | sed -n '/Node 0, zone Normal/,/Node/p' |
| 78 | +# Example tracer (see reference repo) to log kvmalloc addresses/sizes |
| 79 | +sudo ./bpf-tracer.sh |
| 80 | +``` |
| 81 | + |
| 82 | +Exploitation plan (msg_msg + pipe_buffer), adapted from CVE-2021-22555 |
| 83 | +1) Spray many System V msg_msg primary/secondary messages (4KiB-sized to fit kmalloc-cg-4k). |
| 84 | +2) Trigger ksmbd OOB to corrupt a primary message’s next pointer so that two primaries share one secondary. |
| 85 | +3) Detect the corrupted pair by tagging queues and scanning with msgrcv(MSG_COPY) to find mismatched tags. |
| 86 | +4) Free the real secondary to create a UAF; reclaim it with controlled data via UNIX sockets (craft a fake msg_msg). |
| 87 | +5) Leak kernel heap pointers by abusing m_ts over-read in copy_msg to obtain mlist.next/mlist.prev (SMAP bypass). |
| 88 | +6) With an sk_buff spray, rebuild a consistent fake msg_msg with valid links and free it normally to stabilize state. |
| 89 | +7) Reclaim the UAF with struct pipe_buffer objects; leak anon_pipe_buf_ops to compute kernel base (defeat KASLR). |
| 90 | +8) Spray a fake pipe_buf_operations with release pointing to a stack pivot/ROP gadget; close pipes to execute and gain root. |
| 91 | + |
| 92 | +Bypasses and notes |
| 93 | +- KASLR: leak anon_pipe_buf_ops, compute base (kbase_addr) and gadget addresses. |
| 94 | +- 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. |
| 95 | +- Hardened usercopy: not applicable to this page overflow primitive; corruption targets are non-usercopy fields. |
| 96 | + |
| 97 | +Reliability |
| 98 | +- High once adjacency is achieved; occasional misses or panics (<10%). Tuning spray/free counts improves stability. Overwriting two LSBs of a pointer to induce specific collisions was reported as effective (e.g., write 0x0000_0000_0000_0500 pattern into the overlap). |
| 99 | + |
| 100 | +Key parameters to tune |
| 101 | +- Number of msg_msg sprays and hole pattern |
| 102 | +- OOB offset (pos) and resulting OOB length (count') |
| 103 | +- Number of UNIX socket, sk_buff, and pipe_buffer sprays during each stage |
| 104 | + |
| 105 | +Mitigations and reachability |
| 106 | +- Fix: clamp both allocation and destination/length or bound memcpy against the allocated size; upstream patches track as CVE-2025-37947. |
| 107 | +- Remote exploitation would additionally require a reliable infoleak and remote heap grooming; this write-up focuses on local LPE. |
| 108 | + |
| 109 | +References PoC and tooling |
| 110 | +- libsmb2 for SMB auth and streams writes |
| 111 | +- eBPF tracer script to log kvmalloc addresses and histogram allocations (e.g., grep 4048 out-4096.txt) |
| 112 | +- Minimal reachability PoC and full local exploit are publicly available (see References) |
| 113 | + |
| 114 | +## References |
| 115 | +- [ksmbd - Exploiting CVE-2025-37947 (3/3) — Doyensec](https://blog.doyensec.com/2025/10/08/ksmbd-3.html) |
| 116 | +- [libsmb2](https://github.com/sahlberg/libsmb2) |
| 117 | +- [KSMBD-CVE-2025-37947: proof-of-concept.c](https://github.com/doyensec/KSMBD-CVE-2025-37947/blob/main/proof-of-concept.c) |
| 118 | +- [KSMBD-CVE-2025-37947: CVE-2025-37947.c (full exploit)](https://github.com/doyensec/KSMBD-CVE-2025-37947/blob/main/CVE-2025-37947.c) |
| 119 | +- [bpf-tracer.sh](https://github.com/doyensec/KSMBD-CVE-2025-37947/blob/main/bpf-tracer.sh) |
| 120 | + |
| 121 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments