Skip to content

Commit 83cceac

Browse files
test: add fuzz testing + replace get_* with try_get_* to avoid panics (#165)
This adds fuzz testing via cargo-fuzz for three categories: - Parser targets: every Read impl in summit-types. Arbitrary bytes must parse to Ok / Err without panicking, and successful decodes roundtrip to byte-identical output. - Non-codec targets: ssz_tree_key::parse_key, derive_child_public, SszProof::verify with adversarial inputs. - Property-based targets: WithdrawalQueue op-sequence invariants, ExtPrivateKey sign-verify roundtrip, SSZ incremental-update-vs-rebuild root equality, and generate→verify proof roundtrip across all proof kinds. Also replaces calls to get_u64 (and similar) with try_get_u64 to avoid panics if there aren't enough bytes to fill the type.
1 parent 2959d1a commit 83cceac

33 files changed

Lines changed: 7886 additions & 100 deletions

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,9 @@
33
testnet/
44
.vscode/*
55
.nvim.lua
6+
7+
# cargo-fuzz build artifacts + generated corpus / crashes
8+
fuzz/target/
9+
fuzz/corpus/
10+
fuzz/artifacts/
11+
fuzz/coverage/

docs/testing.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,16 @@ Tests a new validator joining by syncing from genesis with no checkpoint.
115115
- **Flow**: First withdraws one validator (modifying the peer set from genesis config), then generates new keys for a joining validator, sends a deposit, creates a `bootstrappers.toml` for peer discovery, and starts node4 with no checkpoint.
116116
- **Verifies**: The new node syncs the entire chain from genesis, catches up to the current epoch, and joins the active validator set.
117117

118+
## Fuzz Testing
119+
120+
Coverage-guided fuzz testing lives in the `fuzz/` crate and runs under nightly Rust via [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz). Three categories:
121+
122+
- **Parser targets** — every `Read` impl in `summit-types`. Arbitrary bytes must parse to `Ok` / `Err` without panicking, and successful decodes roundtrip to byte-identical output.
123+
- **Non-codec targets**`ssz_tree_key::parse_key`, `derive_child_public`, `SszProof::verify` with adversarial inputs.
124+
- **Property-based targets**`WithdrawalQueue` op-sequence invariants, `ExtPrivateKey` sign-verify roundtrip, SSZ incremental-update-vs-rebuild root equality, and generate→verify proof roundtrip across all proof kinds.
125+
126+
See [`fuzz/README.md`](../fuzz/README.md) for the full target list, running instructions, crash reproduction, and how to add new targets.
127+
118128
## Benchmarks
119129

120130
### Consensus State Benchmark

finalizer/src/db.rs

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,9 +341,11 @@ impl<V: Variant> Read for Value<V> {
341341
type Cfg = ();
342342

343343
fn read_cfg(buf: &mut impl Buf, _cfg: &Self::Cfg) -> Result<Self, Error> {
344-
let value_type = buf.get_u8();
344+
let value_type = buf.try_get_u8().map_err(|_| Error::EndOfBuffer)?;
345345
match value_type {
346-
0x01 => Ok(Self::U64(buf.get_u64())),
346+
0x01 => Ok(Self::U64(
347+
buf.try_get_u64().map_err(|_| Error::EndOfBuffer)?,
348+
)),
347349
0x05 => Ok(Self::ConsensusState(Box::new(ConsensusState::read_cfg(
348350
buf,
349351
&(),
@@ -388,6 +390,7 @@ impl<V: Variant> Write for Value<V> {
388390
#[cfg(test)]
389391
mod tests {
390392
use super::*;
393+
use commonware_codec::ReadExt;
391394
use commonware_consensus::simplex::types::{Finalization, Proposal};
392395
use commonware_consensus::types::{Epoch, Round, View};
393396
use commonware_cryptography::bls12381::primitives::{
@@ -405,6 +408,26 @@ mod tests {
405408
use rand::rngs::StdRng;
406409
use summit_types::Block;
407410

411+
#[test]
412+
fn test_value_read_truncated_input_returns_err() {
413+
// Empty buffer — must not panic.
414+
let empty: &[u8] = &[];
415+
assert!(matches!(
416+
Value::<MinPk>::read(&mut empty.as_ref()),
417+
Err(Error::EndOfBuffer)
418+
));
419+
420+
// Tag 0x01 (U64) with 0..8 payload bytes — all truncated.
421+
for n in 0..8 {
422+
let mut buf = vec![0x01u8];
423+
buf.extend(std::iter::repeat_n(0u8, n));
424+
assert!(matches!(
425+
Value::<MinPk>::read(&mut buf.as_ref()),
426+
Err(Error::EndOfBuffer)
427+
));
428+
}
429+
}
430+
408431
async fn create_test_db_with_context<
409432
E: Clock + Storage + Metrics + commonware_runtime::BufferPooler,
410433
V: Variant,

0 commit comments

Comments
 (0)