daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

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)