⚠️ VeridianOS Kernel Documentation - This is low-level kernel code. All functions are unsafe unless explicitly marked otherwise. no_std

veridian_kernel/verification/
cap_proofs.rs

1#![allow(unexpected_cfgs)]
2//! Capability Formal Model
3//!
4//! Formal verification of the capability system: non-forgery, rights
5//! monotonicity (derivation produces subsets), cascading revocation,
6//! generation-based invalidation, and cross-address-space isolation.
7
8#![allow(dead_code)]
9
10#[cfg(feature = "alloc")]
11use alloc::collections::BTreeMap;
12#[cfg(feature = "alloc")]
13use alloc::vec::Vec;
14
15/// Rights bitmask constants
16pub const CAP_RIGHT_READ: u32 = 1 << 0;
17pub const CAP_RIGHT_WRITE: u32 = 1 << 1;
18pub const CAP_RIGHT_EXECUTE: u32 = 1 << 2;
19pub const CAP_RIGHT_GRANT: u32 = 1 << 3;
20pub const CAP_RIGHT_REVOKE: u32 = 1 << 4;
21pub const CAP_RIGHT_MAP: u32 = 1 << 5;
22pub const CAP_RIGHT_DERIVE: u32 = 1 << 6;
23pub const CAP_ALL_RIGHTS: u32 = 0x7F;
24
25/// Model of a capability token
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub struct CapabilityModel {
28    /// Unique token value
29    pub token: u64,
30    /// Rights bitmask
31    pub rights: u32,
32    /// Owner process ID
33    pub owner: u64,
34    /// Generation counter (incremented on revocation)
35    pub generation: u32,
36    /// Parent token (0 = root capability)
37    pub parent_token: u64,
38    /// Address space this capability belongs to
39    pub address_space: u64,
40}
41
42impl CapabilityModel {
43    /// Encode capability fields into a single u64 token
44    ///
45    /// Layout: [generation:16][rights:16][index:32]
46    pub fn encode(index: u32, rights: u32, generation: u32) -> u64 {
47        let gen = (generation as u64 & 0xFFFF) << 48;
48        let rts = ((rights as u64) & 0xFFFF) << 32;
49        let idx = index as u64;
50        gen | rts | idx
51    }
52
53    /// Decode a token into (index, rights, generation)
54    pub fn decode(token: u64) -> (u32, u32, u32) {
55        let generation = ((token >> 48) & 0xFFFF) as u32;
56        let rights = ((token >> 32) & 0xFFFF) as u32;
57        let index = (token & 0xFFFF_FFFF) as u32;
58        (index, rights, generation)
59    }
60
61    /// Derive a child capability with a subset of rights
62    pub fn derive(
63        &self,
64        child_token: u64,
65        child_rights: u32,
66        child_owner: u64,
67    ) -> Result<CapabilityModel, CapModelError> {
68        // Rights can only be reduced, never expanded
69        if child_rights & !self.rights != 0 {
70            return Err(CapModelError::RightsEscalation);
71        }
72
73        // Must have derive right
74        if self.rights & CAP_RIGHT_DERIVE == 0 {
75            return Err(CapModelError::NoDeriveRight);
76        }
77
78        Ok(CapabilityModel {
79            token: child_token,
80            rights: child_rights,
81            owner: child_owner,
82            generation: self.generation,
83            parent_token: self.token,
84            address_space: child_owner, // Each process has its own address space
85        })
86    }
87
88    /// Check if this capability has specific rights
89    pub fn has_rights(&self, required: u32) -> bool {
90        self.rights & required == required
91    }
92}
93
94/// Capability space model (per-process capability table)
95#[derive(Debug, Clone, Default)]
96pub struct CapSpaceModel {
97    /// All capabilities indexed by token
98    #[cfg(feature = "alloc")]
99    capabilities: BTreeMap<u64, CapabilityModel>,
100    /// Parent-child relationships for revocation
101    #[cfg(feature = "alloc")]
102    children: BTreeMap<u64, Vec<u64>>,
103    /// Revocation log
104    #[cfg(feature = "alloc")]
105    revocation_log: Vec<u64>,
106    /// Current generation for new capabilities
107    current_generation: u32,
108    /// Next token counter (kernel-controlled)
109    next_token: u64,
110}
111
112#[cfg(feature = "alloc")]
113impl CapSpaceModel {
114    /// Create a new capability space
115    pub fn new() -> Self {
116        Self {
117            capabilities: BTreeMap::new(),
118            children: BTreeMap::new(),
119            revocation_log: Vec::new(),
120            current_generation: 0,
121            next_token: 1, // Token 0 is reserved
122        }
123    }
124
125    /// Create a root capability (only the kernel can do this)
126    pub fn create_root(&mut self, rights: u32, owner: u64) -> CapabilityModel {
127        let token = self.next_token;
128        self.next_token += 1;
129
130        let cap = CapabilityModel {
131            token,
132            rights,
133            owner,
134            generation: self.current_generation,
135            parent_token: 0,
136            address_space: owner,
137        };
138
139        self.capabilities.insert(token, cap);
140        cap
141    }
142
143    /// Derive a child capability from a parent
144    pub fn derive(
145        &mut self,
146        parent_token: u64,
147        child_rights: u32,
148        child_owner: u64,
149    ) -> Result<CapabilityModel, CapModelError> {
150        let parent = self
151            .capabilities
152            .get(&parent_token)
153            .ok_or(CapModelError::InvalidToken)?;
154
155        let child_token = self.next_token;
156        self.next_token += 1;
157
158        let child = parent.derive(child_token, child_rights, child_owner)?;
159
160        self.capabilities.insert(child_token, child);
161        self.children
162            .entry(parent_token)
163            .or_default()
164            .push(child_token);
165
166        Ok(child)
167    }
168
169    /// Revoke a capability and all its descendants
170    pub fn revoke(&mut self, token: u64) -> Result<usize, CapModelError> {
171        if !self.capabilities.contains_key(&token) {
172            return Err(CapModelError::InvalidToken);
173        }
174
175        let mut revoked = Vec::new();
176        self.collect_descendants(token, &mut revoked);
177        revoked.push(token);
178
179        let count = revoked.len();
180        for t in &revoked {
181            self.capabilities.remove(t);
182            self.children.remove(t);
183            self.revocation_log.push(*t);
184        }
185
186        // Bump generation to invalidate any stale references
187        self.current_generation = self.current_generation.saturating_add(1);
188
189        Ok(count)
190    }
191
192    /// Collect all descendant tokens recursively
193    fn collect_descendants(&self, token: u64, result: &mut Vec<u64>) {
194        if let Some(children) = self.children.get(&token) {
195            for &child in children {
196                result.push(child);
197                self.collect_descendants(child, result);
198            }
199        }
200    }
201
202    /// Look up a capability by token
203    pub fn lookup(&self, token: u64) -> Option<&CapabilityModel> {
204        self.capabilities.get(&token)
205    }
206
207    /// Validate that a token is still valid (correct generation)
208    pub fn validate(
209        &self,
210        token: u64,
211        expected_gen: u32,
212    ) -> Result<&CapabilityModel, CapModelError> {
213        let cap = self
214            .capabilities
215            .get(&token)
216            .ok_or(CapModelError::InvalidToken)?;
217        if cap.generation != expected_gen {
218            return Err(CapModelError::StaleGeneration);
219        }
220        Ok(cap)
221    }
222
223    /// Get the number of capabilities
224    pub fn count(&self) -> usize {
225        self.capabilities.len()
226    }
227
228    /// Get the revocation log
229    pub fn revocation_log(&self) -> &[u64] {
230        &self.revocation_log
231    }
232}
233
234/// Capability invariant checker
235pub struct CapInvariantChecker;
236
237#[cfg(feature = "alloc")]
238impl CapInvariantChecker {
239    /// Verify non-forgery: capabilities can only be created through the kernel
240    /// API
241    ///
242    /// A random u64 should not match any valid capability in the space.
243    pub fn verify_non_forgery(
244        space: &CapSpaceModel,
245        random_token: u64,
246    ) -> Result<(), CapModelError> {
247        // If the random token happens to be in the space, that's only valid if
248        // it was created through create_root() or derive() (tracked by next_token)
249        if random_token >= space.next_token && space.capabilities.contains_key(&random_token) {
250            return Err(CapModelError::ForgeryDetected);
251        }
252        Ok(())
253    }
254
255    /// Verify rights monotonicity: derived capabilities have subset of parent
256    /// rights
257    pub fn verify_rights_monotonicity(space: &CapSpaceModel) -> Result<(), CapModelError> {
258        for (_, cap) in space.capabilities.iter() {
259            if cap.parent_token != 0 {
260                if let Some(parent) = space.capabilities.get(&cap.parent_token) {
261                    if cap.rights & !parent.rights != 0 {
262                        return Err(CapModelError::RightsEscalation);
263                    }
264                }
265            }
266        }
267        Ok(())
268    }
269
270    /// Verify revocation completeness: revoking a parent removes all children
271    pub fn verify_revocation_completeness(
272        space: &CapSpaceModel,
273        revoked_token: u64,
274    ) -> Result<(), CapModelError> {
275        // After revocation, neither the token nor any descendants should exist
276        if space.capabilities.contains_key(&revoked_token) {
277            return Err(CapModelError::RevocationIncomplete);
278        }
279
280        // Check no children reference the revoked token as parent
281        for (_, cap) in space.capabilities.iter() {
282            if cap.parent_token == revoked_token {
283                return Err(CapModelError::OrphanCapability);
284            }
285        }
286
287        Ok(())
288    }
289
290    /// Verify generation integrity: bumping generation invalidates old tokens
291    pub fn verify_generation_integrity(
292        space: &CapSpaceModel,
293        old_gen: u32,
294    ) -> Result<(), CapModelError> {
295        if space.current_generation <= old_gen {
296            return Err(CapModelError::StaleGeneration);
297        }
298        Ok(())
299    }
300}
301
302/// Errors from capability verification
303#[derive(Debug, Clone, Copy, PartialEq, Eq)]
304pub enum CapModelError {
305    /// Token is not valid
306    InvalidToken,
307    /// Attempted rights escalation
308    RightsEscalation,
309    /// No derive right on parent
310    NoDeriveRight,
311    /// Token generation is stale
312    StaleGeneration,
313    /// Forged capability detected
314    ForgeryDetected,
315    /// Revocation did not complete
316    RevocationIncomplete,
317    /// Orphan capability found after revocation
318    OrphanCapability,
319    /// Capability crossed address space boundary
320    IsolationBreach,
321}
322
323// ============================================================================
324// Kani Proof Harnesses
325// ============================================================================
326
327#[cfg(kani)]
328mod kani_proofs {
329    use super::*;
330
331    /// Proof: Token encoding and decoding is a roundtrip
332    #[kani::proof]
333    fn proof_token_encoding_roundtrip() {
334        let index: u32 = kani::any();
335        let rights: u32 = kani::any();
336        let generation: u32 = kani::any();
337
338        // Constrain to valid bit widths
339        kani::assume(rights <= 0xFFFF);
340        kani::assume(generation <= 0xFFFF);
341
342        let encoded = CapabilityModel::encode(index, rights, generation);
343        let (dec_index, dec_rights, dec_gen) = CapabilityModel::decode(encoded);
344
345        assert_eq!(dec_index, index);
346        assert_eq!(dec_rights, rights);
347        assert_eq!(dec_gen, generation);
348    }
349
350    /// Proof: A random u64 that was not created via the API is not a valid
351    /// capability
352    #[kani::proof]
353    fn proof_no_forgery() {
354        let mut space = CapSpaceModel::new();
355        space.create_root(CAP_ALL_RIGHTS, 1);
356
357        let random: u64 = kani::any();
358        kani::assume(random >= space.next_token); // Not a valid token
359
360        assert!(space.lookup(random).is_none());
361    }
362
363    /// Proof: Derived capability rights are always a subset of parent rights
364    #[kani::proof]
365    fn proof_derivation_subset() {
366        let parent_rights: u32 = kani::any();
367        kani::assume(parent_rights & CAP_RIGHT_DERIVE != 0); // Must have derive right
368        kani::assume(parent_rights <= CAP_ALL_RIGHTS);
369
370        let child_rights: u32 = kani::any();
371        kani::assume(child_rights <= CAP_ALL_RIGHTS);
372
373        let parent = CapabilityModel {
374            token: 1,
375            rights: parent_rights,
376            owner: 1,
377            generation: 0,
378            parent_token: 0,
379            address_space: 1,
380        };
381
382        let result = parent.derive(2, child_rights, 2);
383
384        match result {
385            Ok(child) => {
386                // If derivation succeeded, child rights must be subset
387                assert_eq!(child.rights & !parent.rights, 0);
388            }
389            Err(CapModelError::RightsEscalation) => {
390                // If it failed, child had rights not in parent
391                assert!(child_rights & !parent_rights != 0);
392            }
393            _ => panic!("Unexpected error"),
394        }
395    }
396
397    /// Proof: Revoking a parent revokes all descendants
398    #[kani::proof]
399    fn proof_cascading_revocation() {
400        let mut space = CapSpaceModel::new();
401
402        let root = space.create_root(CAP_ALL_RIGHTS, 1);
403        let child = space.derive(root.token, CAP_RIGHT_READ, 2).unwrap();
404        let grandchild = space.derive(child.token, CAP_RIGHT_READ, 3).unwrap();
405
406        let root_token = root.token;
407        let child_token = child.token;
408        let grandchild_token = grandchild.token;
409
410        space.revoke(root_token).unwrap();
411
412        assert!(space.lookup(root_token).is_none());
413        assert!(space.lookup(child_token).is_none());
414        assert!(space.lookup(grandchild_token).is_none());
415    }
416
417    /// Proof: Bumping generation invalidates old tokens
418    #[kani::proof]
419    fn proof_generation_invalidation() {
420        let mut space = CapSpaceModel::new();
421
422        let cap = space.create_root(CAP_ALL_RIGHTS, 1);
423        let old_gen = cap.generation;
424
425        space.revoke(cap.token).unwrap();
426
427        // Generation must have been bumped
428        assert!(space.current_generation > old_gen);
429    }
430
431    /// Proof: AND/OR/NOT on rights bitmask produces correct results
432    #[kani::proof]
433    fn proof_rights_mask_operations() {
434        let r1: u32 = kani::any();
435        let r2: u32 = kani::any();
436        kani::assume(r1 <= CAP_ALL_RIGHTS);
437        kani::assume(r2 <= CAP_ALL_RIGHTS);
438
439        // AND: intersection
440        let intersection = r1 & r2;
441        assert_eq!(intersection & !r1, 0);
442        assert_eq!(intersection & !r2, 0);
443
444        // OR: union
445        let union = r1 | r2;
446        assert_eq!(union & r1, r1);
447        assert_eq!(union & r2, r2);
448
449        // NOT + mask: complement within valid range
450        let complement = !r1 & CAP_ALL_RIGHTS;
451        assert_eq!(complement & r1, 0);
452    }
453
454    /// Proof: Capabilities don't cross address spaces
455    #[kani::proof]
456    fn proof_capability_isolation() {
457        let mut space = CapSpaceModel::new();
458
459        let cap1 = space.create_root(CAP_ALL_RIGHTS, 1);
460        let cap2 = space.create_root(CAP_ALL_RIGHTS, 2);
461
462        assert_eq!(cap1.address_space, 1);
463        assert_eq!(cap2.address_space, 2);
464        assert_ne!(cap1.address_space, cap2.address_space);
465    }
466
467    /// Proof: No orphan capabilities remain after revocation
468    #[kani::proof]
469    fn proof_revocation_completeness() {
470        let mut space = CapSpaceModel::new();
471
472        let root = space.create_root(CAP_ALL_RIGHTS, 1);
473        let _child = space
474            .derive(root.token, CAP_RIGHT_READ | CAP_RIGHT_DERIVE, 2)
475            .unwrap();
476
477        let root_token = root.token;
478        space.revoke(root_token).unwrap();
479
480        // Verify completeness
481        assert!(CapInvariantChecker::verify_revocation_completeness(&space, root_token).is_ok());
482    }
483}
484
485// ============================================================================
486// Unit Tests
487// ============================================================================
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492
493    #[test]
494    fn test_token_encode_decode() {
495        let encoded = CapabilityModel::encode(42, 0x1F, 3);
496        let (idx, rights, gen) = CapabilityModel::decode(encoded);
497        assert_eq!(idx, 42);
498        assert_eq!(rights, 0x1F);
499        assert_eq!(gen, 3);
500    }
501
502    #[test]
503    fn test_has_rights() {
504        let cap = CapabilityModel {
505            token: 1,
506            rights: CAP_RIGHT_READ | CAP_RIGHT_WRITE,
507            owner: 1,
508            generation: 0,
509            parent_token: 0,
510            address_space: 1,
511        };
512
513        assert!(cap.has_rights(CAP_RIGHT_READ));
514        assert!(cap.has_rights(CAP_RIGHT_WRITE));
515        assert!(cap.has_rights(CAP_RIGHT_READ | CAP_RIGHT_WRITE));
516        assert!(!cap.has_rights(CAP_RIGHT_EXECUTE));
517    }
518
519    #[test]
520    fn test_derive_subset() {
521        let parent = CapabilityModel {
522            token: 1,
523            rights: CAP_RIGHT_READ | CAP_RIGHT_WRITE | CAP_RIGHT_DERIVE,
524            owner: 1,
525            generation: 0,
526            parent_token: 0,
527            address_space: 1,
528        };
529
530        let child = parent.derive(2, CAP_RIGHT_READ, 2).unwrap();
531        assert_eq!(child.rights, CAP_RIGHT_READ);
532        assert_eq!(child.parent_token, 1);
533    }
534
535    #[test]
536    fn test_derive_escalation_fails() {
537        let parent = CapabilityModel {
538            token: 1,
539            rights: CAP_RIGHT_READ | CAP_RIGHT_DERIVE,
540            owner: 1,
541            generation: 0,
542            parent_token: 0,
543            address_space: 1,
544        };
545
546        let result = parent.derive(2, CAP_RIGHT_WRITE, 2);
547        assert_eq!(result, Err(CapModelError::RightsEscalation));
548    }
549
550    #[test]
551    fn test_derive_without_derive_right() {
552        let parent = CapabilityModel {
553            token: 1,
554            rights: CAP_RIGHT_READ,
555            owner: 1,
556            generation: 0,
557            parent_token: 0,
558            address_space: 1,
559        };
560
561        let result = parent.derive(2, CAP_RIGHT_READ, 2);
562        assert_eq!(result, Err(CapModelError::NoDeriveRight));
563    }
564
565    #[cfg(feature = "alloc")]
566    #[test]
567    fn test_capspace_create_and_lookup() {
568        let mut space = CapSpaceModel::new();
569        let cap = space.create_root(CAP_ALL_RIGHTS, 1);
570        assert!(space.lookup(cap.token).is_some());
571        assert_eq!(space.count(), 1);
572    }
573
574    #[cfg(feature = "alloc")]
575    #[test]
576    fn test_capspace_derive() {
577        let mut space = CapSpaceModel::new();
578        let root = space.create_root(CAP_ALL_RIGHTS, 1);
579        let child = space.derive(root.token, CAP_RIGHT_READ, 2).unwrap();
580        assert_eq!(child.rights, CAP_RIGHT_READ);
581        assert_eq!(space.count(), 2);
582    }
583
584    #[cfg(feature = "alloc")]
585    #[test]
586    fn test_cascading_revocation() {
587        let mut space = CapSpaceModel::new();
588        let root = space.create_root(CAP_ALL_RIGHTS, 1);
589        let child = space
590            .derive(root.token, CAP_RIGHT_READ | CAP_RIGHT_DERIVE, 2)
591            .unwrap();
592        let _grandchild = space.derive(child.token, CAP_RIGHT_READ, 3).unwrap();
593
594        assert_eq!(space.count(), 3);
595
596        let revoked = space.revoke(root.token).unwrap();
597        assert_eq!(revoked, 3); // root + child + grandchild
598        assert_eq!(space.count(), 0);
599    }
600
601    #[cfg(feature = "alloc")]
602    #[test]
603    fn test_generation_bump() {
604        let mut space = CapSpaceModel::new();
605        let gen_before = space.current_generation;
606
607        let cap = space.create_root(CAP_ALL_RIGHTS, 1);
608        space.revoke(cap.token).unwrap();
609
610        assert!(space.current_generation > gen_before);
611    }
612
613    #[cfg(feature = "alloc")]
614    #[test]
615    fn test_rights_monotonicity() {
616        let mut space = CapSpaceModel::new();
617        let root = space.create_root(CAP_ALL_RIGHTS, 1);
618        let _child = space.derive(root.token, CAP_RIGHT_READ, 2).unwrap();
619
620        assert!(CapInvariantChecker::verify_rights_monotonicity(&space).is_ok());
621    }
622}