Skip to content

Commit 597cd62

Browse files
committed
chore: limit re-delegation depth
1 parent 3b1367a commit 597cd62

File tree

3 files changed

+135
-13
lines changed

3 files changed

+135
-13
lines changed

pallets/permission0/src/ext/namespace_impl.rs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ pub fn delegate_namespace_permission_impl<T: Config>(
105105

106106
parents.push(pid);
107107

108-
resolve_paths::<T>(&delegator, Some(namespace), paths)
108+
resolve_paths::<T>(&delegator, Some((*pid, namespace)), paths)
109109
} else {
110110
resolve_paths::<T>(&delegator, None, paths)
111111
}?;
@@ -150,21 +150,69 @@ pub fn delegate_namespace_permission_impl<T: Config>(
150150
Ok(permission_id)
151151
}
152152

153+
/// Maximum allowed delegation depth for namespaces
154+
const MAX_DELEGATION_DEPTH: u32 = 5;
155+
156+
/// Check the delegation depth of a namespace by traversing up the permission chain
157+
fn check_namespace_delegation_depth<T: Config>(
158+
namespace_path: &NamespacePath,
159+
parent_permission_id: Option<PermissionId>,
160+
current_depth: u32,
161+
) -> Result<(), DispatchError> {
162+
if current_depth > MAX_DELEGATION_DEPTH {
163+
return Err(Error::<T>::DelegationDepthExceeded.into());
164+
}
165+
166+
let Some(parent_id) = parent_permission_id else {
167+
return Ok(());
168+
};
169+
170+
let Some(parent_permission) = Permissions::<T>::get(parent_id) else {
171+
return Err(Error::<T>::ParentPermissionNotFound.into());
172+
};
173+
174+
let PermissionScope::Namespace(parent_scope) = &parent_permission.scope else {
175+
return Err(Error::<T>::UnsupportedPermissionType.into());
176+
};
177+
178+
for (grandparent_id, namespace_set) in parent_scope.paths.iter() {
179+
if namespace_set.contains(namespace_path) {
180+
return check_namespace_delegation_depth::<T>(
181+
namespace_path,
182+
*grandparent_id,
183+
current_depth.saturating_add(1),
184+
);
185+
}
186+
187+
for parent_namespace in namespace_set.iter() {
188+
if parent_namespace.is_parent_of(namespace_path) {
189+
return check_namespace_delegation_depth::<T>(
190+
parent_namespace,
191+
*grandparent_id,
192+
current_depth.saturating_add(1),
193+
);
194+
}
195+
}
196+
}
197+
198+
Ok(())
199+
}
200+
153201
fn resolve_paths<T: Config>(
154202
delegator: &T::AccountId,
155-
parent: Option<&NamespaceScope<T>>,
203+
parent: Option<(PermissionId, &NamespaceScope<T>)>,
156204
paths: &BoundedBTreeSet<NamespacePathInner, T::MaxNamespacesPerPermission>,
157205
) -> Result<BoundedBTreeSet<NamespacePath, T::MaxNamespacesPerPermission>, DispatchError> {
158206
let children = paths.iter().map(|path| {
159207
NamespacePath::new_agent(path).map_err(|_| Error::<T>::NamespacePathIsInvalid.into())
160208
});
161209

162-
let paths = if let Some(parent) = &parent {
210+
let paths = if let Some((parent_pid, parent)) = parent {
163211
let children = children.collect::<Result<BTreeSet<_>, DispatchError>>()?;
164-
let matched_count = parent
212+
let matched_paths = parent
165213
.paths
166214
.values()
167-
.flat_map(|parent| parent.iter())
215+
.flat_map(|p_path| p_path.iter())
168216
.filter_map(|parent_path| {
169217
if children.contains(parent_path) {
170218
Some(())
@@ -185,10 +233,14 @@ fn resolve_paths<T: Config>(
185233
.count();
186234

187235
ensure!(
188-
matched_count == children.len(),
236+
matched_paths == children.len(),
189237
Error::<T>::ParentPermissionNotFound
190238
);
191239

240+
for child_path in &children {
241+
check_namespace_delegation_depth::<T>(child_path, Some(parent_pid), 1)?;
242+
}
243+
192244
children
193245
} else {
194246
children

pallets/permission0/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,8 @@ pub mod pallet {
324324
RevocationTermsTooStrong,
325325
/// Too many curator permissions being delegated in a single permission.
326326
TooManyCuratorPermissions,
327+
/// Namespace delegation depth exceeded the maximum allowed limit.
328+
DelegationDepthExceeded,
327329
}
328330

329331
#[pallet::hooks]

pallets/permission0/tests/namespace.rs

Lines changed: 75 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#![allow(clippy::indexing_slicing)]
1+
#![allow(clippy::indexing_slicing, clippy::arithmetic_side_effects)]
22

33
use pallet_permission0::{
44
CuratorPermissions, Error, Pallet, PermissionDuration, PermissionScope, Permissions,
@@ -15,13 +15,15 @@ use test_utils::*;
1515

1616
fn register_agent(id: AccountId) {
1717
let name = match id {
18-
0 => &b"alice"[..],
19-
1 => &b"bob"[..],
20-
2 => &b"charlie"[..],
21-
3 => &b"dave"[..],
22-
4 => &b"eve"[..],
23-
_ => &b"foo"[..],
18+
0 => "alice".to_string(),
19+
1 => "bob".to_string(),
20+
2 => "charlie".to_string(),
21+
3 => "dave".to_string(),
22+
4 => "eve".to_string(),
23+
5 => "ferdie".to_string(),
24+
_ => format!("agent-{id}"),
2425
};
26+
let name = name.as_bytes();
2527

2628
Balances::force_set_balance(RawOrigin::Root.into(), id, u128::MAX).unwrap();
2729
Torus0::register_agent(get_origin(id), name.to_vec(), name.to_vec(), name.to_vec()).unwrap();
@@ -1572,3 +1574,69 @@ fn delegate_namespace_permission_irrevocable_parent_allows_revocable_after() {
15721574
assert_eq!(PermissionsByDelegator::<Test>::get(bob).len(), 1);
15731575
});
15741576
}
1577+
1578+
#[test]
1579+
fn delegate_namespace_permission_fails_when_exceeding_depth_limit() {
1580+
new_test_ext().execute_with(|| {
1581+
zero_min_burn();
1582+
1583+
for i in 0..7 {
1584+
register_agent(i);
1585+
}
1586+
1587+
let level1 = register_namespace(0, b"agent.alice.compute");
1588+
let level2 = register_namespace(0, b"agent.alice.compute.gpu");
1589+
let level3 = register_namespace(0, b"agent.alice.compute.gpu.h100");
1590+
let level4 = register_namespace(0, b"agent.alice.compute.gpu.h100.cluster");
1591+
let level5 = register_namespace(0, b"agent.alice.compute.gpu.h100.cluster.node01");
1592+
let level6 = register_namespace(0, b"agent.alice.compute.gpu.h100.cluster.node01.core01");
1593+
1594+
let mut parent_permission_id = None;
1595+
let namespaces = [level1, level2, level3, level4, level5.clone()];
1596+
1597+
for (i, namespace) in namespaces.iter().enumerate() {
1598+
let delegator = i as u64 as u32;
1599+
let recipient = (i + 1) as u64 as u32;
1600+
1601+
let mut paths = BoundedBTreeMap::new();
1602+
let mut namespace_set = BoundedBTreeSet::new();
1603+
namespace_set.try_insert(namespace.clone()).unwrap();
1604+
paths
1605+
.try_insert(parent_permission_id, namespace_set)
1606+
.unwrap();
1607+
1608+
dbg!(recipient);
1609+
assert_ok!(Permission0::delegate_namespace_permission(
1610+
get_origin(delegator),
1611+
recipient,
1612+
paths,
1613+
PermissionDuration::Indefinite,
1614+
RevocationTerms::RevocableByDelegator,
1615+
10
1616+
));
1617+
1618+
parent_permission_id = Some(PermissionsByDelegator::<Test>::get(delegator)[0]);
1619+
}
1620+
1621+
for to_fail in [level5, level6] {
1622+
let mut paths = BoundedBTreeMap::new();
1623+
let mut namespace_set = BoundedBTreeSet::new();
1624+
namespace_set.try_insert(to_fail).unwrap();
1625+
paths
1626+
.try_insert(parent_permission_id, namespace_set)
1627+
.unwrap();
1628+
1629+
assert_err!(
1630+
Permission0::delegate_namespace_permission(
1631+
get_origin(5),
1632+
6,
1633+
paths,
1634+
PermissionDuration::Indefinite,
1635+
RevocationTerms::RevocableByDelegator,
1636+
10
1637+
),
1638+
Error::<Test>::DelegationDepthExceeded
1639+
);
1640+
}
1641+
});
1642+
}

0 commit comments

Comments
 (0)