Skip to main content

rvm_cap/
revoke.rs

1//! Epoch-based capability revocation.
2//!
3//! Revocation propagates through the derivation tree, invalidating
4//! all descendants of the revoked capability.
5
6use crate::derivation::DerivationTree;
7use crate::error::{CapError, CapResult};
8use crate::table::CapabilityTable;
9
10/// Result of a revocation operation.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct RevokeResult {
13    /// Number of capabilities revoked (including the target).
14    pub revoked_count: usize,
15}
16
17impl RevokeResult {
18    /// Creates a new revoke result.
19    #[must_use]
20    pub const fn new(revoked_count: usize) -> Self {
21        Self { revoked_count }
22    }
23}
24
25/// Revokes a capability and propagates through the derivation tree.
26///
27/// Both the derivation tree and the capability table are updated:
28/// the tree marks nodes as invalid, and the table invalidates ALL
29/// corresponding slots (bumping generation counters) including
30/// every descendant in the derivation subtree.
31///
32/// # Security
33///
34/// It is critical that table invalidation covers ALL descendants,
35/// not just the root. Without this, a revoked child capability's
36/// table slot remains `is_valid: true` and would pass P1 verification.
37pub fn revoke_capability<const N: usize>(
38    table: &mut CapabilityTable<N>,
39    tree: &mut DerivationTree<N>,
40    index: u32,
41    generation: u32,
42) -> CapResult<RevokeResult> {
43    // Validate that the handle is still valid.
44    let _ = table.lookup(index, generation)?;
45
46    // Collect the set of indices that will be revoked by the tree walk.
47    // We must invalidate ALL of them in the table, not just the root.
48    let revoked_indices = tree.collect_subtree(index);
49
50    // Revoke in the derivation tree (marks descendants invalid).
51    let revoked = tree.revoke(index).map_err(|_| CapError::Revoked)?;
52
53    // Synchronize: invalidate ALL revoked slots in the table,
54    // including the root and every descendant.
55    for &idx in &revoked_indices {
56        table.force_invalidate(idx);
57    }
58
59    Ok(RevokeResult::new(revoked))
60}
61
62/// Revokes a single capability without propagation.
63///
64/// # Errors
65///
66/// Returns [`CapError::InvalidHandle`] if the handle is invalid.
67/// Returns [`CapError::StaleHandle`] if the generation does not match.
68pub fn revoke_single<const N: usize>(
69    table: &mut CapabilityTable<N>,
70    index: u32,
71    generation: u32,
72) -> CapResult<()> {
73    table.remove(index, generation)
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use rvm_types::{CapRights, CapToken, CapType, PartitionId};
80
81    fn all_rights() -> CapRights {
82        CapRights::READ
83            .union(CapRights::WRITE)
84            .union(CapRights::EXECUTE)
85            .union(CapRights::GRANT)
86            .union(CapRights::REVOKE)
87    }
88
89    #[test]
90    fn test_revoke_propagation() {
91        let mut table = CapabilityTable::<64>::new();
92        let mut tree = DerivationTree::<64>::new();
93        let owner = PartitionId::new(1);
94        let token = CapToken::new(1, CapType::Region, all_rights(), 0);
95
96        let (r_idx, r_gen) = table.insert_root(token, owner, 0).unwrap();
97        tree.add_root(r_idx, 0).unwrap();
98
99        let (c1_idx, _) = table.insert_derived(token, owner, 1, r_idx, 0).unwrap();
100        tree.add_child(r_idx, c1_idx, 1, 0).unwrap();
101
102        let (c2_idx, _) = table.insert_derived(token, owner, 1, r_idx, 0).unwrap();
103        tree.add_child(r_idx, c2_idx, 1, 0).unwrap();
104
105        let (gc_idx, _) = table.insert_derived(token, owner, 2, c1_idx, 0).unwrap();
106        tree.add_child(c1_idx, gc_idx, 2, 0).unwrap();
107
108        let result = revoke_capability(&mut table, &mut tree, r_idx, r_gen).unwrap();
109        assert_eq!(result.revoked_count, 4);
110
111        assert!(!tree.is_valid(r_idx));
112        assert!(!tree.is_valid(c1_idx));
113        assert!(!tree.is_valid(c2_idx));
114        assert!(!tree.is_valid(gc_idx));
115    }
116
117    #[test]
118    fn test_revoke_single() {
119        let mut table = CapabilityTable::<64>::new();
120        let owner = PartitionId::new(1);
121        let token = CapToken::new(1, CapType::Region, all_rights(), 0);
122
123        let (idx, gen) = table.insert_root(token, owner, 0).unwrap();
124        revoke_single(&mut table, idx, gen).unwrap();
125        assert!(table.lookup(idx, gen).is_err());
126    }
127
128    #[test]
129    fn test_revoke_invalid_handle() {
130        let mut table = CapabilityTable::<64>::new();
131        let mut tree = DerivationTree::<64>::new();
132        let result = revoke_capability(&mut table, &mut tree, 99, 0);
133        assert!(result.is_err());
134    }
135}