Skip to content

Commit e66c417

Browse files
authored
chore: audit fixes for services pallet (#1032)
1 parent 4768113 commit e66c417

File tree

13 files changed

+256
-78
lines changed

13 files changed

+256
-78
lines changed

pallets/services/src/benchmarking.rs

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use frame_benchmarking::v1::{benchmarks, impl_benchmark_test_suite};
44
use frame_support::BoundedVec;
55
use frame_system::RawOrigin;
66
use scale_info::prelude::boxed::Box;
7-
use sp_core::{ByteArray, H160, crypto::Pair, ecdsa};
7+
use sp_core::{H160, crypto::Pair, ecdsa};
88
use sp_runtime::{KeyTypeId, Percent};
99
use sp_std::vec;
1010
use tangle_primitives::services::{
@@ -96,16 +96,9 @@ fn create_test_blueprint<T: Config>(
9696
origin: OriginFor<T>,
9797
blueprint: ServiceBlueprint<T::Constraints>,
9898
) -> Result<(), sp_runtime::DispatchError> {
99-
Pallet::<T>::create_blueprint(
100-
origin,
101-
Default::default(), // metadata
102-
blueprint, // typedef
103-
MembershipModel::Fixed { min_operators: 1 }, // membership_model
104-
vec![], // security_requirements
105-
None, // price_targets
106-
)
107-
.map(|_| ())
108-
.map_err(|e| e.error)
99+
Pallet::<T>::create_blueprint(origin, blueprint)
100+
.map(|_| ())
101+
.map_err(|e| e.error)
109102
}
110103

111104
benchmarks! {
@@ -120,11 +113,7 @@ benchmarks! {
120113
let blueprint = cggmp21_blueprint::<T>();
121114
}: _(
122115
RawOrigin::Signed(alice.clone()),
123-
Default::default(), // metadata
124-
blueprint, // typedef
125-
MembershipModel::Fixed { min_operators: 1 }, // membership_model
126-
vec![], // security_requirements
127-
None // price_targets
116+
blueprint
128117
)
129118

130119
pre_register {

pallets/services/src/lib.rs

Lines changed: 67 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ use frame_support::{
2323
dispatch::{DispatchResult, DispatchResultWithPostInfo, Pays, PostDispatchInfo},
2424
ensure,
2525
pallet_prelude::*,
26-
storage::TransactionOutcome,
2726
traits::{
2827
Currency, ReservableCurrency,
2928
fungibles::{Inspect, Mutate},
@@ -42,6 +41,7 @@ use tangle_primitives::{
4241
};
4342

4443
#[cfg(not(feature = "std"))]
44+
#[allow(unused_imports)]
4545
use alloc::string::String;
4646

4747
pub mod functions;
@@ -247,6 +247,22 @@ pub mod module {
247247
+ Parameter
248248
+ MaybeSerializeDeserialize;
249249

250+
/// Maximum number of slashes to process per block to prevent DoS attacks.
251+
#[pallet::constant]
252+
type MaxSlashesPerBlock: Get<u32> + Default + Parameter + MaybeSerializeDeserialize;
253+
254+
/// Maximum size of metrics data in heartbeat messages (in bytes).
255+
#[pallet::constant]
256+
type MaxMetricsDataSize: Get<u32> + Default + Parameter + MaybeSerializeDeserialize;
257+
258+
/// Fallback weight for reads when weight calculation overflows.
259+
#[pallet::constant]
260+
type FallbackWeightReads: Get<u64> + Default + Parameter + MaybeSerializeDeserialize;
261+
262+
/// Fallback weight for writes when weight calculation overflows.
263+
#[pallet::constant]
264+
type FallbackWeightWrites: Get<u64> + Default + Parameter + MaybeSerializeDeserialize;
265+
250266
/// Weight information for the extrinsics in this module.
251267
type WeightInfo: WeightInfo;
252268
}
@@ -267,37 +283,38 @@ pub mod module {
267283
/// On initialize, we should check for any unapplied slashes and apply them.
268284
/// Also process subscription payments for active services.
269285
fn on_initialize(n: BlockNumberFor<T>) -> Weight {
270-
let mut weight = Zero::zero();
286+
let mut weight: Weight = Weight::zero();
271287
let current_era = T::OperatorDelegationManager::get_current_round();
272288
let slash_defer_duration = T::SlashDeferDuration::get();
273289

274290
// Only process slashes from eras that have completed their deferral period
275291
let process_era = current_era.saturating_sub(slash_defer_duration);
276292

293+
// Limit processing to prevent DoS attacks using configurable limit
294+
let max_slashes_per_block = T::MaxSlashesPerBlock::get();
295+
277296
// Get all unapplied slashes for this era
278297
let prefix_iter = UnappliedSlashes::<T>::iter_prefix(process_era);
279-
for (index, slash) in prefix_iter {
280-
// TODO: This call must be all or nothing.
281-
// TODO: If fail then revert all storage changes
282-
if Self::slashing_enabled() {
283-
let _ = frame_support::storage::with_transaction(
284-
|| -> TransactionOutcome<Result<_, DispatchError>> {
285-
let res = T::SlashManager::slash_operator(&slash);
286-
match &res {
287-
Ok(weight_used) => {
288-
weight =
289-
weight_used.checked_add(&weight).unwrap_or_else(Zero::zero);
290-
// Remove the slash from storage after successful application
291-
UnappliedSlashes::<T>::remove(process_era, index);
292-
TransactionOutcome::Commit(Ok(res))
293-
},
294-
Err(_) => {
295-
log::error!("Failed to apply slash for index: {:?}", index);
296-
TransactionOutcome::Rollback(Ok(res))
297-
},
298-
}
299-
},
300-
);
298+
299+
for (processed_slashes, (index, slash)) in prefix_iter.enumerate() {
300+
if processed_slashes >= max_slashes_per_block as usize {
301+
break;
302+
}
303+
304+
let res = T::SlashManager::slash_operator(&slash);
305+
match &res {
306+
Ok(weight_used) => {
307+
weight = weight.checked_add(weight_used).unwrap_or(
308+
T::DbWeight::get().reads_writes(
309+
T::FallbackWeightReads::get(),
310+
T::FallbackWeightWrites::get(),
311+
),
312+
);
313+
UnappliedSlashes::<T>::remove(process_era, index);
314+
},
315+
Err(_) => {
316+
weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 0));
317+
},
301318
}
302319
}
303320

@@ -491,6 +508,14 @@ pub mod module {
491508
TooManySubscriptions,
492509
/// Custom asset transfer failed
493510
CustomAssetTransferFailed,
511+
/// Invalid event count provided
512+
InvalidEventCount,
513+
/// Metrics data too large
514+
MetricsDataTooLarge,
515+
/// Subscription not valid
516+
SubscriptionNotValid,
517+
/// Service not owned by caller
518+
ServiceNotOwned,
494519
}
495520

496521
#[pallet::event]
@@ -1151,27 +1176,31 @@ pub mod module {
11511176
registration_args: Vec<Field<T::Constraints, T::AccountId>>,
11521177
#[pallet::compact] value: BalanceOf<T>,
11531178
) -> DispatchResultWithPostInfo {
1154-
let caller = ensure_signed(origin)?;
1155-
// Validate the operator preferences
1156-
ensure!(preferences.key != [0u8; 65], Error::<T>::InvalidKey);
1157-
// Check if the caller is an active operator in the delegation system
1179+
let operator = ensure_signed(origin)?;
1180+
1181+
// Ensure operator is active in delegation system
11581182
ensure!(
1159-
T::OperatorDelegationManager::is_operator_active(&caller),
1183+
T::OperatorDelegationManager::is_operator_active(&operator),
11601184
Error::<T>::OperatorNotActive
11611185
);
1186+
1187+
// Validate the operator preferences
1188+
ensure!(preferences.key != [0u8; 65], Error::<T>::InvalidKey);
1189+
11621190
// Check if operator is already registered for this blueprint
11631191
ensure!(
1164-
!Operators::<T>::contains_key(blueprint_id, &caller),
1192+
!Operators::<T>::contains_key(blueprint_id, &operator),
11651193
Error::<T>::AlreadyRegistered
11661194
);
1195+
11671196
// Check if the key is already in use
11681197
for (_, prefs) in Operators::<T>::iter_prefix(blueprint_id) {
11691198
if prefs.key == preferences.key {
11701199
return Err(Error::<T>::DuplicateKey.into());
11711200
}
11721201
}
11731202

1174-
Self::do_register(&caller, blueprint_id, preferences, registration_args, value)?;
1203+
Self::do_register(&operator, blueprint_id, preferences, registration_args, value)?;
11751204
Ok(PostDispatchInfo { actual_weight: None, pays_fee: Pays::Yes })
11761205
}
11771206

@@ -1661,6 +1690,7 @@ pub mod module {
16611690
) -> DispatchResultWithPostInfo {
16621691
let caller = ensure_signed(origin)?;
16631692
let service = Self::services(service_id)?;
1693+
let (_, _blueprint) = Self::blueprints(service.blueprint)?;
16641694
let (maybe_slashing_origin, _used_weight) = Self::query_slashing_origin(&service)?;
16651695
let slashing_origin = maybe_slashing_origin.ok_or(Error::<T>::NoSlashingOrigin)?;
16661696
ensure!(slashing_origin == caller, DispatchError::BadOrigin);
@@ -2081,6 +2111,10 @@ pub mod module {
20812111
) -> DispatchResultWithPostInfo {
20822112
let caller = ensure_signed(origin)?;
20832113

2114+
// Validate metrics data size before processing
2115+
let max_metrics_size = T::MaxMetricsDataSize::get() as usize;
2116+
ensure!(metrics_data.len() <= max_metrics_size, Error::<T>::MetricsDataTooLarge);
2117+
20842118
// Ensure the service exists and is active
20852119
ensure!(
20862120
ServiceStatus::<T>::contains_key(blueprint_id, service_id),
@@ -2106,9 +2140,10 @@ pub mod module {
21062140
.map_err(|_| Error::<T>::InvalidHeartbeatData)?;
21072141

21082142
// Create the message to verify
2109-
// Format: service_id + blueprint_id + metrics_data
2143+
// Format: service_id + blueprint_id + current_block + metrics_data
21102144
let mut message = service_id.to_le_bytes().to_vec();
21112145
message.extend_from_slice(&blueprint_id.to_le_bytes());
2146+
message.extend_from_slice(&current_block.saturated_into::<u64>().to_le_bytes());
21122147
message.extend_from_slice(&bounded_metrics_data);
21132148
let message_hash = sp_io::hashing::keccak_256(&message);
21142149

pallets/services/src/mock.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,22 @@ parameter_types! {
378378
#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
379379
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
380380
pub const MinimumNativeSecurityRequirement: Percent = Percent::from_percent(10);
381+
382+
#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
383+
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
384+
pub const MaxSlashesPerBlock: u32 = 10;
385+
386+
#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
387+
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
388+
pub const MaxMetricsDataSize: u32 = 1024;
389+
390+
#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
391+
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
392+
pub const FallbackWeightReads: u64 = 100;
393+
394+
#[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
395+
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
396+
pub const FallbackWeightWrites: u64 = 100;
381397
}
382398

383399
impl pallet_services::Config for Runtime {
@@ -415,6 +431,10 @@ impl pallet_services::Config for Runtime {
415431
type MaxResourceNameLength = MaxResourceNameLength;
416432
type MaxMasterBlueprintServiceManagerVersions = MaxMasterBlueprintServiceManagerRevisions;
417433
type MinimumNativeSecurityRequirement = MinimumNativeSecurityRequirement;
434+
type MaxSlashesPerBlock = MaxSlashesPerBlock;
435+
type MaxMetricsDataSize = MaxMetricsDataSize;
436+
type FallbackWeightReads = FallbackWeightReads;
437+
type FallbackWeightWrites = FallbackWeightWrites;
418438
type Constraints = pallet_services::types::ConstraintsOf<Self>;
419439
type OperatorDelegationManager = MultiAssetDelegation;
420440
type SlashDeferDuration = SlashDeferDuration;

0 commit comments

Comments
 (0)