#![cfg(feature = "fuzzing")]
use pktkit::fuzz;
struct Rng(u64);
impl Rng {
fn next(&mut self) -> u64 {
let mut x = self.0;
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
self.0 = x;
x
}
fn below(&mut self, n: usize) -> usize {
if n == 0 {
0
} else {
(self.next() % n as u64) as usize
}
}
fn byte(&mut self) -> u8 {
self.next() as u8
}
}
fn seed() -> u64 {
std::env::var("PKTKIT_FUZZ_SEED")
.ok()
.and_then(|s| s.parse().ok())
.unwrap_or(0x9E37_79B9_7F4A_7C15)
}
fn corpus() -> Vec<Vec<u8>> {
use pktkit::build::{build_icmpv4, build_ipv4, build_ipv6, build_tcp, build_udp};
use pktkit::{EtherType, MacAddr, Protocol, TcpFlags, build_frame};
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
let v4a = Ipv4Addr::new(10, 0, 0, 1);
let v4b = Ipv4Addr::new(10, 0, 0, 2);
let v6a: Ipv6Addr = "2001:db8::1".parse().unwrap();
let v6b: Ipv6Addr = "2001:db8::2".parse().unwrap();
let mut out = Vec::new();
let udp = build_udp(v4a.into(), v4b.into(), 5000, 53, b"hello world");
out.push(build_ipv4(v4a, v4b, Protocol::UDP, 64, &udp));
let tcp = build_tcp(
v4a.into(),
v4b.into(),
1234,
21,
1,
2,
TcpFlags::PSH | TcpFlags::ACK,
65535,
b"PORT 10,0,0,1,4,1\r\n",
);
out.push(build_ipv4(v4a, v4b, Protocol::TCP, 64, &tcp));
out.push(build_ipv4(
v4a,
v4b,
Protocol::ICMP,
64,
&build_icmpv4(8, 0, [0, 1, 0, 1], b"ping"),
));
let big = build_udp(v4a.into(), v4b.into(), 1, 2, &vec![0xAB; 3000]);
out.push(build_ipv4(v4a, v4b, Protocol::UDP, 64, &big));
let udp6 = build_udp(IpAddr::V6(v6a), IpAddr::V6(v6b), 5000, 53, b"hello");
out.push(build_ipv6(v6a, v6b, Protocol::UDP, 64, &udp6));
let mut chained = vec![17u8, 0, 0, 0, 0, 0, 0, 0]; chained.extend_from_slice(&udp6);
out.push(build_ipv6(v6a, v6b, Protocol(0), 64, &chained));
let mut fragged = vec![17u8, 0, 0, 0, 0, 0, 0, 1]; fragged.extend_from_slice(&udp6);
out.push(build_ipv6(v6a, v6b, Protocol(44), 64, &fragged));
let ip = out[0].clone();
let eth = build_frame(MacAddr::broadcast(), MacAddr::zero(), EtherType::IPV4, &ip);
out.push(pktkit::build::push_vlan(
pktkit::Frame::from_slice(ð),
100,
5,
));
out.push(eth);
out.push(dhcp_discover());
out.push(dns_response());
out.push(Vec::new());
out.push(vec![0x45]);
out.push(vec![0x60; 39]);
out.push(vec![0xFF; 64]);
out
}
fn dhcp_discover() -> Vec<u8> {
let mut m = vec![0u8; 240];
m[0] = 1; m[1] = 1; m[2] = 6; m[4..8].copy_from_slice(&0xDEADBEEFu32.to_be_bytes());
m[28..34].copy_from_slice(&[2, 0, 0, 0, 0, 1]);
m[236..240].copy_from_slice(&[99, 130, 83, 99]); m.extend_from_slice(&[53, 1, 1]); m.extend_from_slice(&[55, 3, 1, 3, 6]); m.push(255); m
}
fn dns_response() -> Vec<u8> {
let mut m = Vec::new();
m.extend_from_slice(&0x1234u16.to_be_bytes()); m.extend_from_slice(&0x8180u16.to_be_bytes()); m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&0u16.to_be_bytes()); m.extend_from_slice(&0u16.to_be_bytes()); m.extend_from_slice(&[
7, b'e', b'x', b'a', b'm', b'p', b'l', b'e', 3, b'c', b'o', b'm', 0,
]);
m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&1u16.to_be_bytes()); m.extend_from_slice(&[0xC0, 0x0C]); m.extend_from_slice(&1u16.to_be_bytes());
m.extend_from_slice(&1u16.to_be_bytes());
m.extend_from_slice(&300u32.to_be_bytes()); m.extend_from_slice(&4u16.to_be_bytes()); m.extend_from_slice(&[93, 184, 216, 34]);
m
}
fn mutate(rng: &mut Rng, buf: &mut Vec<u8>) {
match rng.below(8) {
0 => {
if !buf.is_empty() {
let i = rng.below(buf.len());
buf[i] ^= 1 << rng.below(8);
}
}
1 => {
if !buf.is_empty() {
let i = rng.below(buf.len());
buf[i] = rng.byte();
}
}
2 => {
let n = rng.below(buf.len() + 1);
buf.truncate(n);
}
3 => {
let n = rng.below(64);
for _ in 0..n {
buf.push(rng.byte());
}
}
4 => {
if !buf.is_empty() {
let start = rng.below(buf.len());
let end = (start + rng.below(16)).min(buf.len());
for b in &mut buf[start..end] {
*b = rng.byte();
}
}
}
5 => {
for at in [2usize, 4, 6, 12] {
if buf.len() >= at + 2 && rng.below(2) == 0 {
buf[at] = 0xFF;
buf[at + 1] = 0xFF;
}
}
}
6 => {
if !buf.is_empty() {
buf[0] = (rng.byte() & 0xF0) | (buf[0] & 0x0F);
}
}
_ => {
if !buf.is_empty() {
let start = rng.below(buf.len());
let end = (start + rng.below(24)).min(buf.len());
for b in &mut buf[start..end] {
*b = 0;
}
}
}
}
}
#[test]
fn mutated_input_never_panics() {
let seed = seed();
let mut rng = Rng(seed);
let corpus = corpus();
for i in 0..4_000 {
let mut buf = corpus[rng.below(corpus.len())].clone();
for _ in 0..1 + rng.below(4) {
mutate(&mut rng, &mut buf);
}
let guard = Guard {
seed,
iteration: i,
input: &buf,
};
fuzz::all(&buf);
std::mem::forget(guard);
}
}
#[test]
fn random_input_never_panics() {
let mut rng = Rng(seed() ^ 0xA5A5_A5A5);
for i in 0..4_000 {
let len = rng.below(200);
let buf: Vec<u8> = (0..len).map(|_| rng.byte()).collect();
let guard = Guard {
seed: seed(),
iteration: i,
input: &buf,
};
fuzz::all(&buf);
std::mem::forget(guard);
}
}
#[test]
fn every_prefix_of_a_valid_packet_is_survivable() {
for msg in corpus() {
for n in 0..=msg.len() {
let guard = Guard {
seed: 0,
iteration: n,
input: &msg[..n],
};
fuzz::all(&msg[..n]);
std::mem::forget(guard);
}
}
}
struct Guard<'a> {
seed: u64,
iteration: usize,
input: &'a [u8],
}
impl Drop for Guard<'_> {
fn drop(&mut self) {
eprintln!(
"robustness failure: seed={:#x} iteration={} input={:02x?}",
self.seed, self.iteration, self.input
);
}
}