diff --git a/fuzzing/libusb/cleanup.sh b/fuzzing/libusb/cleanup.sh
@@ -30,6 +30,7 @@ pkill -u "$(id -un)" -f "fuzz_descriptor" 2>/dev/null || true
pkill -u "$(id -un)" -f "fuzz_bos" 2>/dev/null || true
pkill -u "$(id -un)" -f "fuzz_iad" 2>/dev/null || true
pkill -u "$(id -un)" -f "fuzz_usbfs" 2>/dev/null || true
+pkill -u "$(id -un)" -f "fuzz_extra" 2>/dev/null || true
[[ "${CRASHES_ONLY}" -eq 1 ]] && { ok "Processes killed."; exit 0; }
@@ -38,6 +39,7 @@ if [[ "${KEEP_BUILDS}" -eq 0 ]]; then
rm -f fuzz_bos fuzz_bos.cmplog
rm -f fuzz_iad fuzz_iad.cmplog
rm -f fuzz_usbfs fuzz_usbfs.cmplog
+ rm -f fuzz_extra fuzz_extra.cmplog
ok "Binaries removed."
fi
diff --git a/fuzzing/libusb/fuzz_extra.c b/fuzzing/libusb/fuzz_extra.c
@@ -0,0 +1,58 @@
+/*
+ * Harness: endpoint extra chain walking via libusb_get_ss_endpoint_companion_descriptor().
+ *
+ * fuzz_descriptor.c parses configs and immediately frees them — it never calls
+ * the public API that walks endpoint->extra. This harness does both: parse the
+ * config, then call the SS endpoint companion extractor on every endpoint's
+ * extra data. That walking loop is completely unexercised by any other harness.
+ */
+
+#include "fuzz-include/libusb_fuzz.h"
+#include "libusb-src/libusb/descriptor.c"
+
+static int fuzz_one(const uint8_t *data, size_t size)
+{
+ if (size < 1)
+ return 0;
+
+ struct libusb_config_descriptor *config = NULL;
+ int r = raw_desc_to_config(NULL, data,
+ (int)(size > (size_t)INT_MAX ? INT_MAX : size), &config);
+ if (r != LIBUSB_SUCCESS || !config)
+ return 0;
+
+ for (uint8_t i = 0; i < config->bNumInterfaces; i++) {
+ const struct libusb_interface *iface = &config->interface[i];
+ for (int a = 0; a < iface->num_altsetting; a++) {
+ const struct libusb_interface_descriptor *alt = &iface->altsetting[a];
+ for (uint8_t e = 0; e < alt->bNumEndpoints; e++) {
+ const struct libusb_endpoint_descriptor *ep = &alt->endpoint[e];
+
+ struct libusb_ss_endpoint_companion_descriptor *comp = NULL;
+ libusb_get_ss_endpoint_companion_descriptor(NULL, ep, &comp);
+ if (comp)
+ libusb_free_ss_endpoint_companion_descriptor(comp);
+ }
+ }
+ }
+
+ libusb_free_config_descriptor(config);
+ return 0;
+}
+
+#ifdef __AFL_FUZZ_TESTCASE_LEN
+__AFL_FUZZ_INIT();
+int main(void)
+{
+ __AFL_INIT();
+ uint8_t *buf = __AFL_FUZZ_TESTCASE_BUF;
+ while (__AFL_LOOP(10000))
+ fuzz_one(buf, __AFL_FUZZ_TESTCASE_LEN);
+ return 0;
+}
+#else
+int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+{
+ return fuzz_one(data, size);
+}
+#endif
diff --git a/fuzzing/libusb/gen_corpus.py b/fuzzing/libusb/gen_corpus.py
@@ -363,7 +363,93 @@ dev = device_desc(1)
zero_len = struct.pack("<BBHBBBBB", 9, 0x02, 0, 0, 1, 0, 0x80, 50)
write_seed("usbfs", "usbfs_wtotallength_0", dev + zero_len)
+# ─── EXTRA / ENDPOINT COMPANION SEEDS ───────────────────────────────────────
+# Seeds for fuzz_extra.c: config + endpoint with SS companion extra data.
+# LIBUSB_DT_SS_ENDPOINT_COMPANION = 0x30, size = 6 bytes.
+
+def ss_companion(blen=6, bulk_max_burst=15, bmAttr=0, wBytesPerInterval=0):
+ return struct.pack("<BBBBH", blen, 0x30, bulk_max_burst, bmAttr, wBytesPerInterval)
+
+print("[*] Generating extra/ seeds...")
+
+# Valid endpoint + SS companion (6 bytes)
+comp = ss_companion(6)
+ep = endpoint_desc(0x81, 0x02, 512) # bulk-in
+total = 9 + 9 + len(ep) + len(comp)
+body = config_header(total) + iface_desc(1) + ep + comp
+write_seed("extra", "extra_ep_with_companion", body)
+
+# Endpoint + companion with bLength=5 (below LIBUSB_DT_SS_ENDPOINT_COMPANION_SIZE=6)
+comp_short = ss_companion(5)
+total = 9 + 9 + len(ep) + 5
+body = config_header(total) + iface_desc(1) + ep + comp_short[:5]
+write_seed("extra", "extra_companion_blen_5", body)
+
+# Endpoint + companion with bLength=2 (minimum header only)
+total = 9 + 9 + len(ep) + 2
+body = config_header(total) + iface_desc(1) + ep + b"\x02\x30"
+write_seed("extra", "extra_companion_blen_2", body)
+
+# Endpoint + companion with bLength=0 (zero — hits bLength < 2 guard)
+total = 9 + 9 + len(ep) + 2
+body = config_header(total) + iface_desc(1) + ep + b"\x00\x30"
+write_seed("extra", "extra_companion_blen_0", body)
+
+# Multiple endpoints each with a companion
+eps_with_comp = b""
+for i in range(4):
+ eps_with_comp += endpoint_desc(0x80 | (i + 1), 0x02, 512) + ss_companion(6, i, 0, 0)
+total = 9 + 9 + len(eps_with_comp)
+body = config_header(total) + iface_desc(4) + eps_with_comp
+write_seed("extra", "extra_four_eps_with_companions", body)
+
+# Endpoint with class-specific extra data (not a companion) before the companion
+class_extra = struct.pack("<BBB", 3, 0x24, 0x01)
+comp = ss_companion(6)
+total = 9 + 9 + len(ep) + len(class_extra) + len(comp)
+body = config_header(total) + iface_desc(1) + ep + class_extra + comp
+write_seed("extra", "extra_class_desc_then_companion", body)
+
+# No endpoints (nothing to walk)
+write_seed("extra", "extra_no_endpoints",
+ config_header(9, 0))
+
+# Many class-specific descriptors between interfaces (seeds config->extra_length int accumulation)
+# 30 interfaces, each preceded by several 3-byte class-specific descriptors
+class_blobs = struct.pack("<BBB", 3, 0x24, 0xFF) * 30
+ifaces_many = b""
+for i in range(30):
+ ifaces_many += class_blobs + iface_desc(0, i)
+total = 9 + len(ifaces_many)
+body = config_header(total, 30) + ifaces_many
+write_seed("extra", "extra_class_desc_accumulation", body)
+
+# ─── ADDITIONAL DESCRIPTOR SEEDS ─────────────────────────────────────────────
+# Extra seeds to stress config->extra_length signed int accumulation.
+
+print("[*] Generating additional descriptor/ seeds...")
+
+# Config with many interfaces each with large class-specific blobs (bLength=0xFF)
+# Accumulates config->extra_length toward INT_MAX with enough mutations
+big_class = struct.pack("<BB", 0xFF, 0x24) + b"\x42" * 253 # 255-byte class desc
+ifaces_with_big_class = b""
+for i in range(8):
+ ifaces_with_big_class += big_class + iface_desc(0, i)
+total = 9 + len(ifaces_with_big_class)
+body = config_header(total, 8) + ifaces_with_big_class
+write_seed("descriptor", "config_large_class_descs", body)
+
+# Config where every byte after the header is a class-specific descriptor type
+class_chain = b""
+for _ in range(50):
+ class_chain += struct.pack("<BBB", 3, 0x25, 0x00) # 3-byte class desc, type 0x25
+total = 9 + len(class_chain) + 9 # one interface at the end
+body = config_header(total, 1) + class_chain + iface_desc(0)
+write_seed("descriptor", "config_class_desc_chain", body)
+
print(f"\n[+] Corpus generated in {CORPUS_DIR}/")
-for d in ["descriptor", "bos", "iad", "usbfs"]:
- n = len(os.listdir(os.path.join(CORPUS_DIR, d)))
- print(f" {d}/: {n} seeds")
+for d in ["descriptor", "bos", "iad", "usbfs", "extra"]:
+ path = os.path.join(CORPUS_DIR, d)
+ if os.path.isdir(path):
+ n = len(os.listdir(path))
+ print(f" {d}/: {n} seeds")
diff --git a/fuzzing/libusb/run-all.sh b/fuzzing/libusb/run-all.sh
@@ -30,7 +30,7 @@ case "${1:-}" in
esac
command -v afl-fuzz > /dev/null 2>&1 || die "afl-fuzz not found."
-for h in fuzz_descriptor fuzz_bos fuzz_iad fuzz_usbfs; do
+for h in fuzz_descriptor fuzz_bos fuzz_iad fuzz_usbfs fuzz_extra; do
[[ -f "${h}" ]] || die "${h} not found. Run setup.sh first."
[[ -f "${h}.cmplog" ]] || die "${h}.cmplog not found."
done
@@ -44,14 +44,18 @@ mkdir -p "${OUTPUT}"
export AFL_SKIP_CPUFREQ=1
export AFL_AUTORESUME=1
export AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES=1
-export ASAN_OPTIONS="abort_on_error=1:symbolize=0:detect_leaks=0:allocator_may_return_null=1"
+export AFL_DISABLE_TRIM=1
+export AFL_KEEP_TIMEOUTS=1
+export ASAN_OPTIONS="abort_on_error=1:symbolize=0:detect_leaks=0:allocator_may_return_null=1:detect_stack_use_after_scope=1"
export UBSAN_OPTIONS="print_stacktrace=0:halt_on_error=1"
+# power_schedule: master=explore (breadth-first), secondaries vary per harness
+# -l 2AT on cmplog instances: type-2 transform + auto token insertion
afl_cmd() {
- local role="$1" flag="$2" target="$3" corpus_sub="$4"
+ local role="$1" flag="$2" target="$3" corpus_sub="$4" sched="${5:-fast}"
local cmplog=""
- [[ "${flag}" == "master" ]] && cmplog="-c ./${target}.cmplog"
- echo "afl-fuzz ${role} -i '${CORPUS}/${corpus_sub}' -o '${OUTPUT}' -x '${DICT}' -t 500 -p fast -m none ${cmplog} -- './${target}'"
+ [[ "${flag}" == "master" || "${flag}" == "cmplog" ]] && cmplog="-c ./${target}.cmplog -l 2AT"
+ echo "afl-fuzz ${role} -i '${CORPUS}/${corpus_sub}' -o '${OUTPUT}' -x '${DICT}' -t 500 -p ${sched} -m none ${cmplog} -- './${target}'"
}
log "Starting session '${SESSION}'..."
@@ -68,14 +72,14 @@ sleep 0.5
send() { tmux send-keys -t "${SESSION}:${1}.${2}" "${3}" Enter; }
-send 0 0 "$(afl_cmd '-M master_descriptor' master fuzz_descriptor descriptor)"
-send 0 1 "$(afl_cmd '-S sec_descriptor' sec fuzz_descriptor descriptor)"
-send 0 2 "$(afl_cmd '-M master_bos' master fuzz_bos bos)"
-send 0 3 "$(afl_cmd '-S sec_bos' sec fuzz_bos bos)"
-send 1 0 "$(afl_cmd '-M master_iad' master fuzz_iad iad)"
-send 1 1 "$(afl_cmd '-S sec_iad' sec fuzz_iad iad)"
-send 1 2 "$(afl_cmd '-M master_usbfs' master fuzz_usbfs usbfs)"
-send 1 3 "$(afl_cmd '-S sec_usbfs' sec fuzz_usbfs usbfs)"
+send 0 0 "$(afl_cmd '-M master_descriptor' master fuzz_descriptor descriptor explore)"
+send 0 1 "$(afl_cmd '-S sec_descriptor' sec fuzz_descriptor descriptor rare)"
+send 0 2 "$(afl_cmd '-M master_bos' master fuzz_bos bos explore)"
+send 0 3 "$(afl_cmd '-S sec_bos' cmplog fuzz_bos bos coe)"
+send 1 0 "$(afl_cmd '-M master_iad' master fuzz_iad iad explore)"
+send 1 1 "$(afl_cmd '-S sec_iad' sec fuzz_iad iad lin)"
+send 1 2 "$(afl_cmd '-M master_usbfs' master fuzz_usbfs usbfs explore)"
+send 1 3 "$(afl_cmd '-S sec_extra' sec fuzz_extra extra fast)"
send 2 0 "watch -n 15 'afl-whatsup ${OUTPUT} 2>/dev/null || echo waiting...'"
ok "Session started. Attach: tmux attach -t ${SESSION}"
diff --git a/fuzzing/libusb/setup.sh b/fuzzing/libusb/setup.sh
@@ -63,21 +63,22 @@ fi
log "Building harnesses..."
CC="afl-clang-fast"
SRC="libusb-src/libusb"
-CFLAGS="-fsanitize=address,undefined -g -O1 -fno-omit-frame-pointer"
+CFLAGS="-fsanitize=address,undefined,signed-integer-overflow -g -O0 -fno-omit-frame-pointer"
CFLAGS+=" -I fuzz-include -I ${SRC} -DFUZZ_BUILD"
CFLAGS+=" -Wno-implicit-function-declaration -Wno-incompatible-pointer-types -Wno-unused-function -Wno-unused-variable"
build() {
local name="$1" extra_src="${2:-}" extra_flags="${3:-}"
log " ${name}..."
- ${CC} ${CFLAGS} ${extra_flags} "${name}.c" fuzz_stubs.c ${extra_src} -o "${name}" -lpthread
- AFL_LLVM_CMPLOG=1 ${CC} ${CFLAGS} ${extra_flags} "${name}.c" fuzz_stubs.c ${extra_src} -o "${name}.cmplog" -lpthread
+ AFL_LLVM_LAF_ALL=1 AFL_LLVM_INSTRUMENT=CLASSIC ${CC} ${CFLAGS} ${extra_flags} "${name}.c" fuzz_stubs.c ${extra_src} -o "${name}" -lpthread
+ AFL_LLVM_LAF_ALL=1 AFL_LLVM_INSTRUMENT=CLASSIC AFL_LLVM_CMPLOG=1 ${CC} ${CFLAGS} ${extra_flags} "${name}.c" fuzz_stubs.c ${extra_src} -o "${name}.cmplog" -lpthread
}
build fuzz_descriptor
build fuzz_bos
build fuzz_iad
build fuzz_usbfs "fuzz_usbfs_shim.c" "-I ${SRC}/os"
+build fuzz_extra
ok "Harnesses built."
# corpus
| | | | |