ksmbd-attack-surface-and-fuzzing-syzkaller.md (19628B)
1 --- 2 title: "ksmbd Attack Surface & SMB2/SMB3 Protocol Fuzzing (syzkaller)" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/pentesting-smb/ksmbd-attack-surface-and-fuzzing-syzkaller.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/pentesting-smb/ksmbd-attack-surface-and-fuzzing-syzkaller.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # ksmbd Attack Surface & SMB2/SMB3 Protocol Fuzzing (syzkaller) 14 15 ## Overview 16 This page summarizes practical techniques for exercising and fuzzing the Linux in-kernel SMB server (ksmbd) with syzkaller. It focuses on expanding the protocol attack surface through configuration, building a stateful harness capable of chaining SMB2 operations, generating grammar-valid PDUs, biasing mutations toward weakly covered code paths, and using syzkaller features such as `focus_areas` and `ANYBLOB`. The cited research enumerates specific CVEs; this page emphasizes the reusable methodology and concrete snippets that can be adapted to a lab.<sup>[[1]](#references)[[2]](#references)</sup> 17 18 Target scope: SMB2/SMB3 over TCP. Kerberos and RDMA are intentionally out-of-scope to keep the harness simple. 19 20 --- 21 22 ## Expand ksmbd Attack Surface via Configuration 23 By default, a minimal ksmbd setup leaves large parts of the server untested. Enable the following features to drive the server through additional parsers/handlers and reach deeper code paths:<sup>[[1]](#references)</sup> 24 25 - Global-level 26 - Durable handles 27 - Server multi-channel 28 - SMB2 leases 29 - Per-share-level 30 - Oplocks (on by default) 31 - VFS objects 32 33 Enabling these increases execution in modules such as: 34 - smb2pdu.c (command parsing/dispatch) 35 - ndr.c (NDR encode/decode) 36 - oplock.c (oplock request/break) 37 - smbacl.c (ACL parsing/enforcement) 38 - vfs.c (VFS ops) 39 - vfs_cache.c (lookup cache) 40 41 Notes 42 - Exact options depend on your distro’s ksmbd userspace (ksmbd-tools). Review /etc/ksmbd/ksmbd.conf and per-share sections to enable durable handles, leases, oplocks and VFS objects. 43 - Multi-channel and durable handles alter state machines and lifetimes, often surfacing UAF/refcount/OOB bugs under concurrency.<sup>[[1]](#references)</sup> 44 45 Minimal lab configuration (adjust to the options your kernel/userspace build actually exposes): 46 47 ```ini 48 [global] 49 map to guest = bad user 50 guest account = nobody 51 max connections = 65536 52 smb2 max credits = 8192 53 smb2 leases = yes 54 server multi channel support = yes 55 durable handles = yes 56 57 [fuzz] 58 path = /srv/ksmbd/fuzz 59 guest ok = yes 60 oplocks = yes 61 vfs objects = acl_xattr streams_xattr 62 ``` 63 64 Why these toggles matter 65 - `server multi channel support` is documented as experimental in current `ksmbd.conf(5)`, which makes it a good fuzz-only knob for race/lifetime bugs.<sup>[[13]](#references)</sup> 66 - `acl_xattr` and `streams_xattr` move traffic into Security Descriptor and alternate-data-stream backends instead of only the ordinary file I/O fast path.<sup>[[1]](#references)</sup> 67 68 --- 69 70 ## Authentication and Rate-Limiting Adjustments for Fuzzing 71 SMB3 needs a valid session. Implementing Kerberos in harnesses adds complexity, so prefer NTLM/guest for fuzzing: 72 73 - Allow guest access and set map to guest = bad user so unknown users fall back to GUEST. 74 - Accept NTLMv2 (patch policy if disabled). This keeps the handshake simple while exercising SMB3 code paths. 75 - Patch out strict credit checks when experimenting (post-hardening for CVE-2024-50285 made simultaneous-op crediting stricter). Otherwise, rate-limits can reject fuzzed sequences too early.<sup>[[1]](#references)</sup> 76 - Increase max connections (e.g., to 65536) to avoid early rejections during high-throughput fuzzing. 77 78 Caution: These relaxations are to facilitate fuzzing only. Do not deploy with these settings in production. 79 80 --- 81 82 ## Stateful Harness: Extract Resources and Chain Requests 83 SMB is stateful: many requests depend on identifiers returned by prior responses (SessionId, TreeID, FileID pairs). Your harness must parse responses and reuse IDs within the same program to reach deep handlers (e.g., smb2_create → smb2_ioctl → smb2_close).<sup>[[1]](#references)</sup> 84 85 Example snippet to process a response buffer after stripping the four-byte SMB-over-TCP framing header and cache IDs: 86 87 ```c 88 // process response. does not contain +4B PDU length 89 void process_buffer(int msg_no, const char *buffer, size_t received) { 90 uint16_t cmd_rsp = u16((const uint8_t *)(buffer + CMD_OFFSET)); 91 switch (cmd_rsp) { 92 case SMB2_TREE_CONNECT: 93 if (received >= TREE_ID_OFFSET + sizeof(uint32_t)) 94 tree_id = u32((const uint8_t *)(buffer + TREE_ID_OFFSET)); 95 break; 96 case SMB2_SESS_SETUP: 97 // The harness expects its first successful session-setup response here. 98 if (msg_no == 0x01 && received >= SESSION_ID_OFFSET + sizeof(uint64_t)) 99 session_id = u64((const uint8_t *)(buffer + SESSION_ID_OFFSET)); 100 break; 101 case SMB2_CREATE: 102 if (received >= CREATE_VFID_OFFSET + sizeof(uint64_t)) { 103 persistent_file_id = u64((const uint8_t *)(buffer + CREATE_PFID_OFFSET)); 104 volatile_file_id = u64((const uint8_t *)(buffer + CREATE_VFID_OFFSET)); 105 } 106 break; 107 default: 108 break; 109 } 110 } 111 ``` 112 113 Tips 114 - Keep one fuzzer process sharing authentication/state: better stability and coverage with ksmbd’s global/session tables. syzkaller still injects concurrency by marking ops async, rerun internally.<sup>[[6]](#references)</sup> 115 - Syzkaller's experimental `reset_acc_state` can reset accumulated state but may introduce substantial slowdown. Measure whether the added isolation is worth the throughput cost for the target.<sup>[[1]](#references)</sup> 116 117 ## Recover Coverage From ksmbd Worker Threads 118 Recent ksmbd fuzzing work added KCOV remote coverage support via a per-connection handle because SMB requests are received on the connection thread and then executed asynchronously by `handle_ksmbd_work()` kworkers. Without that plumbing, valid network traffic can still reach the target but syzkaller loses visibility into the worker-side execution, which makes deeper ksmbd paths look artificially cold.<sup>[[15]](#references)</sup> 119 120 Practical implications 121 - Prefer a kernel that already includes the per-connection `kcov_handle` support, or backport it in fuzz-only labs. 122 - Keep the KCOV handle tied to `struct ksmbd_conn`, not to individual `ksmbd_work` items: one connection can queue multiple outstanding requests concurrently. 123 - Re-check coverage after enabling leases, multichannel, and durable handles, because those features amplify async workqueue traffic and benefit the most from remote coverage.<sup>[[15]](#references)</sup> 124 125 ## Prefer a Hybrid Harness Over One Giant Pseudo-Syscall 126 If you keep extending the setup, use the custom pseudo-syscall mainly for the bootstrap steps that are annoying to express declaratively (negotiate/session-setup/tree-connect), then export the returned identifiers as syzkaller resources for follow-up operations. syzkaller explicitly discourages overusing pseudo-syscalls, and a hybrid model makes minimization/crossover noticeably less painful.<sup>[[1]](#references)[[14]](#references)</sup> 127 128 Example sketch: 129 130 ```text 131 resource ksmbd_sess[int64] 132 resource ksmbd_tree[int32] 133 resource ksmbd_fid[int64] 134 135 syz_ksmbd_bootstrap(..., sess ptr[out, ksmbd_sess], tree ptr[out, ksmbd_tree]) 136 syz_ksmbd_create(..., sess ksmbd_sess, tree ksmbd_tree, fid ptr[out, ksmbd_fid]) 137 syz_ksmbd_setinfo_acl(..., sess ksmbd_sess, tree ksmbd_tree, fid ksmbd_fid, ...) 138 syz_ksmbd_close(..., sess ksmbd_sess, tree ksmbd_tree, fid ksmbd_fid) 139 ``` 140 141 This keeps ordering information visible to the fuzzer instead of hiding the whole protocol behind one blob-oriented helper. 142 143 --- 144 145 ## Grammar-Driven SMB2 Generation (Valid PDUs) 146 Translate the Microsoft Open Specifications SMB2 structures into a fuzzer grammar so your generator produces structurally valid PDUs, which systematically reach dispatchers and IOCTL handlers.<sup>[[11]](#references)</sup> 147 148 Example (SMB2 IOCTL request): 149 150 ```text 151 smb2_ioctl_req { 152 Header_Prefix SMB2Header_Prefix 153 Command const[0xb, int16] 154 Header_Suffix SMB2Header_Suffix 155 StructureSize const[57, int16] 156 Reserved const[0, int16] 157 CtlCode union_control_codes 158 PersistentFileId const[0x4, int64] 159 VolatileFileId const[0x0, int64] 160 InputOffset offsetof[Input, int32] 161 InputCount bytesize[Input, int32] 162 MaxInputResponse const[65536, int32] 163 OutputOffset offsetof[Output, int32] 164 OutputCount len[Output, int32] 165 MaxOutputResponse const[65536, int32] 166 Flags int32[0:1] 167 Reserved2 const[0, int32] 168 Input array[int8] 169 Output array[int8] 170 } [packed] 171 ``` 172 173 This style forces correct structure sizes/offsets and dramatically improves coverage versus blind mutation.<sup>[[3]](#references)</sup> 174 175 ## Prioritise CREATE Contexts, ACLs, and Named Streams 176 Recent upstream fixes landed in parser families that are easy to miss if the corpus only contains generic `open/read/close` traffic. Give these paths dedicated grammar entries and seed packets: 177 178 - **CREATE contexts**: model lease and durable-handle blobs explicitly instead of mutating the whole create-context chain as one opaque buffer; include truncated `DataOffset`/`DataLength` pairs, v1/v2 durable reconnects, and reconnects with stale persistent IDs. 179 - **Security descriptors / ACLs**: fuzz `SET_INFO` and `QUERY_INFO` with malformed owner/group/DACL offsets, undersized ACEs, `num_subauth = 0/2`, large ACE counts, and partial descriptors while `acl_xattr` is enabled. 180 - **Named streams**: with `streams_xattr`, open paths such as `file:stream` and exercise `CREATE -> WRITE/READ -> CLOSE` using large offsets, reconnects, and sparse lengths. 181 - **Compound requests**: build request chains such as `READ -> QUERY_INFO(Security)`, `QUERY_DIRECTORY -> QUERY_INFO(FILE_ALL_INFORMATION)`, and `READ -> QUERY_INFO(EA)` so the second operation receives only the leftover response budget from the first. 182 183 That bias is worthwhile because recent bug fixes landed in create-lease parsing, durable-handle context parsing, DACL/ACE validation, and compound `QUERY_INFO` response builders. If those objects stay opaque, syzkaller tends to spend mutations on packet noise instead of the fields that actually gate parser depth.<sup>[[1]](#references)</sup> 184 185 For an exploitation-oriented example reached through `streams_xattr`, check [the dedicated named-stream OOB write page](https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/binary-exploitation/linux-kernel-exploitation/ksmbd-streams_xattr-oob-write-cve-2025-37947.md). 186 187 --- 188 189 ## Add Compound and Reconnect Scenarios to the Corpus 190 Recent fixes showed that request parsing is only half of the attack surface: ksmbd also breaks in response builders and cross-connection lifetime handling.<sup>[[1]](#references)</sup> 191 192 High-yield scenarios to model explicitly: 193 - **Compound related operations**: keep `NextCommand` grammar-valid and deliberately starve the second response with a first command that consumes most of the shared output buffer. 194 - **Truncated trailing compounds**: keep `NextCommand` internally consistent, but let the final chained element end exactly at the 64-byte SMB2 header boundary (or only 1-2 bytes beyond it) so the parser must prove `StructureSize2` is actually readable before touching it. A 2026 upstream fix showed this tail shape can trigger an OOB read in `ksmbd_smb2_check_message()`, so it deserves explicit seeds instead of hoping generic mutation will discover it.<sup>[[16]](#references)</sup> 195 - **Related-operation SessionId**: in follow-up compound commands, emit `SessionId = 0xffffffffffffffff` together with a valid first request to reach the special related-operation branch instead of the normal session lookup path. 196 - **Multichannel and durable reconnects**: reuse the same `ClientGUID`, `SessionId`, `TreeId`, and persistent file IDs across two sockets, then race second-channel `SESSION_SETUP` / reconnect against `LOGOFF`, disconnect, or scavenger cleanup. 197 198 Useful mutation knobs in those scenarios: 199 - `OutputBufferLength`, security-info masks, and `FileInfoClass`/`InfoType` combinations for `QUERY_INFO` 200 - Filenames that almost fill the remaining reply space after UTF-16 expansion 201 - EA names/values that consume the residual buffer exactly and still require 1-3 bytes of 4-byte alignment padding 202 203 These patterns map directly to recent bug families: response-buffer misaccounting in compound `QUERY_INFO` handlers, multichannel session UAFs in `ksmbd_sessions_deregister()`, and durable/lease create-context parser bugs that only show up when reconnect semantics are preserved. 204 205 --- 206 207 ## Directed Fuzzing With focus_areas 208 Use syzkaller’s experimental focus_areas to overweight specific functions/files that currently have weak coverage.<sup>[[4]](#references)</sup> Example JSON: 209 210 ```json 211 { 212 "focus_areas": [ 213 {"filter": {"functions": ["smb_check_perm_dacl"]}, "weight": 20.0}, 214 {"filter": {"files": ["^fs/smb/server/"]}, "weight": 2.0}, 215 {"weight": 1.0} 216 ] 217 } 218 ``` 219 220 This helps construct valid ACLs that hit arithmetic/overflow paths in smbacl.c. For instance, a malicious Security Descriptor with an oversized dacloffset reproduces an integer-overflow.<sup>[[1]](#references)</sup> 221 222 Reproducer builder (minimal Python): 223 224 ```python 225 def build_sd(): 226 import struct 227 sd = bytearray(0x14) 228 sd[0x00] = 0x00; sd[0x01] = 0x00 229 struct.pack_into('<H', sd, 0x02, 0x0001) 230 struct.pack_into('<I', sd, 0x04, 0x78) 231 struct.pack_into('<I', sd, 0x08, 0x00) 232 struct.pack_into('<I', sd, 0x0C, 0x10000) 233 struct.pack_into('<I', sd, 0x10, 0xFFFFFFFF) # dacloffset 234 while len(sd) < 0x78: 235 sd += b'A' 236 sd += b"\x01\x01\x00\x00\x00\x00\x00\x00" # minimal DACL 237 sd += b"\xCC" * 64 238 return bytes(sd) 239 ``` 240 241 --- 242 243 ## Breaking Coverage Plateaus With ANYBLOB 244 syzkaller’s anyTypes (ANYBLOB/ANYRES) allow collapsing complex structures into blobs that mutate generically.<sup>[[5]](#references)</sup> Seed a new corpus from public SMB pcaps and convert payloads into syzkaller programs calling your pseudo-syscall (e.g., syz_ksmbd_send_req):<sup>[[12]](#references)</sup> 245 246 ```bash 247 # Extract SMB payloads to JSON 248 # tshark -r smb2_dac_sample.pcap -Y "smb || smb2" -T json -e tcp.payload > packets.json 249 ``` 250 251 ```python 252 import json, os 253 os.makedirs("corpus", exist_ok=True) 254 255 with open("packets.json") as f: 256 data = json.load(f) 257 # adjust indexing to your tshark JSON structure 258 packets = [e["_source"]["layers"]["tcp.payload"] for e in data] 259 260 for i, pkt in enumerate(packets): 261 pdu = pkt[0] 262 pdu_size = len(pdu) // 2 # hex string length → bytes 263 with open(f"corpus/packet_{i:03d}.txt", "w") as f: 264 f.write( 265 f"syz_ksmbd_send_req(&(&(0x7f0000000340))=ANY=[@ANYBLOB=\"{pdu}\"], {hex(pdu_size)}, 0x0, 0x0)" 266 ) 267 ``` 268 269 This jump-starts exploration and can immediately trigger UAFs (e.g., in ksmbd_sessions_deregister) while lifting coverage a few percent.<sup>[[1]](#references)</sup> 270 271 Higher-value captures to seed on purpose 272 - Lease negotiation / lease-break traces 273 - Durable-handle reconnects and reconnect failures 274 - `SET_INFO` / `QUERY_INFO` packets carrying Security Descriptors 275 - Named-stream paths (`file:stream`) when `streams_xattr` is enabled 276 - IOCTL-heavy traces (FSCTL/pipe/share-management paths) rather than only directory enumeration 277 278 --- 279 280 ## Sanitizers: Beyond KASAN 281 - KASAN remains the primary detector for heap bugs (UAF/OOB).<sup>[[8]](#references)</sup> 282 - KCSAN may report real but low-impact or intentionally tolerated data races in this target; triage each report rather than treating every race as an exploitable bug.<sup>[[10]](#references)</sup> 283 - UBSAN/KUBSAN can catch declared-bounds mistakes that KASAN misses due to array-index semantics. Example:<sup>[[9]](#references)</sup> 284 285 ```c 286 id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]); 287 struct smb_sid { 288 __u8 revision; __u8 num_subauth; __u8 authority[NUM_AUTHS]; 289 __le32 sub_auth[SID_MAX_SUB_AUTHORITIES]; /* sub_auth[num_subauth] */ 290 } __attribute__((packed)); 291 ``` 292 293 Setting num_subauth = 0 triggers an in-struct OOB read of sub_auth[-1], caught by UBSAN’s declared-bounds checks. 294 295 --- 296 297 ## Throughput and Parallelism Notes 298 - A single fuzzer process (shared auth/state) tends to be significantly more stable for ksmbd and still surfaces races/UAFs thanks to syzkaller’s internal async executor. 299 - The cited setup reached hundreds of SMB commands per second across multiple VMs and reported function-level coverage around 60% of `fs/smb/server` and 70% of `smb2pdu.c`. Treat these as environment-specific observations, not expected guarantees; function coverage also under-represents state-transition coverage.<sup>[[1]](#references)</sup> 300 301 --- 302 303 ## Practical Checklist 304 - Enable durable handles, leases, multi-channel, oplocks, and VFS objects in ksmbd. 305 - Allow guest and map-to-guest; accept NTLMv2. Patch out credit limits and raise max connections for fuzzer stability. 306 - Build a stateful harness that caches SessionId/TreeID/FileIDs and chains create → ioctl → close. 307 - Use a grammar for SMB2 PDUs to maintain structural validity. 308 - Use focus_areas to overweight weakly-covered functions (e.g., smbacl.c paths like smb_check_perm_dacl). 309 - Seed with ANYBLOB from real pcaps to break plateaus; pack seeds with syz-db for reuse.<sup>[[7]](#references)</sup> 310 - Run with KASAN + UBSAN; triage UBSAN declared-bounds reports carefully. 311 - Add compound-request sequences and multi-socket reconnects; mutate `NextCommand`, related-operation `SessionId`, `OutputBufferLength`, EA padding, and UTF-16 filename expansion boundaries. 312 313 --- 314 315 ## References 316 317 - [1] [Doyensec – ksmbd Fuzzing (Part 2)](https://blog.doyensec.com/2025/09/02/ksmbd-2.html) 318 - [2] [Doyensec – ksmbd vulnerability research (Part 1)](https://blog.doyensec.com/2025/01/07/ksmbd-1.html) 319 - [3] [pwning.tech – Tickling ksmbd: fuzzing SMB in the Linux kernel](https://pwning.tech/ksmbd-syzkaller/) 320 - [4] [syzkaller (GitHub repository)](https://github.com/google/syzkaller) 321 - [5] [syzkaller ANYBLOB/anyTypes (commit 9fe8aa4)](https://github.com/google/syzkaller/commit/9fe8aa4) 322 - [6] [syzkaller async executor change (commit fd8caa5)](https://github.com/google/syzkaller/commit/fd8caa5) 323 - [7] [syz-db](https://github.com/google/syzkaller/tree/master/tools/syz-db) 324 - [8] [KASAN documentation](https://docs.kernel.org/dev-tools/kasan.html) 325 - [9] [UBSAN/KUBSAN documentation](https://docs.kernel.org/dev-tools/ubsan.html) 326 - [10] [KCSAN documentation](https://docs.kernel.org/dev-tools/kcsan.html) 327 - [11] [Microsoft Open Specifications (SMB)](https://learn.microsoft.com/openspecs/) 328 - [12] [Wireshark Sample Captures](https://wiki.wireshark.org/SampleCaptures) 329 - [13] [ksmbd.conf(5) manual page](https://manpages.debian.org/unstable/ksmbd-tools/ksmbd.conf.5.en.html) 330 - [14] [syzkaller pseudo-syscalls documentation](https://github.com/google/syzkaller/blob/master/docs/pseudo_syscalls.md) 331 - [15] [[PATCH] ksmbd: add kcov remote coverage support via ksmbd_conn](https://groups.google.com/g/syzkaller/c/voY55e3c9fI) 332 - [16] [Linux upstream fix: `ksmbd: validate compound request size before reading StructureSize2`](https://github.com/torvalds/linux/commit/15b38176fd1530372905c602fde51fe89ec8c877)