test(server): exercise keyed stateful fuzz paths
This commit is contained in:
2 files changed
+128
-13
No files matched your search
@@ -1,8 +1,12 @@
|
||||
#![no_main]
|
||||
|
||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||
use std::{
|
||||
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use arbitrary::Unstructured;
|
||||
use irtt_proto::{decode_open_reply, encode_request, Params, RequestToEncode};
|
||||
use irtt_server::{ServerConfig, ServerCore};
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
@@ -15,6 +19,13 @@ const MAX_DATAGRAM_LEN: usize = 2 * 1024;
|
||||
const MAX_SESSIONS: u8 = 8;
|
||||
const FUZZ_HMAC_KEY: &[u8] = b"fuzz-server-core-key";
|
||||
|
||||
#[derive(Clone)]
|
||||
struct FuzzSession {
|
||||
token: u64,
|
||||
peer: SocketAddr,
|
||||
params: Params,
|
||||
}
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
if data.len() > MAX_INPUT_LEN {
|
||||
return;
|
||||
@@ -24,26 +35,34 @@ fuzz_target!(|data: &[u8]| {
|
||||
let Ok(mut config) = config(&mut input) else {
|
||||
return;
|
||||
};
|
||||
if input.arbitrary::<bool>().unwrap_or(false) {
|
||||
config = config.with_hmac_key(FUZZ_HMAC_KEY);
|
||||
let hmac_key = input
|
||||
.arbitrary::<bool>()
|
||||
.ok()
|
||||
.filter(|enabled| *enabled)
|
||||
.map(|_| FUZZ_HMAC_KEY);
|
||||
if let Some(key) = hmac_key {
|
||||
config = config.with_hmac_key(key);
|
||||
}
|
||||
let mut core = ServerCore::new(config);
|
||||
let mut sessions = Vec::new();
|
||||
|
||||
for _ in 0..MAX_DATAGRAMS {
|
||||
let Ok(peer) = peer(&mut input) else {
|
||||
break;
|
||||
};
|
||||
let Ok(length) = input.int_in_range(0..=MAX_DATAGRAM_LEN) else {
|
||||
break;
|
||||
};
|
||||
let Ok(packet) = input.bytes(length) else {
|
||||
let Ok(operation) = input.arbitrary::<u8>() else {
|
||||
break;
|
||||
};
|
||||
|
||||
match operation % 4 {
|
||||
// Keep arbitrary-byte admission coverage alongside the stateful
|
||||
// production-encoded requests below.
|
||||
0 => raw_datagram(&mut core, &mut input),
|
||||
1 => open_session(&mut core, &mut input, hmac_key, &mut sessions),
|
||||
2 => echo_session(&mut core, &mut input, hmac_key, &sessions),
|
||||
_ => close_session(&mut core, &mut input, hmac_key, &mut sessions),
|
||||
}
|
||||
|
||||
// Arbitrary network input is expected to be rejected or to produce an
|
||||
// internal error in exceptional host conditions. It must never panic,
|
||||
// and it may never grow the bounded table past its configured cap.
|
||||
let _ = core.handle_datagram(peer, packet);
|
||||
assert!(core.session_count() <= core.config().max_sessions());
|
||||
}
|
||||
});
|
||||
@@ -52,7 +71,103 @@ fn config(input: &mut Unstructured<'_>) -> arbitrary::Result<ServerConfig> {
|
||||
let max_sessions = usize::from(input.int_in_range(0..=MAX_SESSIONS)?);
|
||||
Ok(ServerConfig::default()
|
||||
.with_max_sessions(max_sessions)
|
||||
.with_max_packet_length(MAX_DATAGRAM_LEN))
|
||||
.with_max_packet_length(MAX_DATAGRAM_LEN)
|
||||
.with_min_send_interval(Duration::ZERO))
|
||||
}
|
||||
|
||||
fn raw_datagram(core: &mut ServerCore, input: &mut Unstructured<'_>) {
|
||||
let (Ok(peer), Ok(length)) = (peer(input), input.int_in_range(0..=MAX_DATAGRAM_LEN)) else {
|
||||
return;
|
||||
};
|
||||
let Ok(packet) = input.bytes(length) else {
|
||||
return;
|
||||
};
|
||||
let _ = core.handle_datagram(peer, packet);
|
||||
}
|
||||
|
||||
fn open_session(
|
||||
core: &mut ServerCore,
|
||||
input: &mut Unstructured<'_>,
|
||||
hmac_key: Option<&[u8]>,
|
||||
sessions: &mut Vec<FuzzSession>,
|
||||
) {
|
||||
let Ok(peer) = peer(input) else {
|
||||
return;
|
||||
};
|
||||
let params = session_params();
|
||||
let request = encode_request(
|
||||
RequestToEncode::Open {
|
||||
params: ¶ms,
|
||||
no_test: false,
|
||||
},
|
||||
hmac_key,
|
||||
)
|
||||
.expect("fixed valid fuzz open must encode");
|
||||
let Ok(Some(reply)) = core.handle_datagram(peer, &request) else {
|
||||
return;
|
||||
};
|
||||
let Ok(reply) = decode_open_reply(reply.bytes(), hmac_key) else {
|
||||
return;
|
||||
};
|
||||
sessions.push(FuzzSession {
|
||||
token: reply.token,
|
||||
peer,
|
||||
params: reply.params,
|
||||
});
|
||||
}
|
||||
|
||||
fn echo_session(
|
||||
core: &mut ServerCore,
|
||||
input: &mut Unstructured<'_>,
|
||||
hmac_key: Option<&[u8]>,
|
||||
sessions: &[FuzzSession],
|
||||
) {
|
||||
let Some(session) =
|
||||
sessions.get(usize::from(input.arbitrary::<u8>().unwrap_or(0)) % sessions.len().max(1))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let sequence = input.arbitrary().unwrap_or_default();
|
||||
let request = encode_request(
|
||||
RequestToEncode::Echo {
|
||||
token: session.token,
|
||||
sequence,
|
||||
params: &session.params,
|
||||
payload: &[],
|
||||
},
|
||||
hmac_key,
|
||||
)
|
||||
.expect("fixed valid fuzz echo must encode");
|
||||
let _ = core.handle_datagram(session.peer, &request);
|
||||
}
|
||||
|
||||
fn close_session(
|
||||
core: &mut ServerCore,
|
||||
input: &mut Unstructured<'_>,
|
||||
hmac_key: Option<&[u8]>,
|
||||
sessions: &mut Vec<FuzzSession>,
|
||||
) {
|
||||
let index = usize::from(input.arbitrary::<u8>().unwrap_or(0)) % sessions.len().max(1);
|
||||
let Some(session) = sessions.get(index) else {
|
||||
return;
|
||||
};
|
||||
let request = encode_request(
|
||||
RequestToEncode::Close {
|
||||
token: session.token,
|
||||
},
|
||||
hmac_key,
|
||||
)
|
||||
.expect("fixed valid fuzz close must encode");
|
||||
let _ = core.handle_datagram(session.peer, &request);
|
||||
sessions.swap_remove(index);
|
||||
}
|
||||
|
||||
fn session_params() -> Params {
|
||||
Params {
|
||||
protocol_version: 1,
|
||||
length: 256,
|
||||
..Params::default()
|
||||
}
|
||||
}
|
||||
|
||||
fn peer(input: &mut Unstructured<'_>) -> arbitrary::Result<SocketAddr> {
|
||||
|
||||
Reference in new issue
Block a user