Skip to main content

rvm_cap/
grant.rs

1//! Capability granting with monotonic attenuation.
2//!
3//! Implements the grant semantics from ADR-135:
4//! - Source must hold GRANT right
5//! - Derived rights must be a subset of source rights
6//! - Delegation depth is enforced (max 8)
7
8use crate::error::{CapError, CapResult};
9use crate::table::CapSlot;
10use crate::DEFAULT_MAX_DELEGATION_DEPTH;
11use rvm_types::{CapRights, CapToken};
12
13/// Policy configuration for capability grants.
14#[derive(Debug, Clone, Copy)]
15pub struct GrantPolicy {
16    /// Maximum delegation depth allowed.
17    pub max_depth: u8,
18    /// Whether `GRANT_ONCE` capabilities are allowed.
19    pub allow_grant_once: bool,
20}
21
22impl GrantPolicy {
23    /// Creates a grant policy with default settings.
24    #[must_use]
25    pub const fn new() -> Self {
26        Self {
27            max_depth: DEFAULT_MAX_DELEGATION_DEPTH,
28            allow_grant_once: true,
29        }
30    }
31
32    /// Creates a grant policy with a custom depth limit.
33    #[must_use]
34    pub const fn with_max_depth(max_depth: u8) -> Self {
35        Self {
36            max_depth,
37            allow_grant_once: true,
38        }
39    }
40}
41
42impl Default for GrantPolicy {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48/// Validates a grant request and produces the derived token.
49///
50/// If the source has `GRANT_ONCE` (but not `GRANT`), `consume_grant_once`
51/// is set to `true` in the return value so the caller can strip the right
52/// from the source slot.
53///
54/// Returns `(derived_token, depth, consume_grant_once)` on success.
55pub fn validate_grant(
56    source: &CapSlot,
57    requested_rights: CapRights,
58    new_id: u64,
59    badge: u64,
60    epoch: u32,
61    policy: GrantPolicy,
62) -> CapResult<(CapToken, u8, bool)> {
63    let source_rights = source.token.rights();
64
65    let has_grant = source_rights.contains(CapRights::GRANT);
66    let has_grant_once = policy.allow_grant_once && source_rights.contains(CapRights::GRANT_ONCE);
67
68    // Source must hold GRANT or GRANT_ONCE to delegate.
69    if !has_grant && !has_grant_once {
70        return Err(CapError::GrantNotPermitted);
71    }
72
73    // Monotonic attenuation: requested must be a subset of source.
74    if !source_rights.contains(requested_rights) {
75        return Err(CapError::RightsEscalation);
76    }
77
78    // Delegation depth check with overflow protection.
79    let new_depth = source
80        .depth
81        .checked_add(1)
82        .ok_or(CapError::DelegationDepthExceeded)?;
83    if new_depth > policy.max_depth {
84        return Err(CapError::DelegationDepthExceeded);
85    }
86
87    let _ = badge; // Badge is carried by the slot, not the token.
88
89    let derived_token = CapToken::new(new_id, source.token.cap_type(), requested_rights, epoch);
90
91    // Signal that GRANT_ONCE should be consumed if it was the only
92    // grant authority (source has GRANT_ONCE but not GRANT).
93    let consume = !has_grant && has_grant_once;
94
95    Ok((derived_token, new_depth, consume))
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use rvm_types::{CapType, PartitionId};
102
103    fn make_source(rights: CapRights, depth: u8) -> CapSlot {
104        CapSlot {
105            token: CapToken::new(1, CapType::Region, rights, 0),
106            generation: 1,
107            owner: PartitionId::new(1),
108            depth,
109            parent_index: u32::MAX,
110            badge: 0,
111        }
112    }
113
114    fn all_rights() -> CapRights {
115        CapRights::READ
116            .union(CapRights::WRITE)
117            .union(CapRights::EXECUTE)
118            .union(CapRights::GRANT)
119            .union(CapRights::REVOKE)
120    }
121
122    #[test]
123    fn test_valid_grant() {
124        let source = make_source(all_rights(), 0);
125        let policy = GrantPolicy::new();
126        let (token, depth, consume) =
127            validate_grant(&source, CapRights::READ, 10, 42, 0, policy).unwrap();
128        assert_eq!(token.rights(), CapRights::READ);
129        assert_eq!(depth, 1);
130        assert!(!consume); // Source has full GRANT, so GRANT_ONCE is not consumed.
131    }
132
133    #[test]
134    fn test_grant_without_grant_right() {
135        let source = make_source(CapRights::READ, 0);
136        let policy = GrantPolicy::new();
137        let result = validate_grant(&source, CapRights::READ, 10, 0, 0, policy);
138        assert_eq!(result, Err(CapError::GrantNotPermitted));
139    }
140
141    #[test]
142    fn test_rights_escalation() {
143        let source = make_source(CapRights::READ.union(CapRights::GRANT), 0);
144        let policy = GrantPolicy::new();
145        let result = validate_grant(&source, CapRights::WRITE, 10, 0, 0, policy);
146        assert_eq!(result, Err(CapError::RightsEscalation));
147    }
148
149    #[test]
150    fn test_depth_limit() {
151        let source = make_source(all_rights(), 8);
152        let policy = GrantPolicy::new();
153        let result = validate_grant(&source, CapRights::READ, 10, 0, 0, policy);
154        assert_eq!(result, Err(CapError::DelegationDepthExceeded));
155    }
156
157    #[test]
158    fn test_grant_preserves_type() {
159        let source = CapSlot {
160            token: CapToken::new(1, CapType::CommEdge, all_rights(), 5),
161            generation: 1,
162            owner: PartitionId::new(1),
163            depth: 0,
164            parent_index: u32::MAX,
165            badge: 0,
166        };
167        let policy = GrantPolicy::new();
168        let (token, _, _) = validate_grant(&source, CapRights::READ, 10, 0, 5, policy).unwrap();
169        assert_eq!(token.cap_type(), CapType::CommEdge);
170        assert_eq!(token.epoch(), 5);
171    }
172
173    #[test]
174    fn test_grant_at_max_minus_one() {
175        let source = make_source(all_rights(), 7);
176        let policy = GrantPolicy::new();
177        let (_, depth, _) = validate_grant(&source, CapRights::READ, 10, 0, 0, policy).unwrap();
178        assert_eq!(depth, 8);
179    }
180
181    #[test]
182    fn test_grant_once_consumed() {
183        // Source has GRANT_ONCE but not GRANT.
184        let rights = CapRights::READ.union(CapRights::GRANT_ONCE);
185        let source = make_source(rights, 0);
186        let policy = GrantPolicy::new();
187        let (token, depth, consume) =
188            validate_grant(&source, CapRights::READ, 10, 0, 0, policy).unwrap();
189        assert_eq!(token.rights(), CapRights::READ);
190        assert_eq!(depth, 1);
191        assert!(consume); // GRANT_ONCE should be consumed.
192    }
193
194    #[test]
195    fn test_grant_once_not_consumed_when_grant_also_present() {
196        // Source has both GRANT and GRANT_ONCE -- GRANT takes precedence.
197        let rights = CapRights::READ
198            .union(CapRights::GRANT)
199            .union(CapRights::GRANT_ONCE);
200        let source = make_source(rights, 0);
201        let policy = GrantPolicy::new();
202        let (_, _, consume) = validate_grant(&source, CapRights::READ, 10, 0, 0, policy).unwrap();
203        assert!(!consume);
204    }
205
206    #[test]
207    fn test_depth_overflow_protection() {
208        let source = make_source(all_rights(), u8::MAX);
209        let policy = GrantPolicy::with_max_depth(u8::MAX);
210        let result = validate_grant(&source, CapRights::READ, 10, 0, 0, policy);
211        assert_eq!(result, Err(CapError::DelegationDepthExceeded));
212    }
213}