feat(fuzz): add corpus seed generator with seeds for all targets
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b0adff2a9b
commit
58f1e8b471
22 changed files with 309 additions and 0 deletions
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-devlist-only
Normal file
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-devlist-only
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-devlist-then-import
Normal file
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-devlist-then-import
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-import-enumerate
Normal file
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-import-enumerate
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-import-hid-full
Normal file
BIN
lib/fuzz/corpus/fuzz_handle_client/seed-import-hid-full
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_parse_command/seed-devlist
Normal file
BIN
lib/fuzz/corpus/fuzz_parse_command/seed-devlist
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_parse_command/seed-import
Normal file
BIN
lib/fuzz/corpus/fuzz_parse_command/seed-import
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-bulk-in
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-bulk-in
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-bulk-out
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-bulk-out
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-class-requests
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-class-requests
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-interrupt-in
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-cdc-interrupt-in
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-enumerate
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_cdc/seed-enumerate
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-enumerate
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-enumerate
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-get-status
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-get-status
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-hid-class-requests
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-hid-class-requests
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-hid-interrupt-in
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-hid-interrupt-in
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-unlink
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_hid/seed-unlink
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-enumerate
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-enumerate
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-iso-in
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-iso-in
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-iso-out
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-iso-out
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-iso-out-multi
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-iso-out-multi
Normal file
Binary file not shown.
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-set-interface
Normal file
BIN
lib/fuzz/corpus/fuzz_urb_uac/seed-uac-set-interface
Normal file
Binary file not shown.
309
lib/fuzz/gen_corpus.rs
Normal file
309
lib/fuzz/gen_corpus.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
use std::fs;
|
||||
|
||||
// USB/IP protocol constants
|
||||
const USBIP_VERSION: u16 = 0x0111;
|
||||
const OP_REQ_DEVLIST: u16 = 0x8005;
|
||||
const OP_REQ_IMPORT: u16 = 0x8003;
|
||||
const USBIP_CMD_SUBMIT: u16 = 0x0001;
|
||||
const USBIP_CMD_UNLINK: u16 = 0x0002;
|
||||
|
||||
// Direction
|
||||
const DIR_OUT: u32 = 0;
|
||||
const DIR_IN: u32 = 1;
|
||||
|
||||
/// Write a seed file to corpus/<target>/seed-<name>
|
||||
fn write_seed(target: &str, name: &str, data: &[u8]) {
|
||||
let dir = format!("corpus/{target}");
|
||||
fs::create_dir_all(&dir).unwrap();
|
||||
let path = format!("{dir}/seed-{name}");
|
||||
fs::write(&path, data).unwrap();
|
||||
println!(" {path} ({} bytes)", data.len());
|
||||
}
|
||||
|
||||
/// OP_REQ_DEVLIST: version(2) + command(2) + status(4) = 8 bytes
|
||||
fn op_req_devlist() -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&USBIP_VERSION.to_be_bytes());
|
||||
buf.extend_from_slice(&OP_REQ_DEVLIST.to_be_bytes());
|
||||
buf.extend_from_slice(&0u32.to_be_bytes());
|
||||
buf
|
||||
}
|
||||
|
||||
/// OP_REQ_IMPORT: version(2) + command(2) + status(4) + busid(32) = 40 bytes
|
||||
fn op_req_import(busid: &str) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&USBIP_VERSION.to_be_bytes());
|
||||
buf.extend_from_slice(&OP_REQ_IMPORT.to_be_bytes());
|
||||
buf.extend_from_slice(&0u32.to_be_bytes());
|
||||
let mut busid_bytes = [0u8; 32];
|
||||
let len = busid.len().min(32);
|
||||
busid_bytes[..len].copy_from_slice(&busid.as_bytes()[..len]);
|
||||
buf.extend_from_slice(&busid_bytes);
|
||||
buf
|
||||
}
|
||||
|
||||
/// USBIP_CMD_SUBMIT: 48-byte header + data (OUT only) + iso descriptors
|
||||
fn cmd_submit(
|
||||
seqnum: u32,
|
||||
direction: u32,
|
||||
ep: u32,
|
||||
transfer_buffer_length: u32,
|
||||
number_of_packets: u32,
|
||||
interval: u32,
|
||||
setup: [u8; 8],
|
||||
data: &[u8],
|
||||
iso_descriptors: &[u8],
|
||||
) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&0u16.to_be_bytes()); // version (0 for URB phase)
|
||||
buf.extend_from_slice(&USBIP_CMD_SUBMIT.to_be_bytes()); // command
|
||||
buf.extend_from_slice(&seqnum.to_be_bytes()); // seqnum
|
||||
buf.extend_from_slice(&2u32.to_be_bytes()); // devid
|
||||
buf.extend_from_slice(&direction.to_be_bytes()); // direction
|
||||
buf.extend_from_slice(&ep.to_be_bytes()); // ep
|
||||
buf.extend_from_slice(&0u32.to_be_bytes()); // transfer_flags
|
||||
buf.extend_from_slice(&transfer_buffer_length.to_be_bytes()); // transfer_buffer_length
|
||||
buf.extend_from_slice(&0u32.to_be_bytes()); // start_frame
|
||||
buf.extend_from_slice(&number_of_packets.to_be_bytes()); // number_of_packets
|
||||
buf.extend_from_slice(&interval.to_be_bytes()); // interval
|
||||
buf.extend_from_slice(&setup); // setup (8 bytes)
|
||||
buf.extend_from_slice(data);
|
||||
buf.extend_from_slice(iso_descriptors);
|
||||
buf
|
||||
}
|
||||
|
||||
/// USBIP_CMD_UNLINK: 48 bytes total
|
||||
fn cmd_unlink(seqnum: u32, unlink_seqnum: u32) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&0u16.to_be_bytes());
|
||||
buf.extend_from_slice(&USBIP_CMD_UNLINK.to_be_bytes());
|
||||
buf.extend_from_slice(&seqnum.to_be_bytes());
|
||||
buf.extend_from_slice(&2u32.to_be_bytes()); // devid
|
||||
buf.extend_from_slice(&0u32.to_be_bytes()); // direction
|
||||
buf.extend_from_slice(&0u32.to_be_bytes()); // ep
|
||||
buf.extend_from_slice(&unlink_seqnum.to_be_bytes());
|
||||
buf.extend_from_slice(&[0u8; 24]); // padding
|
||||
buf
|
||||
}
|
||||
|
||||
/// ISO packet descriptor: offset(4) + length(4) + actual_length(4) + status(4) = 16 bytes
|
||||
fn iso_desc(offset: u32, length: u32) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend_from_slice(&offset.to_be_bytes());
|
||||
buf.extend_from_slice(&length.to_be_bytes());
|
||||
buf.extend_from_slice(&0u32.to_be_bytes()); // actual_length (client sets to 0)
|
||||
buf.extend_from_slice(&0u32.to_be_bytes()); // status (client sets to 0)
|
||||
buf
|
||||
}
|
||||
|
||||
/// Control IN setup: GET_DESCRIPTOR
|
||||
fn setup_get_desc(desc_type: u8, desc_index: u8, length: u16) -> [u8; 8] {
|
||||
[
|
||||
0x80, 0x06,
|
||||
desc_index, desc_type,
|
||||
0x00, 0x00,
|
||||
(length & 0xFF) as u8, (length >> 8) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
/// Control IN setup: GET_DESCRIPTOR with interface recipient
|
||||
fn setup_get_desc_iface(desc_type: u8, desc_index: u8, iface: u16, length: u16) -> [u8; 8] {
|
||||
[
|
||||
0x81, 0x06,
|
||||
desc_index, desc_type,
|
||||
(iface & 0xFF) as u8, (iface >> 8) as u8,
|
||||
(length & 0xFF) as u8, (length >> 8) as u8,
|
||||
]
|
||||
}
|
||||
|
||||
/// Control OUT setup: SET_CONFIGURATION
|
||||
fn setup_set_config(config: u16) -> [u8; 8] {
|
||||
[
|
||||
0x00, 0x09,
|
||||
(config & 0xFF) as u8, (config >> 8) as u8,
|
||||
0x00, 0x00,
|
||||
0x00, 0x00,
|
||||
]
|
||||
}
|
||||
|
||||
/// Control OUT setup: SET_INTERFACE (alternate setting)
|
||||
fn setup_set_interface(iface: u16, alt: u16) -> [u8; 8] {
|
||||
[
|
||||
0x01, 0x0B,
|
||||
(alt & 0xFF) as u8, (alt >> 8) as u8,
|
||||
(iface & 0xFF) as u8, (iface >> 8) as u8,
|
||||
0x00, 0x00,
|
||||
]
|
||||
}
|
||||
|
||||
/// Build a standard USB enumeration sequence (5 CMD_SUBMITs, seqnums 1-5)
|
||||
fn enumeration_sequence() -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
buf.extend(cmd_submit(1, DIR_IN, 0, 64, 0, 0, setup_get_desc(1, 0, 64), &[], &[]));
|
||||
buf.extend(cmd_submit(2, DIR_IN, 0, 18, 0, 0, setup_get_desc(1, 0, 18), &[], &[]));
|
||||
buf.extend(cmd_submit(3, DIR_IN, 0, 9, 0, 0, setup_get_desc(2, 0, 9), &[], &[]));
|
||||
buf.extend(cmd_submit(4, DIR_IN, 0, 255, 0, 0, setup_get_desc(2, 0, 255), &[], &[]));
|
||||
buf.extend(cmd_submit(5, DIR_OUT, 0, 0, 0, 0, setup_set_config(1), &[], &[]));
|
||||
buf
|
||||
}
|
||||
|
||||
fn gen_parse_command() {
|
||||
println!("fuzz_parse_command:");
|
||||
write_seed("fuzz_parse_command", "devlist", &op_req_devlist());
|
||||
write_seed("fuzz_parse_command", "import", &op_req_import("0-0-0"));
|
||||
}
|
||||
|
||||
fn gen_urb_hid() {
|
||||
println!("fuzz_urb_hid:");
|
||||
|
||||
// Seed 1: Standard enumeration sequence
|
||||
write_seed("fuzz_urb_hid", "enumerate", &enumeration_sequence());
|
||||
|
||||
// Seed 2: Enumeration + HID-specific requests
|
||||
let mut buf = enumeration_sequence();
|
||||
let seq = 6;
|
||||
// GET_DESCRIPTOR(HID Report Descriptor, type=0x22, 128 bytes) on interface 0
|
||||
buf.extend(cmd_submit(seq, DIR_IN, 0, 128, 0, 0,
|
||||
setup_get_desc_iface(0x22, 0, 0, 128), &[], &[]));
|
||||
// SET_IDLE (class request: bmRequestType=0x21, bRequest=0x0A)
|
||||
buf.extend(cmd_submit(seq + 1, DIR_OUT, 0, 0, 0, 0,
|
||||
[0x21, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], &[], &[]));
|
||||
// GET_REPORT (class request: bmRequestType=0xA1, bRequest=0x01, report type=Input(1))
|
||||
buf.extend(cmd_submit(seq + 2, DIR_IN, 0, 8, 0, 0,
|
||||
[0xA1, 0x01, 0x00, 0x01, 0x00, 0x00, 0x08, 0x00], &[], &[]));
|
||||
write_seed("fuzz_urb_hid", "hid-class-requests", &buf);
|
||||
|
||||
// Seed 3: Interrupt IN polls on ep 0x81 (keyboard reports)
|
||||
let mut buf = enumeration_sequence();
|
||||
for i in 0..4u32 {
|
||||
buf.extend(cmd_submit(6 + i, DIR_IN, 0x01, 8, 0, 10, [0u8; 8], &[], &[]));
|
||||
}
|
||||
write_seed("fuzz_urb_hid", "hid-interrupt-in", &buf);
|
||||
|
||||
// Seed 4: CMD_SUBMIT followed by CMD_UNLINK
|
||||
let mut buf = cmd_submit(1, DIR_IN, 0x01, 8, 0, 10, [0u8; 8], &[], &[]);
|
||||
buf.extend(cmd_unlink(2, 1));
|
||||
write_seed("fuzz_urb_hid", "unlink", &buf);
|
||||
|
||||
// Seed 5: Zero-length control transfer (GET_STATUS)
|
||||
write_seed("fuzz_urb_hid", "get-status",
|
||||
&cmd_submit(1, DIR_IN, 0, 2, 0, 0,
|
||||
[0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00], &[], &[]));
|
||||
}
|
||||
|
||||
fn gen_urb_cdc() {
|
||||
println!("fuzz_urb_cdc:");
|
||||
|
||||
// Seed 1: Standard enumeration
|
||||
write_seed("fuzz_urb_cdc", "enumerate", &enumeration_sequence());
|
||||
|
||||
// Seed 2: Enumeration + CDC class requests
|
||||
let mut buf = enumeration_sequence();
|
||||
let seq = 6;
|
||||
// SET_LINE_CODING: bmRequestType=0x21, bRequest=0x20
|
||||
// Data: 115200 baud (LE), 1 stop bit, no parity, 8 data bits
|
||||
let line_coding: [u8; 7] = [0x00, 0xC2, 0x01, 0x00, 0x00, 0x00, 0x08];
|
||||
buf.extend(cmd_submit(seq, DIR_OUT, 0, 7, 0, 0,
|
||||
[0x21, 0x20, 0x00, 0x00, 0x00, 0x00, 0x07, 0x00], &line_coding, &[]));
|
||||
// SET_CONTROL_LINE_STATE: DTR + RTS active (wValue=0x0003)
|
||||
buf.extend(cmd_submit(seq + 1, DIR_OUT, 0, 0, 0, 0,
|
||||
[0x21, 0x22, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00], &[], &[]));
|
||||
write_seed("fuzz_urb_cdc", "cdc-class-requests", &buf);
|
||||
|
||||
// Seed 3: Bulk OUT data transfer on ep 0x02
|
||||
let mut buf = enumeration_sequence();
|
||||
let test_data = b"Hello, CDC!\r\n";
|
||||
buf.extend(cmd_submit(6, DIR_OUT, 0x02, test_data.len() as u32, 0, 0,
|
||||
[0u8; 8], test_data, &[]));
|
||||
write_seed("fuzz_urb_cdc", "cdc-bulk-out", &buf);
|
||||
|
||||
// Seed 4: Bulk IN on ep 0x82 (ep=2, direction=IN)
|
||||
let mut buf = enumeration_sequence();
|
||||
buf.extend(cmd_submit(6, DIR_IN, 0x02, 512, 0, 0, [0u8; 8], &[], &[]));
|
||||
write_seed("fuzz_urb_cdc", "cdc-bulk-in", &buf);
|
||||
|
||||
// Seed 5: Interrupt IN on ep 0x81 (notification, ep=1, direction=IN)
|
||||
let mut buf = enumeration_sequence();
|
||||
buf.extend(cmd_submit(6, DIR_IN, 0x01, 8, 0, 10, [0u8; 8], &[], &[]));
|
||||
write_seed("fuzz_urb_cdc", "cdc-interrupt-in", &buf);
|
||||
}
|
||||
|
||||
fn gen_urb_uac() {
|
||||
println!("fuzz_urb_uac:");
|
||||
|
||||
// Seed 1: Standard enumeration
|
||||
write_seed("fuzz_urb_uac", "enumerate", &enumeration_sequence());
|
||||
|
||||
// Seed 2: Enumeration + SET_INTERFACE to activate audio streaming
|
||||
let mut buf = enumeration_sequence();
|
||||
let seq = 6;
|
||||
buf.extend(cmd_submit(seq, DIR_OUT, 0, 0, 0, 0, setup_set_interface(1, 1), &[], &[]));
|
||||
buf.extend(cmd_submit(seq + 1, DIR_OUT, 0, 0, 0, 0, setup_set_interface(2, 1), &[], &[]));
|
||||
write_seed("fuzz_urb_uac", "uac-set-interface", &buf);
|
||||
|
||||
// Seed 3: Isochronous OUT on ep 0x01 (playback, 1 frame of 48kHz 16-bit stereo = 192 bytes)
|
||||
let audio_frame = vec![0x80u8; 192];
|
||||
let iso = iso_desc(0, 192);
|
||||
let mut buf = enumeration_sequence();
|
||||
buf.extend(cmd_submit(6, DIR_OUT, 0, 0, 0, 0, setup_set_interface(1, 1), &[], &[]));
|
||||
buf.extend(cmd_submit(7, DIR_OUT, 0x01, 192, 1, 1, [0u8; 8], &audio_frame, &iso));
|
||||
write_seed("fuzz_urb_uac", "uac-iso-out", &buf);
|
||||
|
||||
// Seed 4: Isochronous IN on ep 0x82 (capture)
|
||||
let iso = iso_desc(0, 192);
|
||||
let mut buf = enumeration_sequence();
|
||||
buf.extend(cmd_submit(6, DIR_OUT, 0, 0, 0, 0, setup_set_interface(2, 1), &[], &[]));
|
||||
buf.extend(cmd_submit(7, DIR_IN, 0x02, 192, 1, 1, [0u8; 8], &[], &iso));
|
||||
write_seed("fuzz_urb_uac", "uac-iso-in", &buf);
|
||||
|
||||
// Seed 5: Multi-packet ISO OUT (3 frames)
|
||||
let audio_data = vec![0x80u8; 576];
|
||||
let mut iso = Vec::new();
|
||||
iso.extend(iso_desc(0, 192));
|
||||
iso.extend(iso_desc(192, 192));
|
||||
iso.extend(iso_desc(384, 192));
|
||||
let mut buf = enumeration_sequence();
|
||||
buf.extend(cmd_submit(6, DIR_OUT, 0, 0, 0, 0, setup_set_interface(1, 1), &[], &[]));
|
||||
buf.extend(cmd_submit(7, DIR_OUT, 0x01, 576, 3, 1, [0u8; 8], &audio_data, &iso));
|
||||
write_seed("fuzz_urb_uac", "uac-iso-out-multi", &buf);
|
||||
}
|
||||
|
||||
fn gen_handle_client() {
|
||||
println!("fuzz_handle_client:");
|
||||
|
||||
// Seed 1: OP_REQ_DEVLIST only (tests early negotiation path)
|
||||
write_seed("fuzz_handle_client", "devlist-only", &op_req_devlist());
|
||||
|
||||
// Seed 2: OP_REQ_DEVLIST then OP_REQ_IMPORT (two-step negotiation)
|
||||
let mut buf = op_req_devlist();
|
||||
buf.extend(op_req_import("0-0-0"));
|
||||
write_seed("fuzz_handle_client", "devlist-then-import", &buf);
|
||||
|
||||
// Seed 3: OP_REQ_IMPORT + enumeration sequence
|
||||
let mut buf = op_req_import("0-0-0");
|
||||
buf.extend(enumeration_sequence());
|
||||
write_seed("fuzz_handle_client", "import-enumerate", &buf);
|
||||
|
||||
// Seed 4: Full session — import + enumerate + HID class requests + interrupt
|
||||
let mut buf = op_req_import("0-0-0");
|
||||
buf.extend(enumeration_sequence());
|
||||
let seq = 6;
|
||||
buf.extend(cmd_submit(seq, DIR_IN, 0, 128, 0, 0,
|
||||
setup_get_desc_iface(0x22, 0, 0, 128), &[], &[]));
|
||||
buf.extend(cmd_submit(seq + 1, DIR_OUT, 0, 0, 0, 0,
|
||||
[0x21, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], &[], &[]));
|
||||
for i in 0..4u32 {
|
||||
buf.extend(cmd_submit(seq + 2 + i, DIR_IN, 0x01, 8, 0, 10, [0u8; 8], &[], &[]));
|
||||
}
|
||||
write_seed("fuzz_handle_client", "import-hid-full", &buf);
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Generating fuzz corpus seeds...\n");
|
||||
gen_parse_command();
|
||||
gen_urb_hid();
|
||||
gen_urb_cdc();
|
||||
gen_urb_uac();
|
||||
gen_handle_client();
|
||||
println!("\nDone.");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue