Skip to content

Commit

Permalink
Merge pull request #279 from Chia-Network/streamable-fuzzing
Browse files Browse the repository at this point in the history
add fuzzer for streamable
  • Loading branch information
arvidn authored Oct 13, 2023
2 parents b0716b5 + d1ffb46 commit 012fb16
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 10 deletions.
19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions chia-bls/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ hex = "0.4.3"
thiserror = "1.0.44"
pyo3 = { version = ">=0.19.0", features = ["multiple-pymethods"], optional = true }
chia_py_streamable_macro = { version = "0.1.3", path = "../chia_py_streamable_macro", optional = true }
arbitrary = { version = "=1.3.0" }

[dev-dependencies]
rand = "0.8.5"
Expand Down
8 changes: 8 additions & 0 deletions chia-bls/src/public_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ use pyo3::{pyclass, pymethods, IntoPy, PyAny, PyObject, PyResult, Python};
#[derive(Clone, Default)]
pub struct PublicKey(pub(crate) blst_p1);

#[cfg(fuzzing)]
impl<'a> arbitrary::Arbitrary<'a> for PublicKey {
fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
// placeholder
Ok(Self::default())
}
}

impl PublicKey {
pub fn from_bytes_unchecked(bytes: &[u8; 48]) -> Result<Self> {
// check if the element is canonical
Expand Down
9 changes: 9 additions & 0 deletions chia-bls/src/secret_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ use pyo3::{pyclass, pymethods, IntoPy, PyAny, PyObject, PyResult, Python};
#[derive(PartialEq, Eq, Clone)]
pub struct SecretKey(pub(crate) blst_scalar);

#[cfg(fuzzing)]
impl<'a> arbitrary::Arbitrary<'a> for SecretKey {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
let mut seed = [0_u8; 32];
let _ = u.fill_buffer(seed.as_mut_slice());
Ok(Self::from_seed(&seed))
}
}

fn flip_bits(input: [u8; 32]) -> [u8; 32] {
let mut ret = [0; 32];
for i in 0..32 {
Expand Down
8 changes: 8 additions & 0 deletions chia-bls/src/signature.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ pub const DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_AUG_";
#[derive(Clone, Default)]
pub struct Signature(pub(crate) blst_p2);

#[cfg(fuzzing)]
impl<'a> arbitrary::Arbitrary<'a> for Signature {
fn arbitrary(_u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
// placeholder
Ok(Self::default())
}
}

impl Signature {
pub fn from_bytes_unchecked(buf: &[u8; 96]) -> Result<Self> {
let p2 = unsafe {
Expand Down
1 change: 1 addition & 0 deletions chia-protocol/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ clvmr = "0.3.0"
chia-traits = { version = "0.1.0", path = "../chia-traits" }
clvm-traits = { version = "0.1.0", path = "../clvm-traits", features = ["derive"] }
chia-bls = { version = "0.2.7", path = "../chia-bls" }
arbitrary = { version = "=1.3.0", features = ["derive"] }

[dev-dependencies]
rstest = "0.17.0"
Expand Down
10 changes: 10 additions & 0 deletions chia-protocol/fuzz/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ cargo-fuzz = true
[dependencies]
libfuzzer-sys = "0.4"
chia-traits = { path = "../../chia-traits" }
arbitrary = { version = "=1.3.0" }
sha2 = "0.9.9"
hex = "0.4.3"

[dependencies.chia-protocol]
path = ".."
Expand Down Expand Up @@ -49,3 +52,10 @@ path = "fuzz_targets/parse-program.rs"
test = false
doc = false
bench = false

[[bin]]
name = "streamable"
path = "fuzz_targets/streamable.rs"
test = false
doc = false
bench = false
3 changes: 1 addition & 2 deletions chia-protocol/fuzz/fuzz_targets/parse-foliage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
use chia_protocol::Foliage;
use chia_traits::Streamable;
use libfuzzer_sys::fuzz_target;
use std::io::Cursor;

fuzz_target!(|data: &[u8]| {
let _ret = <Foliage as Streamable>::parse(&mut Cursor::<&[u8]>::new(data));
let _ = Foliage::from_bytes(data);
});
3 changes: 1 addition & 2 deletions chia-protocol/fuzz/fuzz_targets/parse-full-block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
use chia_protocol::FullBlock;
use chia_traits::Streamable;
use libfuzzer_sys::fuzz_target;
use std::io::Cursor;

fuzz_target!(|data: &[u8]| {
let _ret = <FullBlock as Streamable>::parse(&mut Cursor::<&[u8]>::new(data));
let _ = FullBlock::from_bytes(data);
});
3 changes: 1 addition & 2 deletions chia-protocol/fuzz/fuzz_targets/parse-header-block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
use chia_protocol::HeaderBlock;
use chia_traits::Streamable;
use libfuzzer_sys::fuzz_target;
use std::io::Cursor;

fuzz_target!(|data: &[u8]| {
let _ret = <HeaderBlock as Streamable>::parse(&mut Cursor::<&[u8]>::new(data));
let _ = HeaderBlock::from_bytes(data);
});
3 changes: 1 addition & 2 deletions chia-protocol/fuzz/fuzz_targets/parse-program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
use chia_protocol::Program;
use chia_traits::Streamable;
use libfuzzer_sys::fuzz_target;
use std::io::Cursor;

fuzz_target!(|data: &[u8]| {
let _ret = <Program as Streamable>::parse(&mut Cursor::<&[u8]>::new(data));
let _ = Program::from_bytes(data);
});
3 changes: 1 addition & 2 deletions chia-protocol/fuzz/fuzz_targets/parse-tx-info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
use chia_protocol::TransactionsInfo;
use chia_traits::Streamable;
use libfuzzer_sys::fuzz_target;
use std::io::Cursor;

fuzz_target!(|data: &[u8]| {
let _ret = <TransactionsInfo as Streamable>::parse(&mut Cursor::<&[u8]>::new(data));
let _ = TransactionsInfo::from_bytes(data);
});
109 changes: 109 additions & 0 deletions chia-protocol/fuzz/fuzz_targets/streamable.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#![no_main]
use ::chia_protocol::*;
use chia_traits::Streamable;
use libfuzzer_sys::fuzz_target;
use sha2::{Digest, Sha256};

#[cfg(fuzzing)]
use arbitrary::{Arbitrary, Unstructured};

pub fn test_streamable<T: Streamable + std::fmt::Debug + PartialEq>(obj: &T) {
let bytes = obj.to_bytes().unwrap();
let obj2 = match T::from_bytes(&bytes) {
Err(_) => {
panic!(
"failed to parse input: {}, from object: {:?}",
hex::encode(bytes),
&obj
);
}
Ok(o) => o,
};
assert_eq!(obj, &obj2);

let mut ctx = Sha256::new();
ctx.update(bytes);
let expect_hash: [u8; 32] = ctx.finalize().into();
assert_eq!(obj.hash(), expect_hash);
}
#[cfg(fuzzing)]
fn test<'a, T: Arbitrary<'a> + Streamable + std::fmt::Debug + PartialEq>(data: &'a [u8]) {
let mut u = Unstructured::new(data);
let obj = <T as Arbitrary<'a>>::arbitrary(&mut u).unwrap();
test_streamable(&obj);
}

// this is here to make clippy happy
#[cfg(not(fuzzing))]
fn test<T: Streamable + std::fmt::Debug + PartialEq>(_data: &[u8]) {}

fuzz_target!(|data: &[u8]| {
test::<Program>(data);
test::<Message>(data);
test::<ClassgroupElement>(data);
test::<Coin>(data);
test::<CoinSpend>(data);
test::<CoinState>(data);
test::<EndOfSubSlotBundle>(data);
test::<FeeRate>(data);
test::<FeeEstimate>(data);
test::<FeeEstimateGroup>(data);
test::<TransactionsInfo>(data);
test::<FoliageTransactionBlock>(data);
test::<FoliageBlockData>(data);
test::<Foliage>(data);
test::<FullBlock>(data);
test::<HeaderBlock>(data);
test::<PoolTarget>(data);
test::<ProofOfSpace>(data);
test::<RewardChainBlockUnfinished>(data);
test::<RewardChainBlock>(data);
test::<ChallengeBlockInfo>(data);
test::<ChallengeChainSubSlot>(data);
test::<InfusedChallengeChainSubSlot>(data);
test::<RewardChainSubSlot>(data);
test::<SubSlotProofs>(data);
test::<SpendBundle>(data);
test::<VDFInfo>(data);
test::<VDFProof>(data);
test::<PuzzleSolutionResponse>(data);
test::<SubSlotData>(data);
test::<SubEpochChallengeSegment>(data);
test::<SubEpochSegments>(data);

test::<Handshake>(data);

// Wallet Protocol
test::<RequestPuzzleSolution>(data);
test::<RespondPuzzleSolution>(data);
test::<RejectPuzzleSolution>(data);
test::<SendTransaction>(data);
test::<TransactionAck>(data);
test::<NewPeakWallet>(data);
test::<RequestBlockHeader>(data);
test::<RespondBlockHeader>(data);
test::<RejectHeaderRequest>(data);
test::<RequestRemovals>(data);
test::<RespondRemovals>(data);
test::<RejectRemovalsRequest>(data);
test::<RequestAdditions>(data);
test::<RespondAdditions>(data);
test::<RejectAdditionsRequest>(data);
test::<RespondBlockHeaders>(data);
test::<RejectBlockHeaders>(data);
test::<RequestBlockHeaders>(data);
test::<RequestHeaderBlocks>(data);
test::<RejectHeaderBlocks>(data);
test::<RespondHeaderBlocks>(data);
test::<RegisterForPhUpdates>(data);
test::<RespondToPhUpdates>(data);
test::<RegisterForCoinUpdates>(data);
test::<RespondToCoinUpdates>(data);
test::<CoinStateUpdate>(data);
test::<RequestChildren>(data);
test::<RespondChildren>(data);
test::<RequestSesInfo>(data);
test::<RespondSesInfo>(data);
test::<RequestFeeEstimates>(data);
test::<RespondFeeEstimates>(data);
});
2 changes: 2 additions & 0 deletions chia-protocol/src/bytes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use pyo3::prelude::*;
use pyo3::types::PyBytes;

#[derive(Hash, Clone, Eq, PartialEq, PartialOrd, Ord)]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
pub struct Bytes(Vec<u8>);

impl Bytes {
Expand Down Expand Up @@ -183,6 +184,7 @@ impl fmt::Display for Bytes {
}

#[derive(Hash, PartialEq, Eq, Copy, Clone, PartialOrd, Ord)]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
pub struct BytesImpl<const N: usize>([u8; N]);

impl<const N: usize> Streamable for BytesImpl<N> {
Expand Down
2 changes: 2 additions & 0 deletions chia-protocol/src/chia_protocol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use chia_py_streamable_macro::{PyJsonDict, PyStreamable};

#[repr(u8)]
#[cfg_attr(feature = "py-bindings", derive(PyJsonDict, PyStreamable))]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
#[derive(Streamable, Hash, Debug, Copy, Clone, Eq, PartialEq)]
pub enum ProtocolMessageTypes {
// Shared protocol (all services)
Expand Down Expand Up @@ -128,6 +129,7 @@ pub trait ChiaProtocolMessage {

#[repr(u8)]
#[cfg_attr(feature = "py-bindings", derive(PyJsonDict, PyStreamable))]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
#[derive(Streamable, Hash, Debug, Copy, Clone, Eq, PartialEq)]
pub enum NodeType {
FullNode = 1,
Expand Down
2 changes: 2 additions & 0 deletions chia-protocol/src/message_struct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ macro_rules! message_struct {
($name:ident {$($field:ident: $t:ty $(,)? )*}) => {
#[cfg_attr(feature = "py-bindings", pyo3::pyclass(get_all, frozen), derive(chia_py_streamable_macro::PyJsonDict, chia_py_streamable_macro::PyStreamable))]
#[derive(Streamable, Hash, Debug, Clone, Eq, PartialEq)]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
pub struct $name {
$(pub $field: $t),*
}
Expand All @@ -28,6 +29,7 @@ macro_rules! streamable_struct {
($name:ident {$($field:ident: $t:ty $(,)? )*}) => {
#[cfg_attr(feature = "py-bindings", pyo3::pyclass(get_all, frozen), derive(chia_py_streamable_macro::PyJsonDict, chia_py_streamable_macro::PyStreamable))]
#[derive(Streamable, Hash, Debug, Clone, Eq, PartialEq)]
#[cfg_attr(fuzzing, derive(arbitrary::Arbitrary))]
pub struct $name {
$(pub $field: $t),*
}
Expand Down
24 changes: 24 additions & 0 deletions chia-protocol/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ use pyo3::prelude::*;
#[derive(Hash, Debug, Clone, Eq, PartialEq)]
pub struct Program(Bytes);

#[cfg(fuzzing)]
impl<'a> arbitrary::Arbitrary<'a> for Program {
fn arbitrary(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result<Self> {
// generate an arbitrary CLVM structure. Not likely a valid program.
let mut items_left = 1;
let mut total_items = 0;
let mut buf = Vec::<u8>::with_capacity(200);

while items_left > 0 {
if total_items < 100 && u.ratio(1, 4).unwrap() {
// make a pair
buf.push(0xff);
items_left += 2;
} else {
// make an atom. just single bytes for now
buf.push(u.int_in_range(0..=0x80).unwrap());
}
total_items += 1;
items_left -= 1;
}
Ok(Self(buf.into()))
}
}

impl Program {
pub fn len(&self) -> usize {
self.0.len()
Expand Down
Loading

0 comments on commit 012fb16

Please sign in to comment.