Found by the runtime whitebox fuzzer.
Summary
asset-hub-rococo documents ProxyType::NonTransfer as a proxy that can execute calls that do not transfer funds or assets. Its filter rejects Balances, Assets, NftFractionalization, Nfts, and Uniques, but it omits ForeignAssets and PoolAssets.
Both omitted variants are separate pallet_assets instances with transfer dispatchables. A NonTransfer proxy can therefore pass ForeignAssets::transfer and PoolAssets::transfer calls.
Severity and sensitivity
Severity: medium. Sensitivity: non-sensitive runtime configuration defect suitable for a public issue. Impact is access-control expectation mismatch: an account granting a NonTransfer proxy would not expect that proxy to move foreign or pool assets.
Source evidence
NonTransfer docs:
|
scale_info::TypeInfo, |
|
)] |
|
pub enum ProxyType { |
|
/// Fully permissioned proxy. Can execute any call on behalf of _proxied_. |
|
Any, |
|
/// Can execute any call that does not transfer funds or assets. |
|
NonTransfer, |
- Filter rejects some transfer-capable variants but omits
ForeignAssets and PoolAssets:
|
impl InstanceFilter<RuntimeCall> for ProxyType { |
|
fn filter(&self, c: &RuntimeCall) -> bool { |
|
match self { |
|
ProxyType::Any => true, |
|
ProxyType::NonTransfer => !matches!( |
|
c, |
|
RuntimeCall::Balances { .. } | |
|
RuntimeCall::Assets { .. } | |
|
RuntimeCall::NftFractionalization { .. } | |
|
RuntimeCall::Nfts { .. } | |
|
RuntimeCall::Uniques { .. } |
|
), |
Assets, ForeignAssets, and PoolAssets are distinct runtime call variants:
|
Assets: pallet_assets::<Instance1> = 50, |
|
Uniques: pallet_uniques = 51, |
|
Nfts: pallet_nfts = 52, |
|
ForeignAssets: pallet_assets::<Instance2> = 53, |
|
NftFractionalization: pallet_nft_fractionalization = 54, |
|
PoolAssets: pallet_assets::<Instance3> = 55, |
Reproduction
cargo test -p asset-hub-rococo-runtime tests::generated_tests::test_risk_c8uxvpea_non_transfer_rejects_foreign_assets_transfer -- --ignored --exact --nocapture
cargo test -p asset-hub-rococo-runtime tests::generated_tests::test_risk_c8uxvpea_non_transfer_rejects_pool_assets_transfer -- --ignored --exact --nocapture
Observed failures:
ProxyType::NonTransfer must reject transfer-capable call: 'ForeignAssets::transfer'
ProxyType::NonTransfer must reject transfer-capable call: 'PoolAssets::transfer'
Focused PoC:
use frame_support::traits::InstanceFilter;
#[ignore]
#[test]
fn test_risk_c8uxvpea_non_transfer_rejects_foreign_assets_transfer() {
let call = RuntimeCall::ForeignAssets(pallet_assets::Call::<Runtime, ForeignAssetsInstance>::transfer {
id: xcm::v5::Location::here(),
target: sp_runtime::MultiAddress::Id(AccountId::new([0u8; 32])),
amount: 0u128,
});
assert!(!ProxyType::NonTransfer.filter(&call),
"ProxyType::NonTransfer must reject transfer-capable call: 'ForeignAssets::transfer'");
}
#[ignore]
#[test]
fn test_risk_c8uxvpea_non_transfer_rejects_pool_assets_transfer() {
let call = RuntimeCall::PoolAssets(pallet_assets::Call::<Runtime, PoolAssetsInstance>::transfer {
id: 0u32,
target: sp_runtime::MultiAddress::Id(AccountId::new([0u8; 32])),
amount: 0u128,
});
assert!(!ProxyType::NonTransfer.filter(&call),
"ProxyType::NonTransfer must reject transfer-capable call: 'PoolAssets::transfer'");
}
Expected behavior
ProxyType::NonTransfer should reject RuntimeCall::ForeignAssets { .. } and RuntimeCall::PoolAssets { .. } transfer-capable calls, consistent with its documented non-transfer contract.
Found by the runtime whitebox fuzzer.
Summary
asset-hub-rococodocumentsProxyType::NonTransferas a proxy that can execute calls that do not transfer funds or assets. Its filter rejectsBalances,Assets,NftFractionalization,Nfts, andUniques, but it omitsForeignAssetsandPoolAssets.Both omitted variants are separate
pallet_assetsinstances with transfer dispatchables. ANonTransferproxy can therefore passForeignAssets::transferandPoolAssets::transfercalls.Severity and sensitivity
Severity: medium. Sensitivity: non-sensitive runtime configuration defect suitable for a public issue. Impact is access-control expectation mismatch: an account granting a
NonTransferproxy would not expect that proxy to move foreign or pool assets.Source evidence
NonTransferdocs:polkadot-sdk/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
Lines 580 to 586 in 8f3592a
ForeignAssetsandPoolAssets:polkadot-sdk/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
Lines 604 to 615 in 8f3592a
Assets,ForeignAssets, andPoolAssetsare distinct runtime call variants:polkadot-sdk/cumulus/parachains/runtimes/assets/asset-hub-rococo/src/lib.rs
Lines 1120 to 1125 in 8f3592a
Reproduction
Observed failures:
Focused PoC:
Expected behavior
ProxyType::NonTransfershould rejectRuntimeCall::ForeignAssets { .. }andRuntimeCall::PoolAssets { .. }transfer-capable calls, consistent with its documented non-transfer contract.