Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
c268d91
initial commit to update e2e documentation
anishnaik Jul 5, 2022
5f612aa
config option fix
anishnaik Jul 6, 2022
d537715
added text about etheno edge cases
anishnaik Jul 7, 2022
396e5f0
docker instructions improved
anishnaik Jul 7, 2022
01b39d5
Initial commit for Substrate not-so-smart
anishnaik Jul 15, 2022
91da30a
Remove transfer function from mint.rs
anishnaik Jul 15, 2022
d1aa7bb
Adding functioning pallet-overflow.rs
anishnaik Aug 3, 2022
ee2fd2c
Changed comment
anishnaik Aug 3, 2022
c70c90b
Add verify first, write list pallet
anishnaik Aug 4, 2022
50e211c
Add pallets-bad-weight.rs
anishnaik Aug 4, 2022
48cf018
Initial commit for validate unsigned transactions
anishnaik Aug 8, 2022
48ba65f
Adding example where pallet panics instead of proper error handling
anishnaik Aug 8, 2022
13fe9ac
Code cleanup for pallet-dont-panic.rs
anishnaik Aug 8, 2022
64bdd33
Comment update in pallet-dont-panic.rs
anishnaik Aug 8, 2022
1ea500f
Bad randomness example
anishnaik Aug 8, 2022
f51030a
Update folder names with underscores instead of hyphens
anishnaik Aug 8, 2022
9579c7f
README for arithmetic_overflow complete
anishnaik Aug 12, 2022
0aafb46
README for dont_panic complete
anishnaik Aug 12, 2022
bb64f06
README for weights_and_fees complete
anishnaik Aug 12, 2022
a7a95c8
Renamed runtime_storage folder to verify_first folder
anishnaik Aug 12, 2022
813d9ca
Add link to README for weights_and_fees
anishnaik Aug 12, 2022
97ec3bf
README for verify_first complete
anishnaik Aug 12, 2022
89e542d
Rename pallet-bad-oracle to pallet-bad-unsigned
anishnaik Aug 15, 2022
f402901
README for validate_unsigned complete
anishnaik Aug 15, 2022
6003666
Add source for pallet-bad-unsigned.rs
anishnaik Aug 15, 2022
538a205
Rename pallet-lottery.rs to pallet-bad-lottery.rs
anishnaik Aug 15, 2022
1d76af8
README for randomness complete
anishnaik Aug 15, 2022
b3c5fbf
README for NSS pallets
anishnaik Aug 15, 2022
bafac13
Minor copyediting changes in all READMEs
anishnaik Aug 15, 2022
67abdec
Added bad origins pallet and README
anishnaik Aug 15, 2022
d0bc881
Updated NSS pallet README
anishnaik Aug 15, 2022
df5dfe9
Fix bad origins README
anishnaik Aug 18, 2022
5097302
Fix bad randomness README
anishnaik Aug 18, 2022
fb19932
Fix validate unsigned README
anishnaik Aug 18, 2022
a4fc1bd
Fix weights and fees README
anishnaik Aug 18, 2022
49e23bc
minor doc changes
Oct 27, 2022
5e84c96
fix note
anishnaik Oct 28, 2022
2f78513
fix weight function
anishnaik Nov 3, 2022
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
31 changes: 31 additions & 0 deletions not-so-smart-contracts/substrate/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# (Not So) Smart Pallets

This repository contains examples of common Substrate pallet vulnerabilities. Use Not So Smart Pallets to learn about Substrate vulnerabilities, as a reference when performing security reviews, and as a benchmark for security and analysis tools.

## Features

Each _Not So Smart Pallet_ includes a standard set of information:

* Description of the vulnerability type
* Attack scenarios to exploit the vulnerability
* Recommendations to eliminate or mitigate the vulnerability
* A mock pallet that exhibits the flaw
* References to third-party resources with more information

## Vulnerabilities

| Not So Smart Pallet | Description |
| --- | --- |
| [Arithmetic overflow](./arithmetic_overflow/README.md) | Integer overflow due to incorrect use of arithmetic operators |
| [Don't panic!](./dont_panic/README.md) | System panics create a potential DoS attack vector |
| [Weights and fees](./weights_and_fees/README.md) | Incorrect weight calculations can create a potential DoS attack vector |
| [Verify first](./verify_first/README.md) | Verify first, write last |
| [Unsigned transaction validation](./validate_unsigned/README.md) | Insufficient validation of unsigned transactions |
| [Bad randomness](./randomness/README.md) | Unsafe sources of randomness in Substrate |
| [Bad origin](./origins/README.md) | Incorrect use of call origin can lead to bypassing access controls |

## Credits

These examples are developed and maintained by [Trail of Bits](https://www.trailofbits.com/).

If you have questions, problems, or just want to learn more, then join the #ethereum channel on the [Empire Hacking Slack](https://empireslacking.herokuapp.com/) or [contact us](https://www.trailofbits.com/contact/) directly.
44 changes: 44 additions & 0 deletions not-so-smart-contracts/substrate/arithmetic_overflow/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Arithmetic overflow

Arithmetic overflow in Substrate occurs when arithmetic operations are performed using primitive operations instead of specialized functions that check for overflow. When a Substrate node is compiled in `debug` mode, integer overflows will cause the program to panic. However, when the node is compiled in `release` mode (e.g. `cargo build --release`), Substrate will perform two's complement wrapping. A production-ready node will be compiled in `release` mode, which makes it vulnerable to arithmetic overflow.

# Example
In the [`pallet-overflow`](./pallet-overflow.rs) pallet, notice that the `transfer` function sets `update_sender` and `update_to` using primitive arithmetic operations.

```rust
/// Allow minting account to transfer a given balance to another account.
///
/// Parameters:
/// - `to`: The account to receive the transfer.
/// - `amount`: The amount of balance to transfer.
///
/// Emits `Transferred` event when successful.
#[pallet::weight(10_000)]
pub fn transfer(
origin: OriginFor<T>,
to: T::AccountId,
amount: u64,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let sender_balance = Self::get_balance(&sender);
let receiver_balance = Self::get_balance(&to);

// Calculate new balances.
let update_sender = sender_balance - amount;
let update_to = receiver_balance + amount;
[...]
}
```

The sender of the extrinsic can exploit this vulnerability by causing `update_sender` to underflow, which artificially inflates their balance.

**Note**: Aside from the stronger mitigations mentioned below, a check to make sure that `sender` has at least `amount` balance would have also prevented an underflow.

# Mitigations
- Use `checked` or `saturating` functions for arithmetic operations.
- [`CheckedAdd` trait](https://docs.rs/num/0.4.0/num/traits/trait.CheckedAdd.html)
- [`Saturating` trait](https://docs.rs/num/0.4.0/num/traits/trait.Saturating.html)

# References
- https://doc.rust-lang.org/book/ch03-02-data-types.html#integer-overflow
- https://docs.substrate.io/reference/how-to-guides/basics/use-helper-functions/
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*};
use frame_system::pallet_prelude::*;

/// Pallet configuration
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
}

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

// DEFAULT total supply of tokens
#[pallet::type_value]
pub(super) fn TotalSupplyDefaultValue<T: Config>() -> u64 {
1000
}

// Data structure that holds the total supply of tokens
#[pallet::storage]
#[pallet::getter(fn total_supply)]
pub(super) type TotalSupply<T: Config> =
StorageValue<_, u64, ValueQuery, TotalSupplyDefaultValue<T>>;

// Data structure that holds whether or not the pallet's init() function has been called
#[pallet::storage]
#[pallet::getter(fn is_init)]
pub(super) type Init<T: Config> = StorageValue<_, bool, ValueQuery>;

/// Storage item for balances to accounts mapping.
#[pallet::storage]
#[pallet::getter(fn get_balance)]
pub(super) type BalanceToAccount<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
u64,
ValueQuery
>;

/// Token mint can emit two Event types.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Token was initialized by user
Initialized(T::AccountId),
/// Tokens were successfully transferred between accounts. [from, to, value]
Transferred(T::AccountId, T::AccountId, u64),
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

#[pallet::error]
pub enum Error<T> {
/// Attempted to initialize the token after it had already been initialized.
AlreadyInitialized,
}

#[pallet::call]
impl<T:Config> Pallet<T> {
/// Initialize the token
/// Transfers the total_supply amount to the caller
/// If init() has already been called, throw AlreadyInitialized error
#[pallet::weight(10_000)]
pub fn init(origin: OriginFor<T>) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
ensure!(!Self::is_init(), <Error<T>>::AlreadyInitialized);

<BalanceToAccount<T>>::insert(sender, Self::total_supply());

Init::<T>::put(true);
Ok(().into())
}

/// Allow minting account to transfer a given balance to another account.
///
/// Parameters:
/// - `to`: The account to receive the transfer.
/// - `amount`: The amount of balance to transfer.
///
/// Emits `Transferred` event when successful.
#[pallet::weight(10_000)]
pub fn transfer(
origin: OriginFor<T>,
to: T::AccountId,
amount: u64,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
let sender_balance = Self::get_balance(&sender);
let receiver_balance = Self::get_balance(&to);

// Calculate new balances.
let update_sender = sender_balance - amount;
let update_to = receiver_balance + amount;

// Update both accounts storage.
<BalanceToAccount<T>>::insert(&sender, update_sender);
<BalanceToAccount<T>>::insert(&to, update_to);

// Emit event.
Self::deposit_event(Event::Transferred(sender, to, amount));
Ok(().into())
}
}
}
39 changes: 39 additions & 0 deletions not-so-smart-contracts/substrate/dont_panic/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Don't Panic!

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.

# Example
In the [`pallet-dont-panic`](./pallet-dont-panic.rs) 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]`.

```rust
/// 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.

# Mitigations
- Write non-throwing Rust code (e.g. prefer returning [`Result`](https://paritytech.github.io/substrate/master/frame_support/dispatch/result/enum.Result.html) types, use [`ensure!`](https://paritytech.github.io/substrate/master/frame_support/macro.ensure.html), 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`](https://github.com/trailofbits/test-fuzz)) can aid in exploring more of the input space.

# References
- https://docs.substrate.io/main-docs/build/events-errors/#errors
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[frame_support::pallet]
pub mod pallet {
use frame_support::{dispatch::DispatchResultWithPostInfo, pallet_prelude::*};
use frame_system::pallet_prelude::*;
use sp_std::prelude::*;

/// Pallet configuration
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
}

#[pallet::pallet]
#[pallet::without_storage_info]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

/// Storage item for holding an ImportantValue
#[pallet::storage]
#[pallet::getter(fn get_val)]
pub(super) type ImportantValue<T: Config> = StorageValue<_, u64, ValueQuery>;

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Emit after val is found
FoundVal(T::AccountId, u64),
}

#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}

#[pallet::error]
pub enum Error<T> {
NoImportantValueFound,
}

#[pallet::call]
impl<T:Config> Pallet<T> {
/// Find important value
///
/// 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]);

// Emit event
Self::deposit_event(Event::FoundVal(sender, useful_amounts[0]));
Ok(().into())
}
}
}
59 changes: 59 additions & 0 deletions not-so-smart-contracts/substrate/origins/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Origins

The origin of a call tells a dispatchable function where the call has come from. Origins are a way to implement access controls in the system.

There are three types of origins that can used in the runtime:

```rust
pub enum RawOrigin<AccountId> {
Root,
Signed(AccountId),
None,
}
```

Outside of the out-of-box origins, custom origins can also be created that are catered to a specific runtime. The primary use case for custom origins is to configure privileged access to dispatch calls in the runtime, outside of `RawOrigin::Root`.

Using privileged origins, like `RawOrigin::Root` or custom origins, can lead to access control violations if not used correctly. It is a common error to use `ensure_signed` in place of `ensure_root` which would allow any user to bypass the access control placed by using `ensure_root`.

# Example
In the [`pallet-bad-origin`](./pallet-bad-origin.rs) pallet, there is a `set_important_val` function that should be only callable by the `ForceOrigin` _custom_ origin type. This custom origin allows the pallet to specify that only a specific account can call `set_important_val`.

```rust
#[pallet::call]
impl<T:Config> Pallet<T> {
/// Set the important val
/// Should be only callable by ForceOrigin
#[pallet::weight(10_000)]
pub fn set_important_val(
origin: OriginFor<T>,
new_val: u64
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
// Change to new value
<ImportantVal<T>>::put(new_val);

// Emit event
Self::deposit_event(Event::ImportantValSet(sender, new_val));

Ok(().into())
}
}
```
However, the `set_important_val` is using `ensure_signed`; this allows any account to set `ImportantVal`. To allow only the `ForceOrigin` to call `set_important_val` the following change can be made:

```rust
T::ForceOrigin::ensure_origin(origin.clone())?;
let sender = ensure_signed(origin)?;
```

# Mitigations
- Ensure that the correct access controls are placed on privileged functions.
- Develop user documentation on all risks associated with the system, including those associated with privileged users.
- A thorough suite of unit tests that validates access controls is crucial.

# References
- https://docs.substrate.io/main-docs/build/origins/
- https://docs.substrate.io/tutorials/work-with-pallets/specify-the-origin-for-a-call/
- https://paritytech.github.io/substrate/master/pallet_sudo/index.html#
- https://paritytech.github.io/substrate/master/pallet_democracy/index.html
Loading