|
| 1 | +# ksmbd Attack Surface & SMB2/SMB3 Protocol Fuzzing (syzkaller) |
| 2 | + |
| 3 | +{{#include ../../banners/hacktricks-training.md}} |
| 4 | + |
| 5 | +## Overview |
| 6 | +This page abstracts practical techniques to exercise and fuzz the Linux in-kernel SMB server (ksmbd) using 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 into weakly-covered code paths, and leveraging syzkaller features such as focus_areas and ANYBLOB. While the original research enumerates specific CVEs, here we emphasise the reusable methodology and concrete snippets you can adapt to your own setups. |
| 7 | + |
| 8 | +Target scope: SMB2/SMB3 over TCP. Kerberos and RDMA are intentionally out-of-scope to keep the harness simple. |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +## Expand ksmbd Attack Surface via Configuration |
| 13 | +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: |
| 14 | + |
| 15 | +- Global-level |
| 16 | + - Durable handles |
| 17 | + - Server multi-channel |
| 18 | + - SMB2 leases |
| 19 | +- Per-share-level |
| 20 | + - Oplocks (on by default) |
| 21 | + - VFS objects |
| 22 | + |
| 23 | +Enabling these increases execution in modules such as: |
| 24 | +- smb2pdu.c (command parsing/dispatch) |
| 25 | +- ndr.c (NDR encode/decode) |
| 26 | +- oplock.c (oplock request/break) |
| 27 | +- smbacl.c (ACL parsing/enforcement) |
| 28 | +- vfs.c (VFS ops) |
| 29 | +- vfs_cache.c (lookup cache) |
| 30 | + |
| 31 | +Notes |
| 32 | +- 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. |
| 33 | +- Multi-channel and durable handles alter state machines and lifetimes, often surfacing UAF/refcount/OOB bugs under concurrency. |
| 34 | + |
| 35 | +--- |
| 36 | + |
| 37 | +## Authentication and Rate-Limiting Adjustments for Fuzzing |
| 38 | +SMB3 needs a valid session. Implementing Kerberos in harnesses adds complexity, so prefer NTLM/guest for fuzzing: |
| 39 | + |
| 40 | +- Allow guest access and set map to guest = bad user so unknown users fall back to GUEST. |
| 41 | +- Accept NTLMv2 (patch policy if disabled). This keeps the handshake simple while exercising SMB3 code paths. |
| 42 | +- 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. |
| 43 | +- Increase max connections (e.g., to 65536) to avoid early rejections during high-throughput fuzzing. |
| 44 | + |
| 45 | +Caution: These relaxations are to facilitate fuzzing only. Do not deploy with these settings in production. |
| 46 | + |
| 47 | +--- |
| 48 | + |
| 49 | +## Stateful Harness: Extract Resources and Chain Requests |
| 50 | +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). |
| 51 | + |
| 52 | +Example snippet to process a response buffer (skipping the +4B NetBIOS PDU length) and cache IDs: |
| 53 | + |
| 54 | +```c |
| 55 | +// process response. does not contain +4B PDU length |
| 56 | +void process_buffer(int msg_no, const char *buffer, size_t received) { |
| 57 | + uint16_t cmd_rsp = u16((const uint8_t *)(buffer + CMD_OFFSET)); |
| 58 | + switch (cmd_rsp) { |
| 59 | + case SMB2_TREE_CONNECT: |
| 60 | + if (received >= TREE_ID_OFFSET + sizeof(uint32_t)) |
| 61 | + tree_id = u32((const uint8_t *)(buffer + TREE_ID_OFFSET)); |
| 62 | + break; |
| 63 | + case SMB2_SESS_SETUP: |
| 64 | + // first session setup response carries session_id |
| 65 | + if (msg_no == 0x01 && received >= SESSION_ID_OFFSET + sizeof(uint64_t)) |
| 66 | + session_id = u64((const uint8_t *)(buffer + SESSION_ID_OFFSET)); |
| 67 | + break; |
| 68 | + case SMB2_CREATE: |
| 69 | + if (received >= CREATE_VFID_OFFSET + sizeof(uint64_t)) { |
| 70 | + persistent_file_id = u64((const uint8_t *)(buffer + CREATE_PFID_OFFSET)); |
| 71 | + volatile_file_id = u64((const uint8_t *)(buffer + CREATE_VFID_OFFSET)); |
| 72 | + } |
| 73 | + break; |
| 74 | + default: |
| 75 | + break; |
| 76 | + } |
| 77 | +} |
| 78 | +``` |
| 79 | +
|
| 80 | +Tips |
| 81 | +- 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. |
| 82 | +- Syzkaller’s experimental reset_acc_state can reset global state but may introduce heavy slowdown. Prefer stability and focus fuzzing instead. |
| 83 | +
|
| 84 | +--- |
| 85 | +
|
| 86 | +## Grammar-Driven SMB2 Generation (Valid PDUs) |
| 87 | +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. |
| 88 | +
|
| 89 | +Example (SMB2 IOCTL request): |
| 90 | +
|
| 91 | +``` |
| 92 | +smb2_ioctl_req { |
| 93 | + Header_Prefix SMB2Header_Prefix |
| 94 | + Command const[0xb, int16] |
| 95 | + Header_Suffix SMB2Header_Suffix |
| 96 | + StructureSize const[57, int16] |
| 97 | + Reserved const[0, int16] |
| 98 | + CtlCode union_control_codes |
| 99 | + PersistentFileId const[0x4, int64] |
| 100 | + VolatileFileId const[0x0, int64] |
| 101 | + InputOffset offsetof[Input, int32] |
| 102 | + InputCount bytesize[Input, int32] |
| 103 | + MaxInputResponse const[65536, int32] |
| 104 | + OutputOffset offsetof[Output, int32] |
| 105 | + OutputCount len[Output, int32] |
| 106 | + MaxOutputResponse const[65536, int32] |
| 107 | + Flags int32[0:1] |
| 108 | + Reserved2 const[0, int32] |
| 109 | + Input array[int8] |
| 110 | + Output array[int8] |
| 111 | +} [packed] |
| 112 | +``` |
| 113 | +
|
| 114 | +This style forces correct structure sizes/offsets and dramatically improves coverage versus blind mutation. |
| 115 | +
|
| 116 | +--- |
| 117 | +
|
| 118 | +## Directed Fuzzing With focus_areas |
| 119 | +Use syzkaller’s experimental focus_areas to overweight specific functions/files that currently have weak coverage. Example JSON: |
| 120 | +
|
| 121 | +```json |
| 122 | +{ |
| 123 | + "focus_areas": [ |
| 124 | + {"filter": {"functions": ["smb_check_perm_dacl"]}, "weight": 20.0}, |
| 125 | + {"filter": {"files": ["^fs/smb/server/"]}, "weight": 2.0}, |
| 126 | + {"weight": 1.0} |
| 127 | + ] |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +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. |
| 132 | + |
| 133 | +Reproducer builder (minimal Python): |
| 134 | + |
| 135 | +```python |
| 136 | +def build_sd(): |
| 137 | + import struct |
| 138 | + sd = bytearray(0x14) |
| 139 | + sd[0x00] = 0x00; sd[0x01] = 0x00 |
| 140 | + struct.pack_into('<H', sd, 0x02, 0x0001) |
| 141 | + struct.pack_into('<I', sd, 0x04, 0x78) |
| 142 | + struct.pack_into('<I', sd, 0x08, 0x00) |
| 143 | + struct.pack_into('<I', sd, 0x0C, 0x10000) |
| 144 | + struct.pack_into('<I', sd, 0x10, 0xFFFFFFFF) # dacloffset |
| 145 | + while len(sd) < 0x78: |
| 146 | + sd += b'A' |
| 147 | + sd += b"\x01\x01\x00\x00\x00\x00\x00\x00" # minimal DACL |
| 148 | + sd += b"\xCC" * 64 |
| 149 | + return bytes(sd) |
| 150 | +``` |
| 151 | + |
| 152 | +--- |
| 153 | + |
| 154 | +## Breaking Coverage Plateaus With ANYBLOB |
| 155 | +syzkaller’s anyTypes (ANYBLOB/ANYRES) allow collapsing complex structures into blobs that mutate generically. Seed a new corpus from public SMB pcaps and convert payloads into syzkaller programs calling your pseudo-syscall (e.g., syz_ksmbd_send_req): |
| 156 | + |
| 157 | +```bash |
| 158 | +# Extract SMB payloads to JSON |
| 159 | +# tshark -r smb2_dac_sample.pcap -Y "smb || smb2" -T json -e tcp.payload > packets.json |
| 160 | +``` |
| 161 | + |
| 162 | +```python |
| 163 | +import json, os |
| 164 | +os.makedirs("corpus", exist_ok=True) |
| 165 | + |
| 166 | +with open("packets.json") as f: |
| 167 | + data = json.load(f) |
| 168 | +# adjust indexing to your tshark JSON structure |
| 169 | +packets = [e["_source"]["layers"]["tcp.payload"] for e in data] |
| 170 | + |
| 171 | +for i, pkt in enumerate(packets): |
| 172 | + pdu = pkt[0] |
| 173 | + pdu_size = len(pdu) // 2 # hex string length → bytes |
| 174 | + with open(f"corpus/packet_{i:03d}.txt", "w") as f: |
| 175 | + f.write( |
| 176 | + f"syz_ksmbd_send_req(&(&(0x7f0000000340))=ANY=[@ANYBLOB=\"{pdu}\"], {hex(pdu_size)}, 0x0, 0x0)" |
| 177 | + ) |
| 178 | +``` |
| 179 | + |
| 180 | +This jump-starts exploration and can immediately trigger UAFs (e.g., in ksmbd_sessions_deregister) while lifting coverage a few percent. |
| 181 | + |
| 182 | +--- |
| 183 | + |
| 184 | +## Sanitizers: Beyond KASAN |
| 185 | +- KASAN remains the primary detector for heap bugs (UAF/OOB). |
| 186 | +- KCSAN often yields false positives or low-severity data races in this target. |
| 187 | +- UBSAN/KUBSAN can catch declared-bounds mistakes that KASAN misses due to array-index semantics. Example: |
| 188 | + |
| 189 | +```c |
| 190 | +id = le32_to_cpu(psid->sub_auth[psid->num_subauth - 1]); |
| 191 | +struct smb_sid { |
| 192 | + __u8 revision; __u8 num_subauth; __u8 authority[NUM_AUTHS]; |
| 193 | + __le32 sub_auth[SID_MAX_SUB_AUTHORITIES]; /* sub_auth[num_subauth] */ |
| 194 | +} __attribute__((packed)); |
| 195 | +``` |
| 196 | + |
| 197 | +Setting num_subauth = 0 triggers an in-struct OOB read of sub_auth[-1], caught by UBSAN’s declared-bounds checks. |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +## Throughput and Parallelism Notes |
| 202 | +- 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. |
| 203 | +- With multiple VMs, you can still hit hundreds of SMB commands/second overall. Function-level coverage around ~60% of fs/smb/server and ~70% of smb2pdu.c is attainable, though state-transition coverage is under-represented by such metrics. |
| 204 | + |
| 205 | +--- |
| 206 | + |
| 207 | +## Practical Checklist |
| 208 | +- Enable durable handles, leases, multi-channel, oplocks, and VFS objects in ksmbd. |
| 209 | +- Allow guest and map-to-guest; accept NTLMv2. Patch out credit limits and raise max connections for fuzzer stability. |
| 210 | +- Build a stateful harness that caches SessionId/TreeID/FileIDs and chains create → ioctl → close. |
| 211 | +- Use a grammar for SMB2 PDUs to maintain structural validity. |
| 212 | +- Use focus_areas to overweight weakly-covered functions (e.g., smbacl.c paths like smb_check_perm_dacl). |
| 213 | +- Seed with ANYBLOB from real pcaps to break plateaus; pack seeds with syz-db for reuse. |
| 214 | +- Run with KASAN + UBSAN; triage UBSAN declared-bounds reports carefully. |
| 215 | + |
| 216 | +--- |
| 217 | + |
| 218 | +## References |
| 219 | +- Doyensec – ksmbd Fuzzing (Part 2): https://blog.doyensec.com/2025/09/02/ksmbd-2.html |
| 220 | +- syzkaller: https://github.com/google/syzkaller |
| 221 | +- ANYBLOB/anyTypes (commit 9fe8aa4): https://github.com/google/syzkaller/commit/9fe8aa4 |
| 222 | +- Async executor change (commit fd8caa5): https://github.com/google/syzkaller/commit/fd8caa5 |
| 223 | +- syz-db: https://github.com/google/syzkaller/tree/master/tools/syz-db |
| 224 | +- KASAN: https://docs.kernel.org/dev-tools/kasan.html |
| 225 | +- UBSAN/KUBSAN: https://docs.kernel.org/dev-tools/ubsan.html |
| 226 | +- KCSAN: https://docs.kernel.org/dev-tools/kcsan.html |
| 227 | +- Microsoft Open Specifications (SMB): https://learn.microsoft.com/openspecs/ |
| 228 | +- Wireshark Sample Captures: https://wiki.wireshark.org/SampleCaptures |
| 229 | +- Background reading: pwning.tech “Tickling ksmbd: fuzzing SMB in the Linux kernel”; Dongliang Mu’s syzkaller notes |
| 230 | + |
| 231 | +{{#include ../../banners/hacktricks-training.md}} |
0 commit comments