diff --git a/Cargo.lock b/Cargo.lock index 8ce023e1..3d64c0eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,6 +37,12 @@ dependencies = [ "backtrace", ] +[[package]] +name = "arbitrary" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110" + [[package]] name = "arc-swap" version = "1.7.1" @@ -1241,6 +1247,17 @@ version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "libfuzzer-sys" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a96cfd5557eb82f2b83fed4955246c988d331975a002961b07c81584d107e7f7" +dependencies = [ + "arbitrary", + "cc", + "once_cell", +] + [[package]] name = "libgit2-sys" version = "0.17.0+1.8.1" @@ -1533,6 +1550,7 @@ dependencies = [ "gtk4", "humansize", "itertools", + "libfuzzer-sys", "lrumap", "memmap2", "num-format", diff --git a/Cargo.toml b/Cargo.toml index 19edcba1..c3fb1ff3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -59,6 +59,7 @@ serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" rand = "0.8.5" rand_xorshift = "0.3.0" +libfuzzer-sys = "0.4.7" [target.'cfg(target_os = "macos")'.dev-dependencies] procspawn = "1.0.0" @@ -116,6 +117,26 @@ record-ui-test = ["serde", "serde_json"] # debug-region-map = [] +# fuzzer: +# +# Used for fuzzing the decoder. Does not affect the main binary, but enables +# an example target named 'fuzzer'. +# +# Build the fuzzer with: +# +# cargo +nightly rustc --features fuzzer --example fuzzer -- \ +# -C passes='sancov-module' \ +# -C llvm-args='-sanitizer-coverage-level=3' \ +# -C llvm-args='-sanitizer-coverage-inline-8bit-counters' \ +# -Z sanitizer=address +# +# Run with: +# +# target/debug/examples/fuzzer +# +# Not currently working on Windows. +fuzzer = [] + [[bin]] name = "packetry" path = "src/main.rs" @@ -124,5 +145,10 @@ path = "src/main.rs" name = "packetry-cli" path = "src/cli.rs" +[[example]] +name = "fuzzer" +path = "src/fuzzer.rs" +required-features = ["fuzzer"] + [package.metadata.cargo-all-features] skip_optional_dependencies = true diff --git a/src/decoder.rs b/src/decoder.rs index 848ab53c..617e340e 100644 --- a/src/decoder.rs +++ b/src/decoder.rs @@ -598,10 +598,11 @@ impl Decoder { let dev_addr = token.device_address(); let ep_num = token.endpoint_number(); let direction = match (ep_num.0, pid) { - (0, _) => Direction::Out, - (_, PID::IN) => Direction::In, - (_, PID::OUT) => Direction::Out, - (_, PID::PING) => Direction::Out, + (0, _) => Direction::Out, + (_, PID::SETUP) => Direction::Out, + (_, PID::IN) => Direction::In, + (_, PID::OUT) => Direction::Out, + (_, PID::PING) => Direction::Out, _ => bail!("PID {pid} does not indicate a direction") }; let key = EndpointKey { @@ -682,11 +683,13 @@ impl Decoder { use PID::*; use TransactionStyle::*; let transaction_id = self.capture.transaction_index.push(packet_id)?; - let (style, endpoint_id) = if pid == SPLIT { - let split = SplitFields::from_packet(packet); - (Split(split.sc(), split.endpoint_type(), None), None) - } else { - (Simple(pid), Some(self.packet_endpoint(pid, packet)?)) + let (style, endpoint_id) = match pid { + Malformed => (Simple(pid), Some(INVALID_EP_ID)), + SPLIT => { + let split = SplitFields::from_packet(packet); + (Split(split.sc(), split.endpoint_type(), None), None) + }, + pid => (Simple(pid), Some(self.packet_endpoint(pid, packet)?)) }; let mut state = TransactionState { style, diff --git a/src/fuzzer.rs b/src/fuzzer.rs new file mode 100644 index 00000000..3e89a727 --- /dev/null +++ b/src/fuzzer.rs @@ -0,0 +1,37 @@ +//! Fuzz the USB packet decoder. + +#![allow(dead_code)] +#![no_main] + +#[macro_use] +extern crate bitfield; + +use libfuzzer_sys::{arbitrary::{Arbitrary, Unstructured}, fuzz_target}; + +mod capture; +mod compact_index; +mod data_stream; +mod decoder; +mod id; +mod index_stream; +mod pcap; +mod rcu; +mod stream; +mod usb; +mod util; +mod vec_map; + +use capture::create_capture; +use decoder::Decoder; + +fuzz_target!(|data: &[u8]| { + let mut input = Unstructured::new(data); + let packets = Vec::<(Vec::, u32)>::arbitrary(&mut input).unwrap(); + let mut timestamp = u32::arbitrary(&mut input).unwrap() as u64; + let (writer, _reader) = create_capture().unwrap(); + let mut decoder = Decoder::new(writer).unwrap(); + for (packet, time_delta) in packets { + timestamp += time_delta as u64; + decoder.handle_raw_packet(&packet, timestamp).unwrap(); + } +}); diff --git a/tests/bad-crcs/capture.pcap b/tests/bad-crcs/capture.pcap new file mode 100644 index 00000000..229cf2ff Binary files /dev/null and b/tests/bad-crcs/capture.pcap differ diff --git a/tests/bad-crcs/reference.txt b/tests/bad-crcs/reference.txt new file mode 100644 index 00000000..229ec5ff --- /dev/null +++ b/tests/bad-crcs/reference.txt @@ -0,0 +1,13 @@ +Polling 1 times for unidentified transfer on endpoint 7.1 IN + IN transaction on 7.1, NAK + IN packet on 7.1, CRC 1B + NAK packet +1 invalid groups + 1 malformed packet + Malformed packet (possibly IN, but bad CRC) of 3 bytes: [69, B7, DB] +1 invalid groups + 1 malformed packet + Malformed packet (possibly IN, but bad CRC) of 3 bytes: [69, B7, DB] +1 invalid groups + 1 malformed packet + Malformed packet (possibly SOF, but bad CRC) of 3 bytes: [A5, BB, CE] diff --git a/tests/tests.txt b/tests/tests.txt index a70f00d7..48ccbeba 100644 --- a/tests/tests.txt +++ b/tests/tests.txt @@ -1,4 +1,5 @@ analyzer-test-bad-cable +bad-crcs emf2022-badge hackrf-connect hackrf-dfu-enum