Skip to content

Commit f50ebf0

Browse files
precompiles: Enforce state mutability (#10080)
`pallet-assets-precompile`, `pallet-xcm-precompiles` and revive builtin precompile implementations currently violate [Solidity state mutability](https://docs.soliditylang.org/en/latest/grammar.html#syntax-rule-SolidityParser.stateMutability), potentially introducing a new attack vector. This PR implements corresponding checks at the function dispatch. Could be enforced in `pallet-revive`, however: 1. Adding something like a `const MUTATES: bool` to the `Precompile` trait won't help because whether the call is mutating or not depends on the [Solidity function selector.](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector). 2. Alloy, which we are using to parse the interface definitions prior to calling precompile implementations, doesn't provide a mapping from function selector to its mutability [modifier](https://docs.soliditylang.org/en/latest/cheatsheet.html#modifiers). --------- Signed-off-by: Cyrill Leutwiler <[email protected]> Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 6c74e3f commit f50ebf0

File tree

4 files changed

+26
-0
lines changed

4 files changed

+26
-0
lines changed

polkadot/xcm/pallet-xcm/precompiles/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ where
8282
};
8383

8484
match input {
85+
IXcmCalls::send(_) | IXcmCalls::execute(_) if env.is_read_only() =>
86+
Err(Error::Error(pallet_revive::Error::<Self::T>::StateChangeDenied.into())),
8587
IXcmCalls::send(IXcm::sendCall { destination, message }) => {
8688
let _ = env.charge(<Runtime as Config>::WeightInfo::send())?;
8789

prdoc/pr_10080.prdoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
title: 'precompiles: Enforce state mutability'
2+
doc:
3+
- audience: Runtime Dev
4+
description: |-
5+
`pallet-assets-precompile`, `pallet-xcm-precompiles` and revive builtin precompile implementations currently violate [Solidity state mutability](https://docs.soliditylang.org/en/latest/grammar.html#syntax-rule-SolidityParser.stateMutability), potentially introducing a new attack vector. This PR implements corresponding checks at the function dispatch.
6+
7+
Could be enforced in `pallet-revive`, however:
8+
1. Adding something like a `const MUTATES: bool` to the `Precompile` trait won't help because whether the call is mutating or not depends on the [Solidity function selector.](https://docs.soliditylang.org/en/latest/abi-spec.html#function-selector).
9+
2. Alloy, which we are using to parse the interface definitions prior to calling precompile implementations, doesn't provide a mapping from function selector to its mutability [modifier](https://docs.soliditylang.org/en/latest/cheatsheet.html#modifiers).
10+
crates:
11+
- name: pallet-assets-precompiles
12+
bump: patch
13+
- name: pallet-xcm-precompiles
14+
bump: patch
15+
- name: pallet-revive
16+
bump: patch

substrate/frame/assets/precompiles/src/lib.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ where
108108
let asset_id = PrecompileConfig::AssetIdExtractor::asset_id_from_address(address)?.into();
109109

110110
match input {
111+
IERC20Calls::transfer(_) | IERC20Calls::approve(_) | IERC20Calls::transferFrom(_)
112+
if env.is_read_only() =>
113+
Err(Error::Error(pallet_revive::Error::<Self::T>::StateChangeDenied.into())),
114+
111115
IERC20Calls::transfer(call) => Self::transfer(asset_id, call, env),
112116
IERC20Calls::totalSupply(_) => Self::total_supply(asset_id, env),
113117
IERC20Calls::balanceOf(call) => Self::balance_of(asset_id, call, env),

substrate/frame/revive/src/precompiles/builtin/storage.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ impl<T: Config> BuiltinPrecompile for Storage<T> {
5454
use IStorage::IStorageCalls;
5555
let max_size = limits::STORAGE_BYTES;
5656
match input {
57+
IStorageCalls::clearStorage(_) | IStorageCalls::takeStorage(_)
58+
if env.is_read_only() =>
59+
Err(Error::Error(crate::Error::<Self::T>::StateChangeDenied.into())),
60+
5761
IStorageCalls::clearStorage(IStorage::clearStorageCall { flags, key, isFixedKey }) => {
5862
let transient = is_transient(*flags)
5963
.map_err(|_| Error::Revert("invalid storage flag".into()))?;

0 commit comments

Comments
 (0)