Panics occur when the node enters a state that it cannot handle and stops the program / process instead of trying to proceed. Panics can occur for a large variety of reasons such as out-of-bounds array access, incorrect data validation, type conversions, and much more. A well-designed Substrate node must NEVER panic! If a node panics, it opens up the possibility for a denial-of-service (DoS) attack.
In the pallet-dont-panic
pallet, the find_important_value
dispatchable checks to see if useful_amounts[0]
is greater than 1_000
. If so, it sets the ImportantVal
StorageValue
to the value held in useful_amounts[0]
.
/// Do some work
///
/// Parameters:
/// - `useful_amounts`: A vector of u64 values in which there is a important value.
///
/// Emits `FoundVal` event when successful.
#[pallet::weight(10_000)]
pub fn find_important_value(
origin: OriginFor<T>,
useful_amounts: Vec<u64>,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(useful_amounts[0] > 1_000, <Error<T>>::NoImportantValueFound);
// Found the important value
ImportantValue::<T>::put(&useful_amounts[0]);
[...]
}
However, notice that there is no check before the array indexing to see whether the length of useful_amounts
is greater than zero. Thus, if useful_amounts
is empty, the indexing will cause an array out-of-bounds error which will make the node panic. Since the find_important_value
function is callable by anyone, an attacker can set useful_amounts
to an empty array and spam the network with malicious transactions to launch a DoS attack.
- Write non-throwing Rust code (e.g. prefer returning
Result
types, useensure!
, etc.). - Proper data validation of all input parameters is crucial to ensure that an unexpected panic does not occur.
- A thorough suite of unit tests should be implemented.
- Fuzz testing (e.g. using
test-fuzz
) can aid in exploring more of the input space.