etc~ scripts and random things |
| git clone https://github.com/djnnvx/etc.git |
| log | files | refs | archive |
gen_corpus.py (24312B)
1 #!/usr/bin/env python3 2 """ 3 gen_corpus.py — generate structured USB/IP protocol seed corpus. 4 5 Produces 80+ binary seeds in ./corpus/, covering: 6 1. op_common_valid — well-formed protocol headers 7 2. op_common_malformed — bad version / unknown op code 8 3. devlist_replies — device-list reply bodies (ndev variations) 9 4. import_requests — busid field edge cases 10 5. urb_submit — CVE-2016-3955 class (length field variations) 11 6. urb_unlink — unlink command edge cases 12 7. raw_boundary — size boundary seeds (empty, 1 byte, max) 13 """ 14 15 import os 16 import struct 17 import itertools 18 import hashlib 19 20 OUT = os.path.join(os.path.dirname(__file__), "corpus") 21 os.makedirs(OUT, exist_ok=True) 22 23 written = 0 24 25 def seed(name: str, data: bytes): 26 global written 27 safe = name.replace("/", "_").replace("\x00", "") 28 path = os.path.join(OUT, safe) 29 with open(path, "wb") as f: 30 f.write(data) 31 written += 1 32 33 34 # ── constants ───────────────────────────────────────────────────────────── 35 36 USBIP_VERSION = 0x0111 37 OP_REQ_DEVLIST = 0x8005 38 OP_REP_DEVLIST = 0x0005 39 OP_REQ_IMPORT = 0x8003 40 OP_REP_IMPORT = 0x0003 41 42 CMD_SUBMIT = 0x00000001 43 CMD_UNLINK = 0x00000002 44 RET_SUBMIT = 0x00000003 45 RET_UNLINK = 0x00000004 46 47 STATUS_OK = 0x00000000 48 STATUS_ERROR = 0x00000001 49 50 SYSFS_BUS_ID = 32 # bytes 51 52 def op_common(code: int, status: int = STATUS_OK, version: int = USBIP_VERSION) -> bytes: 53 return struct.pack(">HHI", version, code, status) 54 55 def usbip_usb_device() -> bytes: 56 """Minimal valid usbip_usb_device struct (312 bytes, all fields safe).""" 57 path = b"/sys/devices/pci0000:00/0000:00:01.2/usb1/1-1\x00" 58 path += b"\x00" * (256 - len(path)) 59 busid = b"1-1\x00" + b"\x00" * (SYSFS_BUS_ID - 4) 60 busnum = struct.pack(">I", 1) 61 devnum = struct.pack(">I", 1) 62 speed = struct.pack(">I", 3) # USB_SPEED_HIGH 63 idvendor = struct.pack(">H", 0x1234) 64 idproduct = struct.pack(">H", 0x5678) 65 bcddevice = struct.pack(">H", 0x0100) 66 devclass = struct.pack(">B", 0) 67 subclass = struct.pack(">B", 0) 68 protocol = struct.pack(">B", 0) 69 cfg = struct.pack(">B", 1) 70 bNumConfs = struct.pack(">B", 1) 71 bNumIfaces = struct.pack(">B", 1) 72 return (path + busid + busnum + devnum + speed + 73 idvendor + idproduct + bcddevice + 74 devclass + subclass + protocol + 75 cfg + bNumConfs + bNumIfaces) 76 77 def usbip_usb_interface() -> bytes: 78 """Minimal usbip_usb_interface struct.""" 79 return struct.pack(">BBBB", 0, 0, 0, 0) 80 81 82 # ══════════════════════════════════════════════════════════════════════════════ 83 # 1. op_common valid 84 # ══════════════════════════════════════════════════════════════════════════════ 85 86 seed("op_common_req_devlist", op_common(OP_REQ_DEVLIST)) 87 seed("op_common_rep_devlist", op_common(OP_REP_DEVLIST)) 88 seed("op_common_req_import", op_common(OP_REQ_IMPORT)) 89 seed("op_common_rep_import", op_common(OP_REP_IMPORT)) 90 seed("op_common_status_error", op_common(OP_REP_DEVLIST, STATUS_ERROR)) 91 92 # With payload after the header (exercises remainder of the stream) 93 seed("op_common_devlist_ndev0", 94 op_common(OP_REP_DEVLIST) + struct.pack(">I", 0)) 95 seed("op_common_devlist_ndev1", 96 op_common(OP_REP_DEVLIST) + struct.pack(">I", 1) + usbip_usb_device() + usbip_usb_interface()) 97 seed("op_common_import_with_dev", 98 op_common(OP_REP_IMPORT) + struct.pack(">I", STATUS_OK) + usbip_usb_device()) 99 100 101 # ══════════════════════════════════════════════════════════════════════════════ 102 # 2. op_common malformed 103 # ══════════════════════════════════════════════════════════════════════════════ 104 105 seed("op_bad_version_0000", struct.pack(">HHHI", 0x0000, OP_REQ_DEVLIST, STATUS_OK, 0)) 106 seed("op_bad_version_ffff", struct.pack(">HHHI", 0xFFFF, OP_REQ_DEVLIST, STATUS_OK, 0)) 107 seed("op_bad_version_0100", struct.pack(">HHHI", 0x0100, OP_REQ_DEVLIST, STATUS_OK, 0)) 108 seed("op_bad_version_0200", struct.pack(">HHHI", 0x0200, OP_REQ_DEVLIST, STATUS_OK, 0)) 109 seed("op_unknown_code_0001", op_common(0x0001)) 110 seed("op_unknown_code_0000", op_common(0x0000)) 111 seed("op_unknown_code_ffff", op_common(0xFFFF)) 112 seed("op_unknown_code_7fff", op_common(0x7FFF)) 113 seed("op_unknown_code_8000", op_common(0x8000)) 114 seed("op_status_max", op_common(OP_REQ_DEVLIST, 0xFFFFFFFF)) 115 seed("op_status_neg1", struct.pack(">HHi", USBIP_VERSION, OP_REQ_DEVLIST, -1)) 116 seed("op_all_zeros", b"\x00" * 8) 117 seed("op_all_ff", b"\xff" * 8) 118 seed("op_just_version", struct.pack(">H", USBIP_VERSION)) # truncated 119 seed("op_7_bytes", op_common(OP_REQ_DEVLIST)[:7]) # one byte short 120 121 122 # ══════════════════════════════════════════════════════════════════════════════ 123 # 3. devlist reply bodies 124 # ══════════════════════════════════════════════════════════════════════════════ 125 126 for ndev in [0, 1, 2, 4, 8, 16, 32, 64, 255, 256, 65535, 0xFFFFFFFF]: 127 name = f"devlist_ndev_{ndev}" 128 body = struct.pack(">I", ndev) 129 if ndev <= 2: 130 for _ in range(min(ndev, 2)): 131 body += usbip_usb_device() + usbip_usb_interface() 132 seed(name, body) 133 134 # Device with multiple interfaces 135 multi_iface = usbip_usb_device() 136 # Patch bNumInterfaces to 3 (last byte of the struct) 137 multi_iface = multi_iface[:-1] + b"\x03" 138 multi_body = struct.pack(">I", 1) + multi_iface 139 for _ in range(3): 140 multi_body += usbip_usb_interface() 141 seed("devlist_multi_iface", multi_body) 142 143 # Truncated device struct 144 seed("devlist_truncated_dev", struct.pack(">I", 1) + usbip_usb_device()[:64]) 145 146 # Device struct with all-ff fields 147 seed("devlist_dev_all_ff", struct.pack(">I", 1) + b"\xff" * len(usbip_usb_device())) 148 149 150 # ══════════════════════════════════════════════════════════════════════════════ 151 # 4. import requests / replies (busid edge cases) 152 # ══════════════════════════════════════════════════════════════════════════════ 153 154 def import_input(busid: bytes, extra: bytes = b"") -> bytes: 155 """Pack busid to exactly 32 bytes (no forced NUL — let fuzzer explore).""" 156 bid = busid[:SYSFS_BUS_ID] 157 bid = bid + b"\x00" * (SYSFS_BUS_ID - len(bid)) 158 return bid + extra 159 160 seed("import_valid_busid", import_input(b"1-1")) 161 seed("import_valid_busid_2_1", import_input(b"2-1")) 162 seed("import_no_null", import_input(b"A" * SYSFS_BUS_ID)) 163 seed("import_path_traversal", import_input(b"../../../etc/passwd")) 164 seed("import_path_traversal2", import_input(b"../../proc/self/mem")) 165 seed("import_format_string", import_input(b"%s%s%s%s%n%n")) 166 seed("import_null_bytes", import_input(b"\x00\x00\x00\x00")) 167 seed("import_unicode_like", import_input(b"\xc0\xaf\xc0\xaf\xc0\xaf")) 168 seed("import_long_busid", import_input(b"1" * SYSFS_BUS_ID)) 169 seed("import_busid_with_reply", import_input(b"1-1") + struct.pack(">I", STATUS_OK) + usbip_usb_device()) 170 seed("import_reply_status_err", import_input(b"1-1") + struct.pack(">I", STATUS_ERROR)) 171 seed("import_reply_truncated", import_input(b"1-1") + struct.pack(">I", STATUS_OK) + usbip_usb_device()[:32]) 172 seed("import_dev_all_ff", import_input(b"1-1") + struct.pack(">I", STATUS_OK) + b"\xff" * len(usbip_usb_device())) 173 seed("import_empty_busid", import_input(b"")) 174 seed("import_slash_busid", import_input(b"///" * 10)) 175 seed("import_colon_busid", import_input(b"0:0")) 176 seed("import_large_devnum", import_input(b"255-255")) 177 seed("import_symbolic_link", import_input(b"1-1 -> /etc/passwd\x00")) 178 179 180 # ══════════════════════════════════════════════════════════════════════════════ 181 # 5. URB submit (CVE-2016-3955 class) 182 # ══════════════════════════════════════════════════════════════════════════════ 183 184 def urb_header(command, seqnum=1, devid=0x10001, direction=0, ep=0, 185 transfer_flags=0, transfer_buffer_length=0, start_frame=0, 186 number_of_packets=0, interval=0, setup=b"\x00"*8) -> bytes: 187 base = struct.pack(">IIIII", command, seqnum, devid, direction, ep) 188 submit = struct.pack(">IiiiI", transfer_flags, transfer_buffer_length, 189 start_frame, number_of_packets, interval) 190 return base + submit + setup[:8] 191 192 # Normal submit 193 seed("urb_submit_empty", urb_header(CMD_SUBMIT, transfer_buffer_length=0)) 194 seed("urb_submit_64b", urb_header(CMD_SUBMIT, transfer_buffer_length=64) + b"\xaa"*64) 195 seed("urb_submit_512b", urb_header(CMD_SUBMIT, transfer_buffer_length=512) + b"\xbb"*512) 196 197 # Length mismatches — CVE-2016-3955 pattern 198 seed("urb_ret_actual_gt_alloc", urb_header(RET_SUBMIT, transfer_buffer_length=64) + b"\xcc"*512) 199 seed("urb_ret_len_neg1", urb_header(RET_SUBMIT, transfer_buffer_length=-1)) 200 seed("urb_ret_len_intmin", urb_header(RET_SUBMIT, transfer_buffer_length=-2147483648)) 201 seed("urb_ret_len_max", urb_header(RET_SUBMIT, transfer_buffer_length=65536)) 202 seed("urb_ret_len_overflow", urb_header(RET_SUBMIT, transfer_buffer_length=0x7FFFFFFF)) 203 204 # number_of_packets edge cases (ISO transfers) 205 # stub_rx.c: kmalloc(number_of_packets * sizeof(struct usbip_iso_packet_descriptor)) 206 # iso_descriptor is 16 bytes; overflow threshold: n > 0x0fffffff 207 seed("urb_iso_packets_0", urb_header(CMD_SUBMIT, number_of_packets=0)) 208 seed("urb_iso_packets_max", urb_header(CMD_SUBMIT, number_of_packets=0x7FFFFFFF)) 209 seed("urb_iso_packets_neg", urb_header(CMD_SUBMIT, number_of_packets=-1)) 210 seed("urb_iso_wrap_u32", urb_header(CMD_SUBMIT, number_of_packets=0x10000000)) # n*16 == 0 211 seed("urb_iso_wrap_u32p1", urb_header(CMD_SUBMIT, number_of_packets=0x10000001)) # n*16 == 16 212 seed("urb_iso_wrap_s32", urb_header(CMD_SUBMIT, number_of_packets=0x08000000)) # n*16 wraps s32 213 214 # transfer_buffer_length near overflow boundaries 215 seed("urb_tbl_uint_max", urb_header(CMD_SUBMIT, transfer_buffer_length=0x7FFFFFFF)) 216 seed("urb_tbl_wrap_s32", urb_header(CMD_SUBMIT, transfer_buffer_length=-2147483648)) 217 218 # Invalid endpoint / direction 219 seed("urb_ep_15", urb_header(CMD_SUBMIT, ep=15)) 220 seed("urb_ep_overflow", urb_header(CMD_SUBMIT, ep=0xFFFFFFFF)) 221 seed("urb_dir_in", urb_header(CMD_SUBMIT, direction=1)) 222 seed("urb_dir_bad", urb_header(CMD_SUBMIT, direction=0xFFFFFFFF)) 223 224 # setup packet edge cases (control transfers) 225 seed("urb_setup_all_ff", urb_header(CMD_SUBMIT, setup=b"\xff"*8)) 226 seed("urb_setup_req_clear", urb_header(CMD_SUBMIT, setup=bytes([0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00]))) 227 seed("urb_setup_get_descriptor",urb_header(CMD_SUBMIT, setup=bytes([0x80,0x06,0x00,0x01,0x00,0x00,0xFF,0x00]))) 228 229 230 # ══════════════════════════════════════════════════════════════════════════════ 231 # 6. URB unlink 232 # ══════════════════════════════════════════════════════════════════════════════ 233 234 def urb_unlink(seqnum=1, devid=0x10001, direction=0, ep=0, unlink_seqnum=0) -> bytes: 235 base = struct.pack(">IIIII", CMD_UNLINK, seqnum, devid, direction, ep) 236 payload = struct.pack(">I", unlink_seqnum) + b"\x00" * 24 # padding to match size 237 return base + payload 238 239 seed("urb_unlink_seq0", urb_unlink(unlink_seqnum=0)) 240 seed("urb_unlink_seq1", urb_unlink(seqnum=1, unlink_seqnum=1)) 241 seed("urb_unlink_seq_max", urb_unlink(unlink_seqnum=0xFFFFFFFF)) 242 seed("urb_unlink_seq_neg", struct.pack(">IiIII", CMD_UNLINK, -1, 0, 0, 0) + b"\x00"*24) 243 seed("urb_ret_unlink_ok", urb_header(RET_UNLINK)) 244 seed("urb_all_zeros", b"\x00" * 48) 245 seed("urb_all_ff", b"\xff" * 48) 246 seed("urb_truncated", b"\x00" * 20) # just basic header, no cmd_submit 247 248 249 # ══════════════════════════════════════════════════════════════════════════════ 250 # 7. vhci-hcd server-side seeds (for fuzz_vhci_server — two-sided fuzzer) 251 # 252 # Each seed is the 28-byte ret_submit union that fuzz_vhci_server overlays onto 253 # USBIP_RET_SUBMIT responses. Fields: status(4) actual_length(4) start_frame(4) 254 # number_of_packets(4) error_count(4) + 8 bytes padding. 255 # ══════════════════════════════════════════════════════════════════════════════ 256 257 def ret_submit_fields(status=0, actual_length=0, start_frame=0, 258 number_of_packets=0, error_count=0) -> bytes: 259 return struct.pack(">iiiii", status, actual_length, start_frame, 260 number_of_packets, error_count) + b"\x00" * 8 261 262 INT_MIN = -0x80000000 263 INT_MAX = 0x7fffffff 264 265 # Normal / status variants 266 seed("vhci_ret_ok", ret_submit_fields(status=0, actual_length=64)) 267 seed("vhci_ret_status_error", ret_submit_fields(status=-22)) # -EINVAL 268 seed("vhci_ret_status_epipe", ret_submit_fields(status=-32)) # -EPIPE 269 seed("vhci_ret_status_enodev", ret_submit_fields(status=-19)) # -ENODEV 270 271 # CVE-2016-3955 class: actual_length > transfer_buffer_length 272 # fuzz_vhci_server will send this as the server's response to any CMD_SUBMIT 273 seed("vhci_actual_len_overflow", ret_submit_fields(actual_length=INT_MAX)) 274 seed("vhci_actual_len_intmin", ret_submit_fields(actual_length=INT_MIN)) 275 seed("vhci_actual_len_neg1", ret_submit_fields(actual_length=-1)) 276 seed("vhci_actual_len_64k", ret_submit_fields(actual_length=65536)) 277 seed("vhci_actual_len_64k_plus1", ret_submit_fields(actual_length=65537)) 278 seed("vhci_actual_len_zero", ret_submit_fields(actual_length=0)) 279 280 # ISO path: number_of_packets > 0 triggers usbip_recv_iso code path 281 seed("vhci_iso_one_pkt", ret_submit_fields(actual_length=16, number_of_packets=1)) 282 seed("vhci_iso_many_pkts", ret_submit_fields(actual_length=0, number_of_packets=255)) 283 seed("vhci_iso_max_pkts", ret_submit_fields(number_of_packets=INT_MAX)) 284 seed("vhci_iso_neg_pkts", ret_submit_fields(number_of_packets=-1)) 285 seed("vhci_iso_overflow", ret_submit_fields(actual_length=INT_MAX, number_of_packets=INT_MAX)) 286 287 # error_count / start_frame edge cases 288 seed("vhci_start_frame_intmin", ret_submit_fields(start_frame=INT_MIN)) 289 seed("vhci_error_count_max", ret_submit_fields(error_count=INT_MAX)) 290 seed("vhci_all_ff", b"\xff" * 28) 291 seed("vhci_all_zeros", b"\x00" * 28) 292 293 # Integer overflow in kmalloc size calculation: 294 # usbip_recv_iso: kmalloc(number_of_packets * sizeof(struct usbip_iso_packet_descriptor)) 295 # sizeof(usbip_iso_packet_descriptor) == 16 bytes on kernel side 296 # Overflow threshold: n * 16 wraps u32 when n > 0x0fffffff (268435455) 297 # These values cause the allocated buffer to be smaller than the data being received. 298 ISO_DESC_SIZE = 16 299 WRAP_U32 = (1 << 32) // ISO_DESC_SIZE # 0x10000000 — exactly wraps to 0 300 WRAP_U32_PLUS1 = WRAP_U32 + 1 # 0x10000001 — wraps to 16 bytes allocated 301 WRAP_S32 = (1 << 31) // ISO_DESC_SIZE # 0x08000000 — wraps signed to negative 302 # WRAP_U32=0x10000000: n*16 == 0x100000000 which wraps to 0 in u32 → kmalloc(0) 303 # WRAP_U32_PLUS1: n*16 == 0x100000010 which wraps to 16 → 16-byte alloc but n*16 bytes used 304 seed("vhci_iso_wrap_u32", ret_submit_fields(number_of_packets=WRAP_U32, actual_length=64)) 305 seed("vhci_iso_wrap_u32p1", ret_submit_fields(number_of_packets=WRAP_U32_PLUS1, actual_length=64)) 306 seed("vhci_iso_wrap_s32", ret_submit_fields(number_of_packets=WRAP_S32, actual_length=64)) 307 308 # Compound: both actual_length overflow AND iso multiplication overflow 309 seed("vhci_double_overflow", ret_submit_fields(actual_length=INT_MAX, number_of_packets=WRAP_U32_PLUS1)) 310 311 # actual_length near UINT_MAX (unsigned interpretation — some code paths treat it unsigned) 312 seed("vhci_actual_uint_max", struct.pack(">iIiii", 0, 0xFFFFFFFF, 0, 0, 0) + b"\x00" * 8) 313 314 # Borderline: actual_length == transfer_buffer_length (exactly equal — off-by-one test) 315 # fuzz_vhci_server always sends the response regardless of the original request's tbl, 316 # but these seed values help AFL++ discover the boundary condition faster. 317 for tbl in [0, 1, 63, 64, 127, 128, 255, 256, 4095, 4096]: 318 seed(f"vhci_actual_eq_tbl_{tbl}", ret_submit_fields(actual_length=tbl)) 319 320 # ── ISO overflow: targeted values matching the three unpatched kernel bugs ────── 321 # 322 # usbip_recv_iso() bug: int size = np * sizeof(*iso) where sizeof(*iso)==16 323 # np=0x08000001 → 0x08000001*16 = 0x80000010 → overflows int32 → -2147483632 324 # np=0x10000001 → 0x10000001*16 = 0x100000010 → truncated uint32 = 0x10 = 16 325 # np=0x7FFFFFFF/16+1 = 0x08000000 → edge case at INT_MAX/16 326 # 327 # With fuzz_vhci_server's ISO descriptor mirroring, the server sends exactly 328 # the overflowed number of bytes and the kernel's loop reads OOB → KASAN hit. 329 # 330 # Seeds named with explicit overflow semantics for clarity in AFL++ crash reports. 331 seed("vhci_iso_np_overflow_08", 332 ret_submit_fields(actual_length=64, number_of_packets=0x08000001)) 333 seed("vhci_iso_np_overflow_10", 334 ret_submit_fields(actual_length=64, number_of_packets=0x10000001)) 335 seed("vhci_iso_np_overflow_7f", 336 ret_submit_fields(actual_length=64, number_of_packets=0x7FFFFFFF // 16 + 1)) 337 338 # usbip_pad_iso() bug: actualoffset underflow → memmove with negative offset 339 # Trigger: ISO packet with actual_length > accumulated offset in the packet array. 340 # We send actual_length=0 per ISO packet but a large total actual_length header, 341 # forcing pad_iso to compute a negative offset for memmove(). 342 # Represent as: number_of_packets=8 (valid), actual_length=INT_MAX (large total) 343 seed("vhci_iso_pad_underflow", 344 ret_submit_fields(actual_length=INT_MAX, number_of_packets=8)) 345 346 # Per-packet ISO descriptor corruption seeds → usbip_pad_iso() memmove underflow. 347 # 348 # struct usbip_iso_packet_descriptor layout (16 bytes each): 349 # offset(4BE) actual_length(4BE) status(4BE) padding(4BE) 350 # 351 # When the per-packet actual_length inside the ISO descriptor array is corrupted 352 # to a huge value, usbip_pad_iso() computes a negative actualoffset for its 353 # memmove(), causing a kernel heap underwrite. These seeds exercise that path 354 # directly — prior seeds only corrupted the header-level actual_length field. 355 for _np in [1, 2, 4, 8]: 356 for _bad in [0x7FFFFFFF, 0x80000000, 0xFFFFFFFF]: 357 _iso_desc = struct.pack(">II", 0, _bad) + b"\x00" * 8 # 16 bytes 358 seed(f"vhci_iso_pkt_corrupt_np{_np}_{hex(_bad)}", 359 ret_submit_fields(actual_length=0, number_of_packets=_np) + _iso_desc * _np) 360 361 # Stub-side sgl_alloc() boundary seeds (for fuzz_stub_client / corpus/stub/). 362 # 363 # stub_recv_cmd_submit() calls sgl_alloc(transfer_buffer_length) with no upper 364 # bound check. Values at and above SG_MAX_SINGLE_ALLOC (128 pages = 512 KB on 365 # x86) exercise the fallback scatter-gather allocation path. IN transfers 366 # (direction=1) trigger the alloc without needing the client to send payload. 367 for _tbl in [0x80000, 0x1000000, 0x40000000, 0x7FFFFFFF, -1]: 368 seed(f"stub_tbl_in_{hex(_tbl & 0xFFFFFFFF)}", 369 urb_header(CMD_SUBMIT, direction=1, transfer_buffer_length=_tbl)) 370 371 # OUT direction: stub also allocates for the receive path, so exercise that too. 372 for _tbl in [0x80000, 0x1000000]: 373 seed(f"stub_tbl_out_{hex(_tbl)}", 374 urb_header(CMD_SUBMIT, direction=0, transfer_buffer_length=_tbl)) 375 376 377 # ══════════════════════════════════════════════════════════════════════════════ 378 # 8. Raw boundary seeds 379 # ══════════════════════════════════════════════════════════════════════════════ 380 381 seed("raw_empty", b"") 382 seed("raw_1byte", b"\x01") 383 seed("raw_7bytes", b"\x01\x11\x80\x05\x00\x00\x00") 384 seed("raw_8bytes_op_common", op_common(OP_REQ_DEVLIST)) 385 seed("raw_max_size", b"\x01\x11\x80\x05\x00\x00\x00\x00" + b"\xde\xad\xbe\xef" * (65536 // 4)) 386 387 388 # ══════════════════════════════════════════════════════════════════════════════ 389 # Per-harness subdirs 390 # 391 # run-fuzzers.sh points each AFL++ instance at a subdirectory that contains 392 # only the seeds meaningful for that harness. This keeps calibration fast and 393 # map density high from the start. Cross-pollination between instances still 394 # happens through the shared output/ directory. 395 # ══════════════════════════════════════════════════════════════════════════════ 396 397 import shutil 398 399 SUBDIRS = { 400 "protocol": lambda n, _: n.startswith("op_common") or n.startswith("raw_8bytes"), 401 "devlist": lambda n, _: "devlist" in n, 402 "import": lambda n, _: "import" in n, 403 "urb": lambda n, _: "urb" in n or "cmd_" in n or "ret_" in n, 404 "vhci": lambda n, _: n.startswith("vhci_"), 405 # stub seeds: CMD_SUBMIT headers for fuzz_stub_client 406 # Uses URB submit seeds as the base (same 48-byte CMD_SUBMIT format) 407 "stub": lambda n, _: "urb_submit" in n or "urb_iso" in n or "urb_tbl" in n 408 or "urb_ep" in n or "urb_dir" in n or "urb_setup" in n 409 or "urb_all" in n or "urb_ret_len" in n 410 or n.startswith("stub_tbl_"), 411 } 412 413 for subdir, predicate in SUBDIRS.items(): 414 dest = os.path.join(OUT, subdir) 415 os.makedirs(dest, exist_ok=True) 416 count = 0 417 for fname in os.listdir(OUT): 418 src = os.path.join(OUT, fname) 419 if os.path.isfile(src) and predicate(fname, src): 420 shutil.copy2(src, os.path.join(dest, fname)) 421 count += 1 422 print(f"[+] corpus/{subdir}/: {count} seeds") 423 424 print(f"[+] Generated {written} seeds in {OUT}/")