-
Notifications
You must be signed in to change notification settings - Fork 123
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test: Experiment with
cargo fuzz
(#1764)
* test: Experiment with `cargo fuzz` Far from being ready. * Fix * Fixes * Fixes * Make clippy happy * Another clippy fix * Fuzz `Frame::decode` * Rework * Fix clippy * Try and disable on Windows * Retry * Retry * Try again * Review suggestions * Clippy * Update Cargo.toml Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Update neqo-transport/src/packet/mod.rs Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Rename directory to `fuzz` * `lazy_static` -> `OnceLock` * * Add ability to save items to fuzzing corpus via `build-fuzzing-corpus` feature. * Disable RNG and encryption during fuzzing and corpus generation * Misc. other fixes. * Clippy * `DefaultHasher` is not in `std::hash` yet in Rust 1.74 * Add two more fuzzers * Tweaks * Don't check coverage for fuzzing code * Update neqo-common/src/fuzz.rs Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Update neqo-transport/src/connection/mod.rs Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Update neqo-transport/src/connection/mod.rs Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Update neqo-crypto/src/p11.rs Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Fixups * More fixups * Remove `fuzz/src/lib.rs` now that #1830 is merged * Fix merge * Create corpus directory if it does not exist * Use a predictable sequence of bytes for `disable-random` instead of just zeros. * Update neqo-common/src/lib.rs Co-authored-by: Martin Thomson <mt@lowentropy.net> Signed-off-by: Lars Eggert <lars@eggert.org> * Use thread-local instead of atomic * Only write generated packets, and only ones without test frames * Build a corpus for `client_initial` and `server_initial` fuzz targets * Cleanup --------- Signed-off-by: Lars Eggert <lars@eggert.org> Co-authored-by: Max Inden <mail@max-inden.de> Co-authored-by: Martin Thomson <mt@lowentropy.net>
- Loading branch information
1 parent
ccf0302
commit 3797225
Showing
23 changed files
with
391 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
[workspace] | ||
members = [ | ||
"fuzz", | ||
"neqo-bin", | ||
"neqo-common", | ||
"neqo-crypto", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
target | ||
corpus | ||
artifacts | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
[package] | ||
name = "fuzz" | ||
authors.workspace = true | ||
homepage.workspace = true | ||
repository.workspace = true | ||
version.workspace = true | ||
edition.workspace = true | ||
rust-version.workspace = true | ||
license.workspace = true | ||
|
||
[package.metadata] | ||
cargo-fuzz = true | ||
|
||
[dependencies] | ||
neqo-common = { path = "../neqo-common" } | ||
neqo-crypto = { path = "../neqo-crypto" } | ||
neqo-transport = { path = "../neqo-transport" } | ||
test-fixture = { path = "../test-fixture" } | ||
|
||
[target.'cfg(not(windows))'.dependencies] | ||
libfuzzer-sys = { version = "0.4" } | ||
|
||
[lints] | ||
workspace = true | ||
|
||
[[bin]] | ||
name = "packet" | ||
path = "fuzz_targets/packet.rs" | ||
test = false | ||
doc = false | ||
bench = false | ||
|
||
[[bin]] | ||
name = "frame" | ||
path = "fuzz_targets/frame.rs" | ||
test = false | ||
doc = false | ||
bench = false | ||
|
||
[[bin]] | ||
name = "client_initial" | ||
path = "fuzz_targets/client_initial.rs" | ||
test = false | ||
doc = false | ||
bench = false | ||
|
||
[[bin]] | ||
name = "server_initial" | ||
path = "fuzz_targets/server_initial.rs" | ||
test = false | ||
doc = false | ||
bench = false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
#![cfg_attr(all(fuzzing, not(windows)), no_main)] | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
use libfuzzer_sys::fuzz_target; | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
fuzz_target!(|data: &[u8]| { | ||
use neqo_common::{Datagram, Encoder, Role}; | ||
use neqo_transport::Version; | ||
use test_fixture::{ | ||
default_client, default_server, | ||
header_protection::{ | ||
apply_header_protection, decode_initial_header, initial_aead_and_hp, | ||
remove_header_protection, | ||
}, | ||
now, | ||
}; | ||
|
||
let mut client = default_client(); | ||
let ci = client.process(None, now()).dgram().expect("a datagram"); | ||
let Some((header, d_cid, s_cid, payload)) = decode_initial_header(&ci, Role::Client) else { | ||
return; | ||
}; | ||
let (aead, hp) = initial_aead_and_hp(d_cid, Role::Client); | ||
let (_, pn) = remove_header_protection(&hp, header, payload); | ||
|
||
let mut payload_enc = Encoder::with_capacity(1200); | ||
payload_enc.encode(data); // Add fuzzed data. | ||
|
||
// Make a new header with a 1 byte packet number length. | ||
let mut header_enc = Encoder::new(); | ||
header_enc | ||
.encode_byte(0xc0) // Initial with 1 byte packet number. | ||
.encode_uint(4, Version::default().wire_version()) | ||
.encode_vec(1, d_cid) | ||
.encode_vec(1, s_cid) | ||
.encode_vvec(&[]) | ||
.encode_varint(u64::try_from(payload_enc.len() + aead.expansion() + 1).unwrap()) | ||
.encode_byte(u8::try_from(pn).unwrap()); | ||
|
||
let mut ciphertext = header_enc.as_ref().to_vec(); | ||
ciphertext.resize(header_enc.len() + payload_enc.len() + aead.expansion(), 0); | ||
let v = aead | ||
.encrypt( | ||
pn, | ||
header_enc.as_ref(), | ||
payload_enc.as_ref(), | ||
&mut ciphertext[header_enc.len()..], | ||
) | ||
.unwrap(); | ||
assert_eq!(header_enc.len() + v.len(), ciphertext.len()); | ||
// Pad with zero to get up to 1200. | ||
ciphertext.resize(1200, 0); | ||
|
||
apply_header_protection( | ||
&hp, | ||
&mut ciphertext, | ||
(header_enc.len() - 1)..header_enc.len(), | ||
); | ||
let fuzzed_ci = Datagram::new( | ||
ci.source(), | ||
ci.destination(), | ||
ci.tos(), | ||
ci.ttl(), | ||
ciphertext, | ||
); | ||
|
||
let mut server = default_server(); | ||
let _response = server.process(Some(&fuzzed_ci), now()); | ||
}); | ||
|
||
#[cfg(any(not(fuzzing), windows))] | ||
fn main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
#![cfg_attr(all(fuzzing, not(windows)), no_main)] | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
use libfuzzer_sys::fuzz_target; | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
fuzz_target!(|data: &[u8]| { | ||
use neqo_common::Decoder; | ||
use neqo_transport::frame::Frame; | ||
|
||
// Run the fuzzer | ||
let mut decoder = Decoder::new(data); | ||
let _ = Frame::decode(&mut decoder); | ||
}); | ||
|
||
#[cfg(any(not(fuzzing), windows))] | ||
fn main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
#![cfg_attr(all(fuzzing, not(windows)), no_main)] | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
use libfuzzer_sys::fuzz_target; | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
fuzz_target!(|data: &[u8]| { | ||
use std::sync::OnceLock; | ||
|
||
use neqo_transport::{packet::PublicPacket, RandomConnectionIdGenerator}; | ||
|
||
static DECODER: OnceLock<RandomConnectionIdGenerator> = OnceLock::new(); | ||
let decoder = DECODER.get_or_init(|| RandomConnectionIdGenerator::new(20)); | ||
neqo_crypto::init().unwrap(); | ||
|
||
// Run the fuzzer | ||
let _ = PublicPacket::decode(data, decoder); | ||
}); | ||
|
||
#[cfg(any(not(fuzzing), windows))] | ||
fn main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
#![cfg_attr(all(fuzzing, not(windows)), no_main)] | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
use libfuzzer_sys::fuzz_target; | ||
|
||
#[cfg(all(fuzzing, not(windows)))] | ||
fuzz_target!(|data: &[u8]| { | ||
use neqo_common::{Datagram, Encoder, Role}; | ||
use neqo_transport::Version; | ||
use test_fixture::{ | ||
default_client, default_server, | ||
header_protection::{ | ||
apply_header_protection, decode_initial_header, initial_aead_and_hp, | ||
remove_header_protection, | ||
}, | ||
now, | ||
}; | ||
|
||
let mut client = default_client(); | ||
let ci = client.process(None, now()).dgram().expect("a datagram"); | ||
let mut server = default_server(); | ||
let si = server | ||
.process(Some(&ci), now()) | ||
.dgram() | ||
.expect("a datagram"); | ||
|
||
let Some((header, d_cid, s_cid, payload)) = decode_initial_header(&si, Role::Server) else { | ||
return; | ||
}; | ||
let (aead, hp) = initial_aead_and_hp(d_cid, Role::Server); | ||
let (_, pn) = remove_header_protection(&hp, header, payload); | ||
|
||
let mut payload_enc = Encoder::with_capacity(1200); | ||
payload_enc.encode(data); // Add fuzzed data. | ||
|
||
// Make a new header with a 1 byte packet number length. | ||
let mut header_enc = Encoder::new(); | ||
header_enc | ||
.encode_byte(0xc0) // Initial with 1 byte packet number. | ||
.encode_uint(4, Version::default().wire_version()) | ||
.encode_vec(1, d_cid) | ||
.encode_vec(1, s_cid) | ||
.encode_vvec(&[]) | ||
.encode_varint(u64::try_from(payload_enc.len() + aead.expansion() + 1).unwrap()) | ||
.encode_byte(u8::try_from(pn).unwrap()); | ||
|
||
let mut ciphertext = header_enc.as_ref().to_vec(); | ||
ciphertext.resize(header_enc.len() + payload_enc.len() + aead.expansion(), 0); | ||
let v = aead | ||
.encrypt( | ||
pn, | ||
header_enc.as_ref(), | ||
payload_enc.as_ref(), | ||
&mut ciphertext[header_enc.len()..], | ||
) | ||
.unwrap(); | ||
assert_eq!(header_enc.len() + v.len(), ciphertext.len()); | ||
// Pad with zero to get up to 1200. | ||
ciphertext.resize(1200, 0); | ||
|
||
apply_header_protection( | ||
&hp, | ||
&mut ciphertext, | ||
(header_enc.len() - 1)..header_enc.len(), | ||
); | ||
let fuzzed_si = Datagram::new( | ||
si.source(), | ||
si.destination(), | ||
si.tos(), | ||
si.ttl(), | ||
ciphertext, | ||
); | ||
let _response = client.process(Some(&fuzzed_si), now()); | ||
}); | ||
|
||
#[cfg(any(not(fuzzing), windows))] | ||
fn main() {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or | ||
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license | ||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your | ||
// option. This file may not be copied, modified, or distributed | ||
// except according to those terms. | ||
|
||
use std::{ | ||
collections::hash_map::DefaultHasher, | ||
fs::File, | ||
hash::{Hash, Hasher}, | ||
io::Write, | ||
path::Path, | ||
}; | ||
|
||
/// Write a data item `data` for the fuzzing target `target` to the fuzzing corpus. The caller needs | ||
/// to make sure that `target` is the correct fuzzing target name for the data written. | ||
/// | ||
/// # Panics | ||
/// | ||
/// Panics if the corpus directory does not exist or if the corpus item cannot be written. | ||
pub fn write_item_to_fuzzing_corpus(target: &str, data: &[u8]) { | ||
// This bakes in the assumption that we're executing in the root of the neqo workspace. | ||
// Unfortunately, `cargo fuzz` doesn't provide a way to learn the location of the corpus | ||
// directory. | ||
let corpus = Path::new("../fuzz/corpus").join(target); | ||
if !corpus.exists() { | ||
std::fs::create_dir_all(&corpus).expect("failed to create corpus directory"); | ||
} | ||
|
||
// Hash the data to get a unique name for the corpus item. | ||
let mut hasher = DefaultHasher::new(); | ||
data.hash(&mut hasher); | ||
let item_name = hex::encode(hasher.finish().to_be_bytes()); | ||
let item_path = corpus.join(item_name); | ||
if item_path.exists() { | ||
// Don't overwrite existing corpus items. | ||
return; | ||
} | ||
|
||
// Write the data to the corpus item. | ||
let mut file = File::create(item_path).expect("failed to create corpus item"); | ||
Write::write_all(&mut file, data).expect("failed to write to corpus item"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.