Skip to content

Commit 2c6325f

Browse files
committed
PoC: DenyInstructionsWithXcm for validating nested XCM instructions
1 parent be2404c commit 2c6325f

File tree

6 files changed

+178
-5
lines changed

6 files changed

+178
-5
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

polkadot/xcm/xcm-builder/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ workspace = true
1313

1414
[dependencies]
1515
codec = { features = ["derive"], workspace = true }
16+
environmental = { workspace = true }
1617
frame-support = { workspace = true }
1718
frame-system = { workspace = true }
1819
impl-trait-for-tuples = { workspace = true }
@@ -21,6 +22,7 @@ pallet-asset-conversion = { workspace = true }
2122
pallet-transaction-payment = { workspace = true }
2223
scale-info = { features = ["derive"], workspace = true }
2324
sp-arithmetic = { workspace = true }
25+
sp-core = { workspace = true }
2426
sp-io = { workspace = true }
2527
sp-runtime = { workspace = true }
2628
sp-weights = { workspace = true }
@@ -40,7 +42,6 @@ polkadot-primitives = { workspace = true, default-features = true }
4042
polkadot-runtime-parachains = { workspace = true, default-features = true }
4143
polkadot-test-runtime = { workspace = true }
4244
primitive-types = { features = ["codec", "num-traits", "scale-info"], workspace = true }
43-
sp-core = { workspace = true, default-features = true }
4445

4546
[features]
4647
default = ["std"]
@@ -63,6 +64,7 @@ runtime-benchmarks = [
6364
]
6465
std = [
6566
"codec/std",
67+
"environmental/std",
6668
"frame-support/std",
6769
"frame-system/std",
6870
"log/std",
@@ -72,6 +74,7 @@ std = [
7274
"primitive-types/std",
7375
"scale-info/std",
7476
"sp-arithmetic/std",
77+
"sp-core/std",
7578
"sp-io/std",
7679
"sp-runtime/std",
7780
"sp-weights/std",

polkadot/xcm/xcm-builder/src/barriers.rs

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -490,7 +490,7 @@ impl ShouldExecute for DenyReserveTransferToRelayChain {
490490
if matches!(origin, Location { parents: 1, interior: Here }) =>
491491
{
492492
log::warn!(
493-
target: "xcm::barrier",
493+
target: "xcm::barriers",
494494
"Unexpected ReserveAssetDeposited from the Relay Chain",
495495
);
496496
Ok(ControlFlow::Continue(()))
@@ -504,3 +504,71 @@ impl ShouldExecute for DenyReserveTransferToRelayChain {
504504
Ok(())
505505
}
506506
}
507+
508+
environmental::environmental!(recursion_count: u8);
509+
510+
// TBD:
511+
/// Applies the `Inner` filter to the nested XCM for the `SetAppendix`, `SetErrorHandler`, and `ExecuteWithOrigin` instructions.
512+
///
513+
/// Note: The nested XCM is checked recursively!
514+
pub struct DenyInstructionsWithXcm<Inner>(PhantomData<Inner>);
515+
impl<Inner: ShouldExecute> ShouldExecute for DenyInstructionsWithXcm<Inner> {
516+
fn should_execute<RuntimeCall>(
517+
origin: &Location,
518+
message: &mut [Instruction<RuntimeCall>],
519+
max_weight: Weight,
520+
properties: &mut Properties,
521+
) -> Result<(), ProcessMessageError> {
522+
message.matcher().match_next_inst_while(
523+
|_| true,
524+
|inst| match inst {
525+
SetAppendix(nested_xcm) |
526+
SetErrorHandler(nested_xcm) |
527+
ExecuteWithOrigin { xcm: nested_xcm, .. } => {
528+
// check xcm instructions with `Inner` filter
529+
let _ = Inner::should_execute(origin, nested_xcm.inner_mut(), max_weight, properties)
530+
.inspect_err(|e| {
531+
log::warn!(
532+
target: "xcm::barriers",
533+
"`DenyInstructionsWithXcm`'s `Inner::should_execute` did not pass for origin: {:?} and nested_xcm: {:?} with error: {:?}",
534+
origin,
535+
nested_xcm,
536+
e
537+
);
538+
})?;
539+
540+
// Initialize the recursion count only the first time we hit this code in our
541+
// potential recursive execution.
542+
let _ = recursion_count::using_once(&mut 1, || {
543+
recursion_count::with(|count| {
544+
if *count > xcm_executor::RECURSION_LIMIT {
545+
return Err(ProcessMessageError::StackLimitReached)
546+
}
547+
*count = count.saturating_add(1);
548+
Ok(())
549+
})
550+
// This should always return `Some`, but let's play it safe.
551+
.unwrap_or(Ok(()))?;
552+
553+
// Ensure that we always decrement the counter whenever we finish processing
554+
// the `DenyInstructionsWithXcm`.
555+
sp_core::defer! {
556+
recursion_count::with(|count| {
557+
*count = count.saturating_sub(1);
558+
});
559+
}
560+
561+
// check recursively with `DenyInstructionsWithXcm`
562+
Self::should_execute(origin, nested_xcm.inner_mut(), max_weight, properties)
563+
})?;
564+
565+
Ok(ControlFlow::Continue(()))
566+
},
567+
_ => Ok(ControlFlow::Continue(())),
568+
},
569+
)?;
570+
571+
// Permit everything else
572+
Ok(())
573+
}
574+
}

polkadot/xcm/xcm-builder/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ mod barriers;
4242
pub use barriers::{
4343
AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
4444
AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
45-
AllowUnpaidExecutionFrom, DenyReserveTransferToRelayChain, DenyThenTry, IsChildSystemParachain,
46-
IsParentsOnly, IsSiblingSystemParachain, RespectSuspension, TakeWeightCredit,
45+
AllowUnpaidExecutionFrom, DenyReserveTransferToRelayChain, DenyInstructionsWithXcm, DenyThenTry,
46+
IsChildSystemParachain, IsParentsOnly, IsSiblingSystemParachain, RespectSuspension, TakeWeightCredit,
4747
TrailingSetTopicAsId, WithComputedOrigin,
4848
};
4949

polkadot/xcm/xcm-builder/src/tests/barriers.rs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
// You should have received a copy of the GNU General Public License
1515
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
1616

17+
use core::ops::ControlFlow;
1718
use xcm_executor::traits::Properties;
1819

1920
use super::*;
@@ -532,3 +533,103 @@ fn allow_hrmp_notifications_from_relay_chain_should_work() {
532533
Ok(()),
533534
);
534535
}
536+
537+
538+
#[test]
539+
fn deny_instructions_with_xcm_works() {
540+
frame_support::__private::sp_tracing::try_init_simple();
541+
542+
// dummy filter which denies `ClearOrigin`
543+
struct DenyClearOrigin;
544+
impl ShouldExecute for DenyClearOrigin {
545+
fn should_execute<RuntimeCall>(_: &Location, message: &mut [Instruction<RuntimeCall>], _: Weight, _: &mut Properties) -> Result<(), ProcessMessageError> {
546+
message.matcher().match_next_inst_while(
547+
|_| true,
548+
|inst| match inst {
549+
ClearOrigin => Err(ProcessMessageError::Unsupported),
550+
_ => Ok(ControlFlow::Continue(())),
551+
},
552+
)?;
553+
Ok(())
554+
}
555+
}
556+
557+
// closure for (xcm, origin) testing with `DenyInstructionsWithXcm` which denies `ClearOrigin` instruction
558+
let assert_should_execute = |mut xcm: Vec<Instruction<()>>, origin, expected_result| {
559+
assert_eq!(
560+
DenyInstructionsWithXcm::<DenyClearOrigin>::should_execute(
561+
&origin,
562+
&mut xcm,
563+
Weight::from_parts(10, 10),
564+
&mut props(Weight::zero()),
565+
),
566+
expected_result
567+
);
568+
};
569+
570+
// ok
571+
assert_should_execute(
572+
vec![ClearTransactStatus],
573+
Location::parent(),
574+
Ok(()),
575+
);
576+
// ok top-level contains `ClearOrigin`
577+
assert_should_execute(
578+
vec![ClearOrigin],
579+
Location::parent(),
580+
Ok(()),
581+
);
582+
// ok - SetAppendix with XCM without ClearOrigin
583+
assert_should_execute(
584+
vec![SetAppendix(Xcm(vec![ClearTransactStatus]))],
585+
Location::parent(),
586+
Ok(()),
587+
);
588+
589+
// invalid - empty XCM
590+
assert_should_execute(
591+
vec![],
592+
Location::parent(),
593+
Err(ProcessMessageError::BadFormat),
594+
);
595+
// invalid - SetAppendix with empty XCM
596+
assert_should_execute(
597+
vec![SetAppendix(Xcm(vec![]))],
598+
Location::parent(),
599+
Err(ProcessMessageError::BadFormat),
600+
);
601+
// invalid SetAppendix contains `ClearOrigin`
602+
assert_should_execute(
603+
vec![SetAppendix(Xcm(vec![ClearOrigin]))],
604+
Location::parent(),
605+
Err(ProcessMessageError::Unsupported),
606+
);
607+
// invalid nested SetAppendix contains `ClearOrigin`
608+
assert_should_execute(
609+
vec![SetAppendix(Xcm(vec![
610+
SetAppendix(Xcm(vec![
611+
SetAppendix(Xcm(vec![
612+
SetAppendix(Xcm(vec![
613+
SetAppendix(Xcm(vec![
614+
SetAppendix(Xcm(vec![
615+
SetAppendix(Xcm(vec![
616+
SetAppendix(Xcm(vec![
617+
SetAppendix(Xcm(vec![
618+
SetAppendix(Xcm(vec![
619+
SetAppendix(Xcm(vec![
620+
SetAppendix(Xcm(vec![ClearOrigin]))
621+
]))
622+
]))
623+
]))
624+
]))
625+
]))
626+
]))
627+
]))
628+
]))
629+
]))
630+
]))
631+
]))],
632+
Location::parent(),
633+
Err(ProcessMessageError::StackLimitReached),
634+
);
635+
}

polkadot/xcm/xcm-executor/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ pub struct FeesMode {
6060
pub jit_withdraw: bool,
6161
}
6262

63-
const RECURSION_LIMIT: u8 = 10;
63+
pub const RECURSION_LIMIT: u8 = 10;
6464

6565
environmental::environmental!(recursion_count: u8);
6666

0 commit comments

Comments
 (0)