Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions xcm/xcm-simulator/fuzzer/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
hfuzz_target
hfuzz_workspace
cargo
coverage
ccov.zip
1 change: 1 addition & 0 deletions xcm/xcm-simulator/fuzzer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ edition.workspace = true
[dependencies]
codec = { package = "parity-scale-codec", version = "3.3.0" }
honggfuzz = "0.5.55"
arbitrary = "1.2.0"
scale-info = { version = "2.1.2", features = ["derive"] }

frame-system = { git = "https://github.com/paritytech/substrate", branch = "master" }
Expand Down
38 changes: 38 additions & 0 deletions xcm/xcm-simulator/fuzzer/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# XCM Simulator Fuzzer

This project will fuzz-test the XCM simulator. It can catch reachable panics, timeouts as well as integer overflows and underflows.

## Install dependencies

```
cargo install honggfuzz
```

## Run the fuzzer

In this directory, run this command:

```
cargo hfuzz run xcm-fuzzer
```

## Run a single input

In this directory, run this command:

```
cargo hfuzz run-debug xcm-fuzzer hfuzz_workspace/xcm-fuzzer/fuzzer_input_file
```

## Generate coverage

In this directory, run these four commands:

```
RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort" CARGO_INCREMENTAL=0 SKIP_WASM_BUILD=1 CARGO_HOME=./cargo cargo build
../../../target/debug/xcm-fuzzer hfuzz_workspace/xcm-fuzzer/input/
zip -0 ccov.zip `find ../../../target/ \( -name "*.gc*" -o -name "test-*.gc*" \) -print`
grcov ccov.zip -s ../../../ -t html --llvm --branch --ignore-not-existing -o ./coverage
```

The code coverage will be in `./coverage/index.html`.
143 changes: 105 additions & 38 deletions xcm/xcm-simulator/fuzzer/src/fuzz.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,16 @@ mod parachain;
mod relay_chain;

use codec::DecodeLimit;
use polkadot_core_primitives::AccountId;
use polkadot_parachain::primitives::Id as ParaId;
use sp_runtime::traits::AccountIdConversion;
use xcm_simulator::{decl_test_network, decl_test_parachain, decl_test_relay_chain, TestExt};

use frame_support::assert_ok;
use xcm::{latest::prelude::*, MAX_XCM_DECODE_DEPTH};

pub const ALICE: sp_runtime::AccountId32 = sp_runtime::AccountId32::new([0u8; 32]);
use arbitrary::{Arbitrary, Error, Unstructured};

pub const INITIAL_BALANCE: u128 = 1_000_000_000;

decl_test_parachain! {
Expand All @@ -46,6 +48,15 @@ decl_test_parachain! {
}
}

decl_test_parachain! {
pub struct ParaC {
Runtime = parachain::Runtime,
XcmpMessageHandler = parachain::MsgQueue,
DmpMessageHandler = parachain::MsgQueue,
new_ext = para_ext(3),
}
}

decl_test_relay_chain! {
pub struct Relay {
Runtime = relay_chain::Runtime,
Expand All @@ -60,10 +71,35 @@ decl_test_network! {
parachains = vec![
(1, ParaA),
(2, ParaB),
(3, ParaC),
],
}
}

// An XCM message that will be generated by the fuzzer through the Arbitrary trait
struct XcmMessage {
// Source chain
source: u32,
// Destination chain
destination: u32,
// XCM message
message: Xcm<()>,
}

impl<'a> Arbitrary<'a> for XcmMessage {
fn arbitrary(u: &mut Unstructured<'a>) -> Result<Self, Error> {
let source: u32 = u.arbitrary()?;
let destination: u32 = u.arbitrary()?;
let mut encoded_message: &[u8] = u.arbitrary()?;
if let Ok(message) =
DecodeLimit::decode_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut encoded_message)
{
return Ok(XcmMessage { source, destination, message })
}
Err(Error::IncorrectFormat)
}
}

pub fn para_account_id(id: u32) -> relay_chain::AccountId {
ParaId::from(id).into_account_truncating()
}
Expand All @@ -73,9 +109,11 @@ pub fn para_ext(para_id: u32) -> sp_io::TestExternalities {

let mut t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();

pallet_balances::GenesisConfig::<Runtime> { balances: vec![(ALICE, INITIAL_BALANCE)] }
.assimilate_storage(&mut t)
.unwrap();
pallet_balances::GenesisConfig::<Runtime> {
balances: (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect(),
}
.assimilate_storage(&mut t)
.unwrap();

let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
Expand All @@ -90,11 +128,13 @@ pub fn relay_ext() -> sp_io::TestExternalities {

let mut t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();

pallet_balances::GenesisConfig::<Runtime> {
balances: vec![(ALICE, INITIAL_BALANCE), (para_account_id(1), INITIAL_BALANCE)],
}
.assimilate_storage(&mut t)
.unwrap();
let mut balances: Vec<(AccountId, u128)> = vec![];
balances.append(&mut (1..=3).map(|i| (para_account_id(i), INITIAL_BALANCE)).collect());
balances.append(&mut (0..6).map(|i| ([i; 32].into(), INITIAL_BALANCE)).collect());

pallet_balances::GenesisConfig::<Runtime> { balances }
.assimilate_storage(&mut t)
.unwrap();

let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| System::set_block_number(1));
Expand All @@ -104,46 +144,70 @@ pub fn relay_ext() -> sp_io::TestExternalities {
pub type RelayChainPalletXcm = pallet_xcm::Pallet<relay_chain::Runtime>;
pub type ParachainPalletXcm = pallet_xcm::Pallet<parachain::Runtime>;

fn run_one_input(mut data: &[u8]) {
fn run_input(xcm_messages: [XcmMessage; 5]) {
MockNet::reset();
if let Ok(m) = Xcm::decode_all_with_depth_limit(MAX_XCM_DECODE_DEPTH, &mut data) {
#[cfg(not(fuzzing))]
{
println!("Executing message {:?}", m);

#[cfg(not(fuzzing))]
println!();

for xcm_message in xcm_messages {
if xcm_message.source % 4 == 0 {
// We get the destination for the message
let parachain_id = (xcm_message.destination % 3) + 1;
let destination: MultiLocation = Parachain(parachain_id).into();
#[cfg(not(fuzzing))]
{
println!(" source: Relay Chain");
println!(" destination: Parachain {parachain_id}");
println!(" message: {:?}", xcm_message.message);
}
Relay::execute_with(|| {
assert_ok!(RelayChainPalletXcm::send_xcm(Here, destination, xcm_message.message));
})
} else {
// We get the source's execution method
let execute_with = match xcm_message.source % 4 {
1 => ParaA::execute_with,
2 => ParaB::execute_with,
_ => ParaC::execute_with,
};
// We get the destination for the message
let destination: MultiLocation = match xcm_message.destination % 4 {
n @ 1..=3 => (Parent, Parachain(n)).into(),
_ => Parent.into(),
};
#[cfg(not(fuzzing))]
{
let destination_str = match xcm_message.destination % 4 {
n @ 1..=3 => format!("Parachain {n}"),
_ => "Relay Chain".to_string(),
};
println!(" source: Parachain {}", xcm_message.source % 4);
println!(" destination: {}", destination_str);
println!(" message: {:?}", xcm_message.message);
}
// We execute the message with the appropriate source and destination
execute_with(|| {
assert_ok!(ParachainPalletXcm::send_xcm(Here, destination, xcm_message.message));
});
}
ParaA::execute_with(|| {
assert_ok!(ParachainPalletXcm::send_xcm(Here, Parent, m));
});
Relay::execute_with(|| {});
#[cfg(not(fuzzing))]
println!();
}
Relay::execute_with(|| {});
}

fn main() {
#[cfg(fuzzing)]
{
loop {
honggfuzz::fuzz!(|data: &[u8]| {
run_one_input(data);
});
honggfuzz::fuzz!(|xcm_messages: [XcmMessage; 5]| {
run_input(xcm_messages);
})
}
}
#[cfg(not(fuzzing))]
{
//This code path can be used to generate a line-code coverage report in HTML
//that depicts which lines are executed by at least one input in the current fuzzing queue.
//To generate this code coverage report, run the following commands:
/*
```
export CARGO_INCREMENTAL=0
export RUSTFLAGS="-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort"
export RUSTDOCFLAGS="-Cpanic=abort"
rustup override set nightly
SKIP_WASM_BUILD=1 cargo build
./xcm/xcm-simulator/fuzzer/target/debug/xcm-fuzzer hfuzz_workspace/xcm-fuzzer/input
zip -0 ccov.zip `find ../../target/debug \( -name "*.gc*" -o -name "test-*.gc*" \) -print`
grcov ccov.zip -s / -t html --llvm --branch --ignore-not-existing -o ../../target/debug/coverage/
```
*/
use std::{env, fs, fs::File, io::Read};
let args: Vec<_> = env::args().collect();
let md = fs::metadata(&args[1]).unwrap();
Expand All @@ -152,15 +216,18 @@ fn main() {
.unwrap()
.map(|x| x.unwrap().path().to_str().unwrap().to_string())
.collect::<Vec<String>>(),
false => (&args[1..]).to_vec(),
false => (args[1..]).to_vec(),
};
println!("All_files {:?}", all_files);
for argument in all_files {
println!("Now doing file {:?}", argument);
let mut buffer: Vec<u8> = Vec::new();
let mut f = File::open(argument).unwrap();
f.read_to_end(&mut buffer).unwrap();
run_one_input(&buffer.as_slice());
let mut unstructured = Unstructured::new(&buffer);
if let Ok(xcm_messages) = unstructured.arbitrary() {
run_input(xcm_messages);
}
}
}
}