diff --git a/.github/workflows/generic-weekly-fuzzer.yml b/.github/workflows/generic-weekly-fuzzer.yml new file mode 100644 index 0000000..f5802b1 --- /dev/null +++ b/.github/workflows/generic-weekly-fuzzer.yml @@ -0,0 +1,37 @@ +name: Generic Template Fuzzer (Weekly run) + +on: + schedule: + # Runs at 00:00 UTC every Sunday + - cron: '0 0 * * 0' + +jobs: + generic-template-fuzzer: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + + - name: Add dependencies + run: cargo install ziggy cargo-afl honggfuzz grcov + + - name: Add target + run: rustup target add wasm32-unknown-unknown + + - name: Run Ziggy Fuzzing + run: | + timeout 5h cargo ziggy fuzz -t 20 || true + working-directory: generic-template/template-fuzzer + + - name: Save Artifacts + uses: actions/upload-artifact@v2 + with: + name: fuzzing-artifacts + path: | + generic-template/template-fuzzer/output \ No newline at end of file diff --git a/evm-template/Cargo.lock b/evm-template/Cargo.lock index 33a4fa4..a28d37e 100644 --- a/evm-template/Cargo.lock +++ b/evm-template/Cargo.lock @@ -13993,7 +13993,13 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", + "pallet-collator-selection", + "pallet-multisig", + "pallet-scheduler", + "pallet-sudo", "pallet-timestamp", + "pallet-utility", + "pallet-whitelist", "parachains-common", "parity-scale-codec", "sp-consensus-aura", diff --git a/evm-template/template-fuzzer/Cargo.toml b/evm-template/template-fuzzer/Cargo.toml index 1afc4b8..e4a10a9 100644 --- a/evm-template/template-fuzzer/Cargo.toml +++ b/evm-template/template-fuzzer/Cargo.toml @@ -31,7 +31,13 @@ sp-runtime = { workspace = true } sp-state-machine = { workspace = true } pallet-balances = { workspace = true } +pallet-collator-selection = { workspace = true } +pallet-multisig = { workspace = true } +pallet-scheduler = { workspace = true } +pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } +pallet-utility = { workspace = true } +pallet-whitelist = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-primitives-core = { workspace = true } @@ -43,7 +49,13 @@ default = [ "std", "try-runtime" ] std = [ "evm-runtime-template/std", "frame-support/std", + "pallet-collator-selection/std", + "pallet-multisig/std", + "pallet-scheduler/std", + "pallet-sudo/std", "pallet-timestamp/std", + "pallet-utility/std", + "pallet-whitelist/std", "parity-scale-codec/std", "sp-consensus-aura/std", "sp-runtime/std", @@ -52,7 +64,13 @@ try-runtime = [ "evm-runtime-template/try-runtime", "frame-support/try-runtime", "frame-system/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-multisig/try-runtime", + "pallet-scheduler/try-runtime", + "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", + "pallet-utility/try-runtime", + "pallet-whitelist/try-runtime", "sp-runtime/try-runtime", ] diff --git a/evm-template/template-fuzzer/result-scanner.sh b/evm-template/template-fuzzer/result-scanner.sh new file mode 100755 index 0000000..4e1a75b --- /dev/null +++ b/evm-template/template-fuzzer/result-scanner.sh @@ -0,0 +1,7 @@ +#!/bin/zsh + +data=0; +for file in output/template-fuzzer/crashes/**/*(.); do + cargo ziggy run -i $file 2>output/$data.err 1>output/$data.out + data=$((data+1)) +done diff --git a/evm-template/template-fuzzer/src/main.rs b/evm-template/template-fuzzer/src/main.rs index 1ec71d2..c13eaec 100644 --- a/evm-template/template-fuzzer/src/main.rs +++ b/evm-template/template-fuzzer/src/main.rs @@ -2,9 +2,9 @@ use std::time::{Duration, Instant}; use cumulus_primitives_core::relay_chain::Slot; use evm_runtime_template::{ - constants::SLOT_DURATION, AccountId, AllPalletsWithSystem, Balance, Balances, BlockNumber, - EVMChainIdConfig, Executive, Runtime, RuntimeCall, RuntimeOrigin, SudoConfig, - UncheckedExtrinsic, + configs::MaxCandidates, constants::SLOT_DURATION, AccountId, AllPalletsWithSystem, Balance, + Balances, BlockNumber, EVMChainIdConfig, Executive, Runtime, RuntimeCall, RuntimeOrigin, + SudoConfig, UncheckedExtrinsic, }; use frame_support::{ dispatch::GetDispatchInfo, @@ -173,6 +173,10 @@ fn main() { externalities.execute_with(|| start_block(current_block, 0)); for (maybe_lapse, origin, extrinsic) in extrinsics { + let origin_no = origin % endowed_accounts.len(); + if !recursive_call_filter(&extrinsic, origin_no) { + continue; + } // If the lapse is in the range [0, MAX_BLOCK_LAPSE] we finalize the block and initialize // a new one. if let Some(lapse) = maybe_lapse { @@ -201,7 +205,7 @@ fn main() { } externalities.execute_with(|| { - let origin_account = endowed_accounts[origin % endowed_accounts.len()]; + let origin_account = endowed_accounts[origin_no]; { println!("\n origin: {origin_account:?}"); println!(" call: {extrinsic:?}"); @@ -287,3 +291,71 @@ fn main() { }); }); } + +fn recursive_call_filter(call: &RuntimeCall, origin: usize) -> bool { + match call { + //recursion + RuntimeCall::Sudo( + pallet_sudo::Call::sudo { call } + | pallet_sudo::Call::sudo_unchecked_weight { call, weight: _ }, + ) if origin == 0 => recursive_call_filter(call, origin), + RuntimeCall::Utility( + pallet_utility::Call::with_weight { call, weight: _ } + | pallet_utility::Call::dispatch_as { as_origin: _, call } + | pallet_utility::Call::as_derivative { index: _, call }, + ) => recursive_call_filter(call, origin), + RuntimeCall::Utility( + pallet_utility::Call::force_batch { calls } + | pallet_utility::Call::batch { calls } + | pallet_utility::Call::batch_all { calls }, + ) => calls.iter().map(|call| recursive_call_filter(call, origin)).all(|e| e), + RuntimeCall::Scheduler( + pallet_scheduler::Call::schedule_named_after { + id: _, + after: _, + maybe_periodic: _, + priority: _, + call, + } + | pallet_scheduler::Call::schedule { when: _, maybe_periodic: _, priority: _, call } + | pallet_scheduler::Call::schedule_named { + when: _, + id: _, + maybe_periodic: _, + priority: _, + call, + } + | pallet_scheduler::Call::schedule_after { + after: _, + maybe_periodic: _, + priority: _, + call, + }, + ) => recursive_call_filter(call, origin), + RuntimeCall::Multisig( + pallet_multisig::Call::as_multi_threshold_1 { other_signatories: _, call } + | pallet_multisig::Call::as_multi { + threshold: _, + other_signatories: _, + maybe_timepoint: _, + call, + max_weight: _, + }, + ) => recursive_call_filter(call, origin), + RuntimeCall::Whitelist( + pallet_whitelist::Call::dispatch_whitelisted_call_with_preimage { call }, + ) => recursive_call_filter(call, origin), + + // restrictions + RuntimeCall::Sudo(_) if origin != 0 => false, + RuntimeCall::System( + frame_system::Call::set_code { .. } | frame_system::Call::kill_prefix { .. }, + ) => false, + RuntimeCall::CollatorSelection( + pallet_collator_selection::Call::set_desired_candidates { max }, + ) => *max < MaxCandidates::get(), + RuntimeCall::Balances(pallet_balances::Call::force_adjust_total_issuance { .. }) => false, + + _ => true, + } +} diff --git a/generic-template/Cargo.lock b/generic-template/Cargo.lock index 2e8e706..74f19c8 100644 --- a/generic-template/Cargo.lock +++ b/generic-template/Cargo.lock @@ -13075,7 +13075,13 @@ dependencies = [ "frame-system", "generic-runtime-template", "pallet-balances", + "pallet-collator-selection", + "pallet-multisig", + "pallet-scheduler", + "pallet-sudo", "pallet-timestamp", + "pallet-utility", + "pallet-whitelist", "parachains-common", "parity-scale-codec", "sp-consensus-aura", diff --git a/generic-template/template-fuzzer/Cargo.toml b/generic-template/template-fuzzer/Cargo.toml index 21301cf..a7e5bfb 100644 --- a/generic-template/template-fuzzer/Cargo.toml +++ b/generic-template/template-fuzzer/Cargo.toml @@ -31,7 +31,13 @@ sp-runtime = { workspace = true } sp-state-machine = { workspace = true } pallet-balances = { workspace = true } +pallet-collator-selection = { workspace = true } +pallet-multisig = { workspace = true } +pallet-scheduler = { workspace = true } +pallet-sudo = { workspace = true } pallet-timestamp = { workspace = true } +pallet-utility = { workspace = true } +pallet-whitelist = { workspace = true } cumulus-pallet-parachain-system = { workspace = true } cumulus-primitives-core = { workspace = true } @@ -43,16 +49,29 @@ default = [ "std", "try-runtime" ] std = [ "frame-support/std", "generic-runtime-template/std", + "pallet-collator-selection/std", + "pallet-multisig/std", + "pallet-scheduler/std", + "pallet-sudo/std", "pallet-timestamp/std", + "pallet-utility/std", + "pallet-whitelist/std", "parity-scale-codec/std", "sp-consensus-aura/std", "sp-runtime/std", ] +fuzzing = [] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "generic-runtime-template/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-multisig/try-runtime", + "pallet-scheduler/try-runtime", + "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", + "pallet-utility/try-runtime", + "pallet-whitelist/try-runtime", "sp-runtime/try-runtime", ] diff --git a/generic-template/template-fuzzer/result-scanner.sh b/generic-template/template-fuzzer/result-scanner.sh new file mode 100755 index 0000000..4e1a75b --- /dev/null +++ b/generic-template/template-fuzzer/result-scanner.sh @@ -0,0 +1,7 @@ +#!/bin/zsh + +data=0; +for file in output/template-fuzzer/crashes/**/*(.); do + cargo ziggy run -i $file 2>output/$data.err 1>output/$data.out + data=$((data+1)) +done diff --git a/generic-template/template-fuzzer/src/main.rs b/generic-template/template-fuzzer/src/main.rs index 22f7500..5573334 100644 --- a/generic-template/template-fuzzer/src/main.rs +++ b/generic-template/template-fuzzer/src/main.rs @@ -8,8 +8,8 @@ use frame_support::{ weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; use generic_runtime_template::{ - constants::SLOT_DURATION, AllPalletsWithSystem, Balance, Balances, BlockNumber, Executive, - Runtime, RuntimeCall, RuntimeOrigin, SudoConfig, UncheckedExtrinsic, + configs::MaxCandidates, constants::SLOT_DURATION, AllPalletsWithSystem, Balance, Balances, + BlockNumber, Executive, Runtime, RuntimeCall, RuntimeOrigin, SudoConfig, UncheckedExtrinsic, }; use parachains_common::AccountId; use sp_consensus_aura::AURA_ENGINE_ID; @@ -168,6 +168,10 @@ fn main() { for (maybe_lapse, origin, extrinsic) in extrinsics { // If the lapse is in the range [0, MAX_BLOCK_LAPSE] we finalize the block and initialize // a new one. + let origin_no = origin % endowed_accounts.len(); + if !recursive_call_filter(&extrinsic, origin_no) { + continue; + } if let Some(lapse) = maybe_lapse { // We update our state variables current_weight = Weight::zero(); @@ -194,7 +198,7 @@ fn main() { } externalities.execute_with(|| { - let origin_account = endowed_accounts[origin % endowed_accounts.len()].clone(); + let origin_account = endowed_accounts[origin_no].clone(); { println!("\n origin: {origin_account:?}"); println!(" call: {extrinsic:?}"); @@ -280,3 +284,74 @@ fn main() { }); }); } + +fn recursive_call_filter(call: &RuntimeCall, origin: usize) -> bool { + match call { + //recursion + RuntimeCall::Sudo( + pallet_sudo::Call::sudo { call } + | pallet_sudo::Call::sudo_unchecked_weight { call, weight: _ }, + ) if origin == 0 => recursive_call_filter(call, origin), + RuntimeCall::Utility( + pallet_utility::Call::with_weight { call, weight: _ } + | pallet_utility::Call::dispatch_as { as_origin: _, call } + | pallet_utility::Call::as_derivative { index: _, call }, + ) => recursive_call_filter(call, origin), + RuntimeCall::Utility( + pallet_utility::Call::force_batch { calls } + | pallet_utility::Call::batch { calls } + | pallet_utility::Call::batch_all { calls }, + ) => calls + .iter() + .map(|call| recursive_call_filter(call, origin)) + .all(|e| e), + RuntimeCall::Scheduler( + pallet_scheduler::Call::schedule_named_after { + id: _, + after: _, + maybe_periodic: _, + priority: _, + call, + } + | pallet_scheduler::Call::schedule { when: _, maybe_periodic: _, priority: _, call } + | pallet_scheduler::Call::schedule_named { + when: _, + id: _, + maybe_periodic: _, + priority: _, + call, + } + | pallet_scheduler::Call::schedule_after { + after: _, + maybe_periodic: _, + priority: _, + call, + }, + ) => recursive_call_filter(call, origin), + RuntimeCall::Multisig( + pallet_multisig::Call::as_multi_threshold_1 { other_signatories: _, call } + | pallet_multisig::Call::as_multi { + threshold: _, + other_signatories: _, + maybe_timepoint: _, + call, + max_weight: _, + }, + ) => recursive_call_filter(call, origin), + RuntimeCall::Whitelist( + pallet_whitelist::Call::dispatch_whitelisted_call_with_preimage { call }, + ) => recursive_call_filter(call, origin), + + // restrictions + RuntimeCall::Sudo(_) if origin != 0 => false, + RuntimeCall::System( + frame_system::Call::set_code { .. } | frame_system::Call::kill_prefix { .. }, + ) => false, + RuntimeCall::CollatorSelection( + pallet_collator_selection::Call::set_desired_candidates { max }, + ) => *max < MaxCandidates::get(), + RuntimeCall::Balances(pallet_balances::Call::force_adjust_total_issuance { .. }) => false, + + _ => true, + } +}