Substrate Vulnerability Scanner
- Purpose
Systematically scan Substrate runtime modules (pallets) for platform-specific security vulnerabilities that can cause node crashes, DoS attacks, or unauthorized access. This skill encodes 7 critical vulnerability patterns unique to Substrate/FRAME-based chains.
- When to Use This Skill
-
Auditing custom Substrate pallets
-
Reviewing FRAME runtime code
-
Pre-launch security assessment of Substrate chains (Polkadot parachains, standalone chains)
-
Validating dispatchable extrinsic functions
-
Reviewing weight calculation functions
-
Assessing unsigned transaction validation logic
- Platform Detection
File Extensions & Indicators
- Rust files: .rs
Language/Framework Markers
// Substrate/FRAME indicators #[pallet] pub mod pallet { use frame_support::pallet_prelude::; use frame_system::pallet_prelude::;
#[pallet::config]
pub trait Config: frame_system::Config { }
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(10_000)]
pub fn example_function(origin: OriginFor<T>) -> DispatchResult { }
}
}
// Common patterns DispatchResult, DispatchError ensure!, ensure_signed, ensure_root StorageValue, StorageMap, StorageDoubleMap #[pallet::storage] #[pallet::call] #[pallet::weight] #[pallet::validate_unsigned]
Project Structure
-
pallets/*/lib.rs
-
Pallet implementations
-
runtime/lib.rs
-
Runtime configuration
-
benchmarking.rs
-
Weight benchmarks
-
Cargo.toml with frame-* dependencies
Tool Support
-
cargo-fuzz: Fuzz testing for Rust
-
test-fuzz: Property-based testing framework
-
benchmarking framework: Built-in weight calculation
-
try-runtime: Runtime migration testing
- How This Skill Works
When invoked, I will:
-
Search your codebase for Substrate pallets
-
Analyze each pallet for the 7 vulnerability patterns
-
Report findings with file references and severity
-
Provide fixes for each identified issue
-
Check weight calculations and origin validation
- Vulnerability Patterns (7 Critical Patterns)
I check for 7 critical vulnerability patterns unique to Substrate/FRAME. For detailed detection patterns, code examples, mitigations, and testing strategies, see VULNERABILITY_PATTERNS.md.
Pattern Summary:
Arithmetic Overflow ⚠️ CRITICAL
-
Direct + , - , * , / operators wrap in release mode
-
Must use checked_* or saturating_* methods
-
Affects balance/token calculations, reward/fee math
Don't Panic ⚠️ CRITICAL - DoS
-
Panics cause node to stop processing blocks
-
No unwrap() , expect() , array indexing without bounds check
-
All user input must be validated with ensure!
Weights and Fees ⚠️ CRITICAL - DoS
-
Incorrect weights allow spam attacks
-
Fixed weights for variable-cost operations enable DoS
-
Must use benchmarking framework, bound all input parameters
Verify First, Write Last ⚠️ HIGH (Pre-v0.9.25)
-
Storage writes before validation persist on error (pre-v0.9.25)
-
Pattern: validate → write → emit event
-
Upgrade to v0.9.25+ or use manual #[transactional]
Unsigned Transaction Validation ⚠️ HIGH
-
Insufficient validation allows spam/replay attacks
-
Prefer signed transactions
-
If unsigned: validate parameters, replay protection, authenticate source
Bad Randomness ⚠️ MEDIUM
-
pallet_randomness_collective_flip vulnerable to collusion
-
Must use BABE randomness (pallet_babe::RandomnessFromOneEpochAgo )
-
Use random(subject) not random_seed()
Bad Origin ⚠️ CRITICAL
-
ensure_signed allows any user for privileged operations
-
Must use ensure_root or custom origins (ForceOrigin, AdminOrigin)
-
Origin types must be properly configured in runtime
For complete vulnerability patterns with code examples, see VULNERABILITY_PATTERNS.md.
- Scanning Workflow
Step 1: Platform Identification
-
Verify Substrate/FRAME framework usage
-
Check Substrate version (v0.9.25+ has transactional storage)
-
Locate pallet implementations (pallets/*/lib.rs )
-
Identify runtime configuration (runtime/lib.rs )
Step 2: Dispatchable Analysis
For each #[pallet::call] function:
-
Arithmetic: Uses checked/saturating operations?
-
Panics: No unwrap/expect/indexing?
-
Weights: Proportional to cost, bounded inputs?
-
Origin: Appropriate validation level?
-
Validation: All checks before storage writes?
Step 3: Panic Sweep
Search for panic-prone patterns
rg "unwrap()" pallets/ rg "expect(" pallets/ rg "[.*]" pallets/ # Array indexing rg " as u\d+" pallets/ # Type casts rg ".unwrap_or" pallets/
Step 4: Arithmetic Safety Check
Find direct arithmetic
rg " + |+=| - |-=| * |*=| / |/=" pallets/
Should find checked/saturating alternatives instead
rg "checked_add|checked_sub|checked_mul|checked_div" pallets/ rg "saturating_add|saturating_sub|saturating_mul" pallets/
Step 5: Weight Analysis
-
Run benchmarking: cargo test --features runtime-benchmarks
-
Verify weights match computational cost
-
Check for bounded input parameters
-
Review weight calculation functions
Step 6: Origin & Privilege Review
Find privileged operations
rg "ensure_signed" pallets/ | grep -E "pause|emergency|admin|force|sudo"
Should use ensure_root or custom origins
rg "ensure_root|ForceOrigin|AdminOrigin" pallets/
Step 7: Testing Review
-
Unit tests cover all dispatchables
-
Fuzz tests for panic conditions
-
Benchmarks for weight calculation
-
try-runtime tests for migrations
- Priority Guidelines
Critical (Immediate Fix Required)
-
Arithmetic overflow (token creation, balance manipulation)
-
Panic DoS (node crash risk)
-
Bad origin (unauthorized privileged operations)
High (Fix Before Launch)
-
Incorrect weights (DoS via spam)
-
Verify-first violations (state corruption, pre-v0.9.25)
-
Unsigned validation issues (spam, replay attacks)
Medium (Address in Audit)
- Bad randomness (manipulation possible but limited impact)
- Testing Recommendations
Fuzz Testing
// Use test-fuzz for property-based testing #[cfg(test)] mod tests { use test_fuzz::test_fuzz;
#[test_fuzz]
fn fuzz_transfer(from: AccountId, to: AccountId, amount: u128) {
// Should never panic
let _ = Pallet::transfer(from, to, amount);
}
#[test_fuzz]
fn fuzz_no_panics(call: Call) {
// No dispatchable should panic
let _ = call.dispatch(origin);
}
}
Benchmarking
Run benchmarks to generate weights
cargo build --release --features runtime-benchmarks
./target/release/node benchmark pallet
--chain dev
--pallet pallet_example
--extrinsic "*"
--steps 50
--repeat 20
try-runtime
Test runtime upgrades
cargo build --release --features try-runtime
try-runtime --runtime ./target/release/wbuild/runtime.wasm
on-runtime-upgrade live --uri wss://rpc.polkadot.io
- Additional Resources
-
Building Secure Contracts: building-secure-contracts/not-so-smart-contracts/substrate/
-
Substrate Documentation: https://docs.substrate.io/
-
FRAME Documentation: https://paritytech.github.io/substrate/master/frame_support/
-
test-fuzz: https://github.com/trailofbits/test-fuzz
-
Substrate StackExchange: https://substrate.stackexchange.com/
- Quick Reference Checklist
Before completing Substrate pallet audit:
Arithmetic Safety (CRITICAL):
-
No direct + , - , * , / operators in dispatchables
-
All arithmetic uses checked_* or saturating_*
-
Type conversions use try_into() with error handling
Panic Prevention (CRITICAL):
-
No unwrap() or expect() in dispatchables
-
No direct array/slice indexing without bounds check
-
All user inputs validated with ensure!
-
Division operations check for zero divisor
Weights & DoS (CRITICAL):
-
Weights proportional to computational cost
-
Input parameters have maximum bounds
-
Benchmarking used to determine weights
-
No free (zero-weight) expensive operations
Access Control (CRITICAL):
-
Privileged operations use ensure_root or custom origins
-
ensure_signed only for user-level operations
-
Origin types properly configured in runtime
-
Sudo pallet removed before production
Storage Safety (HIGH):
-
Using Substrate v0.9.25+ OR manual #[transactional]
-
Validation before storage writes
-
Events emitted after successful operations
Other (MEDIUM):
-
Unsigned transactions use signed alternative if possible
-
If unsigned: proper validation, replay protection, authentication
-
BABE randomness used (not RandomnessCollectiveFlip)
-
Randomness uses random(subject) not random_seed()
Testing:
-
Unit tests for all dispatchables
-
Fuzz tests to find panics
-
Benchmarks generated and verified
-
try-runtime tests for migrations