Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Snowbridge]: Ensure source always from AH for exported message #6838

Draft
wants to merge 36 commits into
base: master
Choose a base branch
from

Conversation

yrong
Copy link
Contributor

@yrong yrong commented Dec 11, 2024

@yrong yrong marked this pull request as ready for review December 11, 2024 05:35
@paritytech-review-bot paritytech-review-bot bot requested a review from a team December 11, 2024 05:36
Copy link
Contributor

@acatangiu acatangiu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's wrong to enforce this rule at the router level:

  • maybe some other chain wants to use this router primitive with another bridge (unlikely)
  • maybe we change the router in the AH runtime and the new one doesn't validate this bridge-specific rule (likely)
  • XCM goes to bridge on Bridge Hub through another path than this router and this rule is not enforced (potentially dangerous)

This rule should definitely be enforced by the bridge itself on Bridge Hub. We can have the router also enforce it in order to fail early, fail at AH instead of failing on the next hop on BH. If this is the case, then the impl details comments below apply.

bridges/snowbridge/primitives/router/src/outbound/mod.rs Outdated Show resolved Hide resolved
bridges/snowbridge/primitives/router/src/outbound/mod.rs Outdated Show resolved Hide resolved
bridges/snowbridge/primitives/router/src/outbound/mod.rs Outdated Show resolved Hide resolved
bridges/snowbridge/primitives/router/src/outbound/mod.rs Outdated Show resolved Hide resolved
@paritytech-review-bot paritytech-review-bot bot requested a review from a team December 13, 2024 01:03
@yrong
Copy link
Contributor Author

yrong commented Dec 13, 2024

We can have the router also enforce it to fail early at AH instead of failing on the next hop on BH.

IIUC EthereumBlobExporter is the only entry point where users can send arbitrary messages to OutboundQueue. Meanwhile, since we've already patched with polkadot-fellows/runtimes#506 to disable the agent/channel, I'd assume it's safe for now, so maybe we can improve that in a separate PR?

@acatangiu acatangiu added the T15-bridges This PR/Issue is related to bridges. label Dec 18, 2024
@bkontur
Copy link
Contributor

bkontur commented Jan 13, 2025

I think it's wrong to enforce this rule at the router level:

* maybe some other chain wants to use this router primitive with another bridge (unlikely)

* maybe we change the router in the AH runtime and the new one doesn't validate this bridge-specific rule (likely)

* XCM goes to bridge on Bridge Hub through another path than this router and this rule is not enforced (potentially dangerous)

This rule should definitely be enforced by the bridge itself on Bridge Hub. We can have the router also enforce it in order to fail early, fail at AH instead of failing on the next hop on BH. If this is the case, then the impl details comments below apply.

I agree, and I think changing the EthereumBlobExporter to filter the AssetHub paraId is not necessary. On the Bridge Hub, the EthereumBlobExporter is triggered only when the ExportMessage instruction is received.
A simpler and better solution would be to add a custom Barrier to forbid the ExportMessage for Ethereum if the origin is not AssetHubLocation here.

This solution should be better and safer because it prevents the execution of any XCM from starting. As a result, it will fail fast without executing other instructions before ExportMessage (so no withdrawals and so on). Do we want/need to trap any assets in this case? I would say no.

Something like this:

pub type Barrier = TrailingSetTopicAsId<
	DenyThenTry<
		(
			DenyReserveTransferToRelayChain,
			// TODO: here just add this `Deny` filter
			DenyFirstExportMessageFrom<
				EverythingBut<Equals<AssetHubLocation>,
		                Equals<EthereumNetwork>
			>
		(
			// Allow local users to buy weight credit.
			TakeWeightCredit,
pub struct DenyFirstExportMessageFrom<FromOrigin, ToGlobalConsensus>(PhantomData<(FromOrigin, ToGlobalConsensus)>);
impl<FromOrigin: Contains<Location>, ToGlobalConsensus: Contains<NetworkId>> ShouldExecute for DenyFirstExportMessageFrom<(FromOrigin, ToGlobalConsensus)> {
	fn should_execute<RuntimeCall>(...

@yrong
Copy link
Contributor Author

yrong commented Jan 14, 2025

As a result, it will fail fast without executing other instructions before ExportMessage (so no withdrawals and so on). Do we want/need to trap any assets in this case? I would say no.

@bkontur That makes sense. I've made some changes accordingly please check.

Comment on lines 103 to 125

#[test]
fn deny_with_reserve_transfer_to_relay_chain() {
let mut xcm: Vec<Instruction<()>> = vec![DepositReserveAsset {
assets: Wild(All),
dest: Location { parents: 1, interior: Here },
xcm: Default::default(),
}];

let result = DenyThenTry::<
DenyFirstExportMessageFrom<
EverythingBut<Equals<AssetHubLocation>>,
Equals<EthereumNetwork>,
>,
DenyThenTry<DenyReserveTransferToRelayChain, TakeWeightCredit>,
>::should_execute(
&AssetHubLocation::get(),
&mut xcm,
Weight::zero(),
&mut Properties { weight_credit: Weight::zero(), message_id: None },
);
assert_err!(result, ProcessMessageError::Unsupported);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please, do not test other barriers here:

Suggested change
#[test]
fn deny_with_reserve_transfer_to_relay_chain() {
let mut xcm: Vec<Instruction<()>> = vec![DepositReserveAsset {
assets: Wild(All),
dest: Location { parents: 1, interior: Here },
xcm: Default::default(),
}];
let result = DenyThenTry::<
DenyFirstExportMessageFrom<
EverythingBut<Equals<AssetHubLocation>>,
Equals<EthereumNetwork>,
>,
DenyThenTry<DenyReserveTransferToRelayChain, TakeWeightCredit>,
>::should_execute(
&AssetHubLocation::get(),
&mut xcm,
Weight::zero(),
&mut Properties { weight_credit: Weight::zero(), message_id: None },
);
assert_err!(result, ProcessMessageError::Unsupported);
}

But definitely, I would be very happy if you move this test for DenyThenTry and DenyReserveTransferToRelayChain to the https://github.com/paritytech/polkadot-sdk/blob/master/polkadot/xcm/xcm-builder/src/tests/barriers.rs where we have tests for other barriers defined in xcm-builder.

But please do this as a separate PR/issue, because I have a feeling that DenyThenTry does not work for Deny tuple, does it? That's why you used chaining of DenyThenTry instead of tuple?

So for example one test should be dedicated just for DenyThenTry to check all branches, e.g if DenyCase1 returns Ok() does it also checks DenyCase2 and DenyCase3?

DenyThenTry<
    (
        DenyCase1,     
        DenyCase2,     
        DenyCase3,     
    ),
    (
        AllowCase1,     
        AllowCase2,     
        AllowCase3,     
    )
>

If there is such a problem with DenyThenTry, we should at least update documentation (that for Deny you cannot use tuple, but just chaining) and/or find better fix and/or deprecated DenyThenTry. Alternatively, we could go with some dedicated Allow filtering wrapper barrier with EverythingBut like behavior.


/// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.
/// If it passes the Deny, and matches one of the Allow cases then it is let through.
pub struct DenyThenTry<Deny, Allow>(PhantomData<Deny>, PhantomData<Allow>)

I am worried here about: /// Deny executing the XCM if it matches any of the Deny filter regardless of anything else.,
if it passes (does not match/ignore XCM case) DenyCase1 which returns Ok(()) then the tuple return Ok(()), but does not check DenyCase2 and DenyCase3, which could cause a potential problem.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yrong actually, let me prepare that issue

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a feeling that DenyThenTry does not work for Deny tuple, does it? That's why you used chaining of DenyThenTry instead of tuple?

Exactly, the tuple does not work as expected. That's why I added the tests for the DenyThenTry here.

I will address it in a separate PR.

Copy link
Contributor Author

@yrong yrong Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bkontur Though I revert the change in EthereumBlobExporter with 0a51fec#diff-0bd5b75b88aee6992e3ddba0de8bd08d2ab07ecd38fc745c7d76daff56bba52

Do you think it may be better to keep? so we have the double-check first in Barrier and then in EthereumBlobExporter.

This PR is mainly for addressing one security issue recently found and I feel a bit more secure with both of that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is fine as it is, just please remove this test deny_with_reserve_transfer_to_relay_chain from here as a part of that #7148, it is not important here.


This PR is mainly for addressing one security issue recently found and I feel a bit more secure with both of that.

The test covering the security issue is more important at the runtime level (not the primitives level). If you want to be sure, simply write a unit test directly in the BridgeHubWestend runtime - e.g. trigger the xcm-executor with a malicious XCM program (ExportMessage(Eth) and origin other than AssetHubLocation), where the runtime uses the real type Barrier.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yrong
Copy link
Contributor Author

yrong commented Jan 16, 2025

I'll rebase this PR on top of #7169, so please don't merge.

@yrong yrong marked this pull request as draft January 16, 2025 03:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T15-bridges This PR/Issue is related to bridges.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants