9b0f09a8a8
Track B (milestone 5 / §16 hardening): - tests/hostile_network.rs: in-process UDP relay/shim between a test client and a real dosh-server that can drop, reorder, and duplicate datagrams, and rebind its upstream socket mid-session. Asserts: session survives loss/reorder, duplicated/replayed Input is applied at most once, stale packets after resume are ignored (not fatal), and a client source-address change preserves the session. - tests/parser_robustness.rs: deterministic randomized tests throwing garbage at every reachable public parser (packet decode, from_body for all protocol/native structs, authorized_keys, known_hosts, host-key line, ssh-ed25519 blob, bootstrap, attach ticket); asserts none panic. - fuzz/: standalone cargo-fuzz crate (own [workspace], non-default member) with libfuzzer-sys harnesses for packet decode, from_body, authorized_keys, known_hosts, handshake structs+verifiers, and attach tickets. README documents the run command. - .github/workflows/ci.yml: add fuzz-smoke job (nightly + cargo-fuzz, short -max_total_time per target, tolerant if tooling unavailable); existing fmt/test/build/bench steps unchanged. cargo fmt --check and cargo test are green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
45 lines
1.8 KiB
Rust
45 lines
1.8 KiB
Rust
#![no_main]
|
|
//! Fuzz the native handshake structs and their structural verifiers.
|
|
//!
|
|
//! Beyond plain deserialization (covered by the `from_body` target), this drives
|
|
//! the verifier state machine: if the input happens to decode into the handshake
|
|
//! structs, run `verify_server_hello`, `user_auth_transcript`, and
|
|
//! `verify_native_user_auth` on them. These run on attacker-controlled material
|
|
//! during the handshake and must reject (Err) without panicking.
|
|
|
|
use libfuzzer_sys::fuzz_target;
|
|
|
|
use dosh::native::{
|
|
NativeClientHello, NativeServerHello, NativeUserAuth, user_auth_transcript,
|
|
verify_native_user_auth, verify_server_hello,
|
|
};
|
|
use dosh::protocol;
|
|
|
|
fuzz_target!(|data: &[u8]| {
|
|
// Split the input into three slices and try to decode each into a handshake
|
|
// struct. Use a length prefix scheme that is robust to short inputs.
|
|
if data.len() < 3 {
|
|
return;
|
|
}
|
|
let n = data.len();
|
|
let a = n / 3;
|
|
let b = 2 * n / 3;
|
|
let (chunk_client, chunk_server, chunk_auth) = (&data[..a], &data[a..b], &data[b..]);
|
|
|
|
let client: Option<NativeClientHello> = protocol::from_body(chunk_client).ok();
|
|
let server: Option<NativeServerHello> = protocol::from_body(chunk_server).ok();
|
|
let auth: Option<NativeUserAuth> = protocol::from_body(chunk_auth).ok();
|
|
|
|
if let (Some(client), Some(server)) = (&client, &server) {
|
|
// Host signature verification over the transcript must not panic.
|
|
let _ = verify_server_hello(client, server);
|
|
|
|
if let Some(auth) = &auth {
|
|
// Transcript construction and full user-auth verification (signature
|
|
// check + authorized-key matching) must not panic on garbage.
|
|
let _ = user_auth_transcript(client, server, auth);
|
|
let _ = verify_native_user_auth(client, server, auth, &[], None);
|
|
}
|
|
}
|
|
});
|