etc.git


etc

~ scripts and random things
git clone https://github.com/djnnvx/etc.git
log | files | refs | archive

gen_corpus.py (18931B)

      1 #!/usr/bin/env python3
      2 """
      3 setup-corpus.sh — generate seed corpus for libusb fuzzing harnesses.
      4 
      5 Produces ~90 binary seed files across four subdirectories:
      6   corpus/descriptor/   config/interface/endpoint chain seeds
      7   corpus/bos/          BOS descriptor and device capability seeds
      8   corpus/iad/          IAD two-pass parsing seeds
      9   corpus/usbfs/        Raw device descriptor blob seeds (linux_usbfs path)
     10 """
     11 
     12 import os
     13 import struct
     14 
     15 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
     16 CORPUS_DIR = os.path.join(SCRIPT_DIR, "corpus")
     17 
     18 def write_seed(subdir, name, data):
     19     path = os.path.join(CORPUS_DIR, subdir, name)
     20     os.makedirs(os.path.dirname(path), exist_ok=True)
     21     with open(path, "wb") as f:
     22         f.write(data)
     23     print(f"  {subdir}/{name} ({len(data)} bytes)")
     24 
     25 # ─── CONFIG DESCRIPTOR SEEDS ─────────────────────────────────────────────────
     26 # Format: [bLength][bDescriptorType=0x02][wTotalLength LE16][bNumInterfaces]
     27 #         [bConfigurationValue][iConfiguration][bmAttributes][MaxPower]
     28 #         [... interface + endpoint descriptors ...]
     29 
     30 def config_header(total_len, num_ifaces=1, value=1):
     31     return struct.pack("<BBHBBBBB",
     32         9, 0x02, total_len, num_ifaces, value, 0, 0x80, 50)
     33 
     34 def iface_desc(num_eps=1, iface_num=0, alt=0):
     35     return struct.pack("<BBBBBBBBB",
     36         9, 0x04, iface_num, alt, num_eps, 0xFF, 0x00, 0x00, 0)
     37 
     38 def endpoint_desc(addr=0x81, attrs=0x02, mps=512):
     39     return struct.pack("<BBBBHB",
     40         7, 0x05, addr, attrs, mps, 0)
     41 
     42 print("[*] Generating descriptor/ seeds...")
     43 
     44 # Minimal valid: 9-byte header, zero interfaces, wTotalLength=9
     45 write_seed("descriptor", "config_minimal_valid",
     46     config_header(9, num_ifaces=0))
     47 
     48 # One interface + one endpoint (total = 9+9+7 = 25 bytes)
     49 body = config_header(25, num_ifaces=1) + iface_desc(num_eps=1) + endpoint_desc()
     50 write_seed("descriptor", "config_one_iface_one_ep", body)
     51 
     52 # One interface + max endpoints (32 endpoints = 9+9+32*7 = 242 bytes)
     53 eps = b"".join(endpoint_desc(addr=0x80 | i, attrs=i % 4) for i in range(32))
     54 total = 9 + 9 + len(eps)
     55 body = config_header(total, num_ifaces=1) + iface_desc(num_eps=32) + eps
     56 write_seed("descriptor", "config_max_endpoints", body)
     57 
     58 # wTotalLength > actual data (triggers short-read path)
     59 write_seed("descriptor", "config_wtotallength_mismatch",
     60     config_header(0xFFFF, num_ifaces=1) + iface_desc(1) + endpoint_desc())
     61 
     62 # wTotalLength = 0
     63 write_seed("descriptor", "config_wtotallength_zero",
     64     struct.pack("<BBHBBBBB", 9, 0x02, 0, 0, 1, 0, 0x80, 50))
     65 
     66 # wTotalLength = 1 (below minimum)
     67 write_seed("descriptor", "config_wtotallength_one",
     68     struct.pack("<BBHBBBBB", 9, 0x02, 1, 0, 1, 0, 0x80, 50))
     69 
     70 # bNumInterfaces = 32 (USB_MAXINTERFACES limit)
     71 ifaces = b"".join(iface_desc(0, i) for i in range(32))
     72 total = 9 + len(ifaces)
     73 write_seed("descriptor", "config_bNumInterfaces_32",
     74     config_header(total, num_ifaces=32) + ifaces)
     75 
     76 # bNumInterfaces = 255 (over-max, exercises the > USB_MAXINTERFACES check)
     77 write_seed("descriptor", "config_bNumInterfaces_255",
     78     struct.pack("<BBHBBBBB", 9, 0x02, 9, 255, 1, 0, 0x80, 50))
     79 
     80 # bLength = 1 (below LIBUSB_DT_CONFIG_SIZE=9)
     81 write_seed("descriptor", "config_bLength_1",
     82     struct.pack("<BB", 1, 0x02) + b"\x00" * 16)
     83 
     84 # bLength = 2 (just a header)
     85 write_seed("descriptor", "config_bLength_2",
     86     struct.pack("<BB", 2, 0x02) + b"\x00" * 16)
     87 
     88 # Truncated at various offsets
     89 for n in [0, 1, 4, 8]:
     90     write_seed("descriptor", f"config_truncated_{n}", b"\x09\x02" + b"\x20\x00" + b"\x00" * n)
     91 
     92 # All 0xFF bytes
     93 write_seed("descriptor", "config_all_ff", b"\xff" * 64)
     94 
     95 # All 0x00 bytes (hits multiple early-return paths)
     96 write_seed("descriptor", "config_all_zeros", b"\x00" * 32)
     97 
     98 # Class-specific descriptor before interface (bDescriptorType=0x24)
     99 class_desc = struct.pack("<BBB", 4, 0x24, 0x01)  # 4-byte class desc
    100 total = 9 + len(class_desc) + 9 + 7
    101 body = config_header(total) + class_desc + iface_desc(1) + endpoint_desc()
    102 write_seed("descriptor", "config_class_desc_before_iface", body)
    103 
    104 # Two interfaces, two alt settings each
    105 alts = iface_desc(1, 0, 0) + iface_desc(1, 0, 1) + iface_desc(1, 1, 0) + iface_desc(1, 1, 1)
    106 eps2 = endpoint_desc() * 4
    107 total = 9 + len(alts) + len(eps2)
    108 write_seed("descriptor", "config_two_ifaces_two_alts",
    109     config_header(total, num_ifaces=2) + alts + eps2)
    110 
    111 # Audio endpoint (bLength=9 for synch fields)
    112 audio_ep = struct.pack("<BBBBHBBB", 9, 0x05, 0x81, 0x05, 256, 1, 0, 0)
    113 total = 9 + 9 + len(audio_ep)
    114 write_seed("descriptor", "config_audio_endpoint",
    115     config_header(total) + iface_desc(1) + audio_ep)
    116 
    117 # ─── BOS DESCRIPTOR SEEDS ────────────────────────────────────────────────────
    118 # Format: [bLength=5][bDescriptorType=0x0F][wTotalLength LE16][bNumDeviceCaps]
    119 #         [capability descriptors...]
    120 
    121 def bos_header(total_len, num_caps):
    122     return struct.pack("<BBHB", 5, 0x0F, total_len, num_caps)
    123 
    124 def cap_usb20_ext():
    125     # bLength=7, bDescriptorType=0x10, bDevCapabilityType=0x02, bmAttributes=LE32
    126     return struct.pack("<BBB I", 7, 0x10, 0x02, 0x00000002)
    127 
    128 def cap_ss():
    129     # bLength=10, type=0x10, captype=0x03, bmAttr, wSpeedsSupported, bFuncSupport, bU1, bU2
    130     return struct.pack("<BBB B H B B B", 10, 0x10, 0x03, 0, 0x000E, 1, 10, 40)
    131 
    132 def cap_container_id():
    133     # bLength=20, type=0x10, captype=0x04, bReserved=0, ContainerID[16]
    134     return struct.pack("<BBB B", 20, 0x10, 0x04, 0) + b"\xDE\xAD\xBE\xEF" * 4
    135 
    136 def cap_ssplus(num_sublinks):
    137     # bLength = 12 + 4*num_sublinks
    138     # bmAttr lower nibble = (num_sublinks - 1) for sublinks count
    139     blen = 12 + 4 * num_sublinks
    140     bmattr = struct.pack("<I", (num_sublinks - 1) & 0xF)
    141     sublinks = struct.pack("<I", 0x000A4000) * num_sublinks  # lane speed attr
    142     return struct.pack("<BBB", blen, 0x10, 0x0A) + bmattr + struct.pack("<HBB", 0, 0, 0) + sublinks
    143 
    144 def cap_platform(cap_data_len):
    145     # bLength = 3 + 1 + 16 + cap_data_len = 20 + cap_data_len
    146     blen = 20 + cap_data_len
    147     uuid = b"\x38\xB6\x08\xBF\xD0\xBB\x3D\x4B\x87\xC0\x52\x0E\x3B\x8F\xE8\xB7"  # WinUSB UUID
    148     return struct.pack("<BBB B", blen, 0x10, 0x05, 0) + uuid + b"\x00" * cap_data_len
    149 
    150 print("[*] Generating bos/ seeds...")
    151 
    152 # Minimal BOS: 5-byte header, no capabilities
    153 write_seed("bos", "bos_minimal", bos_header(5, 0))
    154 
    155 # One USB 2.0 extension capability
    156 c = cap_usb20_ext()
    157 write_seed("bos", "bos_one_usb20_ext", bos_header(5 + len(c), 1) + c)
    158 
    159 # One SuperSpeed capability
    160 c = cap_ss()
    161 write_seed("bos", "bos_one_ss_cap", bos_header(5 + len(c), 1) + c)
    162 
    163 # One Container ID capability
    164 c = cap_container_id()
    165 write_seed("bos", "bos_one_container_id", bos_header(5 + len(c), 1) + c)
    166 
    167 # SSPlus with 1 sublink (minimum, triggers sublink loop)
    168 c = cap_ssplus(1)
    169 write_seed("bos", "bos_ssplus_1_sublink", bos_header(5 + len(c), 1) + c)
    170 
    171 # SSPlus with 15 sublinks (high sublink count, stresses OOB path at line 1115)
    172 c = cap_ssplus(15)
    173 write_seed("bos", "bos_ssplus_15_sublinks", bos_header(5 + len(c), 1) + c)
    174 
    175 # Platform with 0 cap_data bytes (bLength=20, minimum, edge case for underflow)
    176 c = cap_platform(0)
    177 write_seed("bos", "bos_platform_cap_data_0", bos_header(5 + len(c), 1) + c)
    178 
    179 # Platform with 4 cap_data bytes (bLength=24, normal)
    180 c = cap_platform(4)
    181 write_seed("bos", "bos_platform_cap_data_4", bos_header(5 + len(c), 1) + c)
    182 
    183 # Platform with bLength manually set to 19 (below MIN=20, triggers check)
    184 raw = bytearray(cap_platform(0))
    185 raw[0] = 19
    186 write_seed("bos", "bos_platform_blen_19", bos_header(5 + 19, 1) + bytes(raw[:19]))
    187 
    188 # Platform with bLength=18 (well below minimum)
    189 raw[0] = 18
    190 write_seed("bos", "bos_platform_blen_18", bos_header(5 + 18, 1) + bytes(raw[:18]))
    191 
    192 # All 5 capability types in one BOS
    193 caps = cap_usb20_ext() + cap_ss() + cap_container_id() + cap_ssplus(2) + cap_platform(2)
    194 write_seed("bos", "bos_all_five_caps", bos_header(5 + len(caps), 5) + caps)
    195 
    196 # bNumDeviceCaps=255 with truncated data (tests allocation vs actual caps)
    197 write_seed("bos", "bos_bNumCaps_255_truncated",
    198     bos_header(5 + 7, 255) + cap_usb20_ext())
    199 
    200 # wTotalLength=0xFFFF
    201 write_seed("bos", "bos_wtotal_max",
    202     struct.pack("<BBHB", 5, 0x0F, 0xFFFF, 1) + cap_usb20_ext())
    203 
    204 # All zeros
    205 write_seed("bos", "bos_all_zeros", b"\x00" * 16)
    206 
    207 # Truncated BOS (4 bytes — below LIBUSB_DT_BOS_SIZE=5)
    208 write_seed("bos", "bos_truncated_4", b"\x05\x0f\x05\x00")
    209 
    210 # Unknown capability type (bDevCapabilityType=0xFF)
    211 c = struct.pack("<BBB I", 7, 0x10, 0xFF, 0xDEADBEEF)
    212 write_seed("bos", "bos_unknown_cap_type", bos_header(5 + len(c), 1) + c)
    213 
    214 # Capability with bLength=2 (below 3-byte minimum)
    215 c = struct.pack("<BB", 2, 0x10) + b"\xFF" * 5
    216 write_seed("bos", "bos_cap_blen_2", bos_header(5 + 2, 1) + c[:2])
    217 
    218 # SSPlus bmAttributes sublink count = 0 (no sublink speed attrs)
    219 c = struct.pack("<BBB I H B B", 12, 0x10, 0x0A, 0, 0, 0, 0)
    220 write_seed("bos", "bos_ssplus_0_sublinks", bos_header(5 + len(c), 1) + c)
    221 
    222 # ─── IAD SEEDS ───────────────────────────────────────────────────────────────
    223 # IAD embedded within a config descriptor
    224 # [config header 9B][IAD 8B each][interface descriptors 9B each]
    225 
    226 def iad_desc(first_iface=0, count=2, func_class=0xEF):
    227     return struct.pack("<BBBBBBBB",
    228         8, 0x0B, first_iface, count, func_class, 0x02, 0x01, 0)
    229 
    230 print("[*] Generating iad/ seeds...")
    231 
    232 # No IADs (just a valid config)
    233 body = config_header(9, 0)
    234 write_seed("iad", "iad_none", body)
    235 
    236 # One IAD + two interfaces
    237 iad = iad_desc(0, 2)
    238 ifaces = iface_desc(1, 0) + iface_desc(1, 1)
    239 total = 9 + len(iad) + len(ifaces) + 7 * 2
    240 body = config_header(total, 2) + iad + ifaces + endpoint_desc() + endpoint_desc(0x02)
    241 write_seed("iad", "iad_one", body)
    242 
    243 # Two IADs with non-overlapping interface ranges
    244 iad1 = iad_desc(0, 2)
    245 iad2 = iad_desc(2, 2)
    246 ifaces = b"".join(iface_desc(0, i) for i in range(4))
    247 total = 9 + len(iad1) + len(iad2) + len(ifaces)
    248 write_seed("iad", "iad_two_sequential",
    249     config_header(total, 4) + iad1 + iad2 + ifaces)
    250 
    251 # Two IADs with overlapping interface ranges
    252 iad_overlap1 = iad_desc(0, 3)
    253 iad_overlap2 = iad_desc(1, 3)
    254 ifaces = b"".join(iface_desc(0, i) for i in range(4))
    255 total = 9 + len(iad_overlap1) + len(iad_overlap2) + len(ifaces)
    256 write_seed("iad", "iad_two_overlapping",
    257     config_header(total, 4) + iad_overlap1 + iad_overlap2 + ifaces)
    258 
    259 # bInterfaceCount = 0
    260 iad_zero = struct.pack("<BBBBBBBB", 8, 0x0B, 0, 0, 0xEF, 0x02, 0x01, 0)
    261 write_seed("iad", "iad_bInterfaceCount_0",
    262     config_header(9 + 8, 0) + iad_zero)
    263 
    264 # bInterfaceCount = 255
    265 iad_max = struct.pack("<BBBBBBBB", 8, 0x0B, 0, 255, 0xEF, 0x02, 0x01, 0)
    266 write_seed("iad", "iad_bInterfaceCount_255",
    267     config_header(9 + 8, 0) + iad_max)
    268 
    269 # IAD bLength = 2 (minimum header — too short for IAD but passes header check)
    270 iad_short = struct.pack("<BB", 2, 0x0B) + b"\xFF" * 6
    271 write_seed("iad", "iad_bLength_2",
    272     config_header(9 + 2, 0) + iad_short[:2])
    273 
    274 # IAD bLength = 7 (one below standard 8)
    275 iad_7 = struct.pack("<BBBBBBB", 7, 0x0B, 0, 2, 0xEF, 0x02, 0x01)
    276 write_seed("iad", "iad_bLength_7",
    277     config_header(9 + 7, 0) + iad_7)
    278 
    279 # Many IADs (tests uint8_t count field — 32 IADs)
    280 iads_32 = b"".join(iad_desc(i * 2, 2) for i in range(32))
    281 total = 9 + len(iads_32)
    282 write_seed("iad", "iad_32_entries",
    283     config_header(total, 0) + iads_32)
    284 
    285 # IAD after interface (not at start of config data)
    286 iface_first = iface_desc(0, 0)
    287 iad_after = iad_desc(1, 1)
    288 iface_second = iface_desc(0, 1)
    289 total = 9 + len(iface_first) + len(iad_after) + len(iface_second)
    290 write_seed("iad", "iad_after_interface",
    291     config_header(total, 2) + iface_first + iad_after + iface_second)
    292 
    293 # All zeros (hits early-return paths in both passes)
    294 write_seed("iad", "iad_all_zeros", b"\x00" * 32)
    295 
    296 # ─── USBFS SEEDS ─────────────────────────────────────────────────────────────
    297 # Format: [18-byte device descriptor][config descriptor(s)]
    298 # The device descriptor's bNumConfigurations controls how many configs are expected.
    299 
    300 def device_desc(num_configs=1, vendor=0x0483, product=0x5740):
    301     return struct.pack("<BBHBBBBHHHBBBB",
    302         18, 0x01,   # bLength, bDescriptorType
    303         0x0200,     # bcdUSB (USB 2.0)
    304         0x00,       # bDeviceClass
    305         0x00,       # bDeviceSubClass
    306         0x00,       # bDeviceProtocol
    307         64,         # bMaxPacketSize0
    308         vendor,     # idVendor
    309         product,    # idProduct
    310         0x0100,     # bcdDevice
    311         1, 2, 3,    # iManufacturer, iProduct, iSerialNumber
    312         num_configs)
    313 
    314 print("[*] Generating usbfs/ seeds...")
    315 
    316 # Valid 1-config device
    317 dev = device_desc(1)
    318 cfg = config_header(25) + iface_desc(1) + endpoint_desc()
    319 write_seed("usbfs", "usbfs_valid_1config", dev + cfg)
    320 
    321 # Valid 2-config device
    322 dev = device_desc(2)
    323 cfg1 = config_header(18, 1, value=1) + iface_desc(0)
    324 cfg2 = config_header(18, 1, value=2) + iface_desc(0)
    325 write_seed("usbfs", "usbfs_valid_2configs", dev + cfg1 + cfg2)
    326 
    327 # bNumConfigurations = 0 (should return immediately)
    328 write_seed("usbfs", "usbfs_bNumConfigs_0", device_desc(0))
    329 
    330 # bNumConfigurations = 255 with no config data (only device desc)
    331 write_seed("usbfs", "usbfs_bNumConfigs_255_no_data", device_desc(255))
    332 
    333 # bNumConfigurations = 255 with one small config (loop runs until remaining exhausted)
    334 write_seed("usbfs", "usbfs_bNumConfigs_255_one_config",
    335     device_desc(255) + config_header(9, 0))
    336 
    337 # wTotalLength > remaining (truncation path)
    338 dev = device_desc(1)
    339 cfg = struct.pack("<BBHBBBBB", 9, 0x02, 0xFFFF, 0, 1, 0, 0x80, 50)
    340 write_seed("usbfs", "usbfs_wtotallength_overflow", dev + cfg)
    341 
    342 # Device descriptor only (no config data)
    343 write_seed("usbfs", "usbfs_device_desc_only", device_desc(1))
    344 
    345 # All zeros (18+ bytes — hits bDescriptorType != LIBUSB_DT_CONFIG check)
    346 write_seed("usbfs", "usbfs_all_zeros", b"\x00" * 32)
    347 
    348 # Short descriptor (17 bytes — below LIBUSB_DT_DEVICE_SIZE=18, harness skips)
    349 write_seed("usbfs", "usbfs_short_17", b"\x12\x01" + b"\x00" * 15)
    350 
    351 # Device descriptor + config with bLength < LIBUSB_DT_CONFIG_SIZE (bad config)
    352 dev = device_desc(1)
    353 bad_cfg = struct.pack("<BB", 1, 0x02) + b"\x00" * 20
    354 write_seed("usbfs", "usbfs_config_blen_too_short", dev + bad_cfg)
    355 
    356 # Normal device + config where bDescriptorType != 0x02 (not a config desc)
    357 dev = device_desc(1)
    358 not_cfg = struct.pack("<BBHBBBBB", 9, 0x04, 9, 0, 1, 0, 0x80, 50)  # type=interface
    359 write_seed("usbfs", "usbfs_wrong_config_type", dev + not_cfg)
    360 
    361 # wTotalLength = 0 (below minimum)
    362 dev = device_desc(1)
    363 zero_len = struct.pack("<BBHBBBBB", 9, 0x02, 0, 0, 1, 0, 0x80, 50)
    364 write_seed("usbfs", "usbfs_wtotallength_0", dev + zero_len)
    365 
    366 # ─── EXTRA / ENDPOINT COMPANION SEEDS ───────────────────────────────────────
    367 # Seeds for fuzz_extra.c: config + endpoint with SS companion extra data.
    368 # LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30, size = 6 bytes.
    369 
    370 def ss_companion(blen=6, bulk_max_burst=15, bmAttr=0, wBytesPerInterval=0):
    371     return struct.pack("<BBBBH", blen, 0x30, bulk_max_burst, bmAttr, wBytesPerInterval)
    372 
    373 print("[*] Generating extra/ seeds...")
    374 
    375 # Valid endpoint + SS companion (6 bytes)
    376 comp = ss_companion(6)
    377 ep = endpoint_desc(0x81, 0x02, 512)  # bulk-in
    378 total = 9 + 9 + len(ep) + len(comp)
    379 body = config_header(total) + iface_desc(1) + ep + comp
    380 write_seed("extra", "extra_ep_with_companion", body)
    381 
    382 # Endpoint + companion with bLength=5 (below LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE=6)
    383 comp_short = ss_companion(5)
    384 total = 9 + 9 + len(ep) + 5
    385 body = config_header(total) + iface_desc(1) + ep + comp_short[:5]
    386 write_seed("extra", "extra_companion_blen_5", body)
    387 
    388 # Endpoint + companion with bLength=2 (minimum header only)
    389 total = 9 + 9 + len(ep) + 2
    390 body = config_header(total) + iface_desc(1) + ep + b"\x02\x30"
    391 write_seed("extra", "extra_companion_blen_2", body)
    392 
    393 # Endpoint + companion with bLength=0 (zero — hits bLength < 2 guard)
    394 total = 9 + 9 + len(ep) + 2
    395 body = config_header(total) + iface_desc(1) + ep + b"\x00\x30"
    396 write_seed("extra", "extra_companion_blen_0", body)
    397 
    398 # Multiple endpoints each with a companion
    399 eps_with_comp = b""
    400 for i in range(4):
    401     eps_with_comp += endpoint_desc(0x80 | (i + 1), 0x02, 512) + ss_companion(6, i, 0, 0)
    402 total = 9 + 9 + len(eps_with_comp)
    403 body = config_header(total) + iface_desc(4) + eps_with_comp
    404 write_seed("extra", "extra_four_eps_with_companions", body)
    405 
    406 # Endpoint with class-specific extra data (not a companion) before the companion
    407 class_extra = struct.pack("<BBB", 3, 0x24, 0x01)
    408 comp = ss_companion(6)
    409 total = 9 + 9 + len(ep) + len(class_extra) + len(comp)
    410 body = config_header(total) + iface_desc(1) + ep + class_extra + comp
    411 write_seed("extra", "extra_class_desc_then_companion", body)
    412 
    413 # No endpoints (nothing to walk)
    414 write_seed("extra", "extra_no_endpoints",
    415     config_header(9, 0))
    416 
    417 # Many class-specific descriptors between interfaces (seeds config->extra_length int accumulation)
    418 # 30 interfaces, each preceded by several 3-byte class-specific descriptors
    419 class_blobs = struct.pack("<BBB", 3, 0x24, 0xFF) * 30
    420 ifaces_many = b""
    421 for i in range(30):
    422     ifaces_many += class_blobs + iface_desc(0, i)
    423 total = 9 + len(ifaces_many)
    424 body = config_header(total, 30) + ifaces_many
    425 write_seed("extra", "extra_class_desc_accumulation", body)
    426 
    427 # ─── ADDITIONAL DESCRIPTOR SEEDS ─────────────────────────────────────────────
    428 # Extra seeds to stress config->extra_length signed int accumulation.
    429 
    430 print("[*] Generating additional descriptor/ seeds...")
    431 
    432 # Config with many interfaces each with large class-specific blobs (bLength=0xFF)
    433 # Accumulates config->extra_length toward INT_MAX with enough mutations
    434 big_class = struct.pack("<BB", 0xFF, 0x24) + b"\x42" * 253  # 255-byte class desc
    435 ifaces_with_big_class = b""
    436 for i in range(8):
    437     ifaces_with_big_class += big_class + iface_desc(0, i)
    438 total = 9 + len(ifaces_with_big_class)
    439 body = config_header(total, 8) + ifaces_with_big_class
    440 write_seed("descriptor", "config_large_class_descs", body)
    441 
    442 # Config where every byte after the header is a class-specific descriptor type
    443 class_chain = b""
    444 for _ in range(50):
    445     class_chain += struct.pack("<BBB", 3, 0x25, 0x00)  # 3-byte class desc, type 0x25
    446 total = 9 + len(class_chain) + 9  # one interface at the end
    447 body = config_header(total, 1) + class_chain + iface_desc(0)
    448 write_seed("descriptor", "config_class_desc_chain", body)
    449 
    450 print(f"\n[+] Corpus generated in {CORPUS_DIR}/")
    451 for d in ["descriptor", "bos", "iad", "usbfs", "extra"]:
    452     path = os.path.join(CORPUS_DIR, d)
    453     if os.path.isdir(path):
    454         n = len(os.listdir(path))
    455         print(f"    {d}/: {n} seeds")