Skip to main content

nucleus/security/
state.rs

1use crate::error::StateTransition;
2
3/// Security state machine matching Nucleus_Security_SecurityEnforcement.tla
4///
5/// State transitions:
6/// privileged -> capabilities_dropped -> seccomp_applied -> landlock_applied -> locked
7///
8/// Properties verified by TLA+ model:
9/// - irreversible_lockdown: Once security layers are applied, can only move forward to locked
10/// - defense_in_depth: Locked state requires capabilities dropped, seccomp applied, and landlock applied
11/// - no_privilege_escalation: Cannot return to privileged state after dropping capabilities
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum SecurityState {
14    /// Initial state with all privileges
15    Privileged,
16    /// Capabilities have been dropped
17    CapabilitiesDropped,
18    /// Seccomp filter has been applied
19    SeccompApplied,
20    /// Landlock filesystem policy has been applied
21    LandlockApplied,
22    /// Final locked state - no further security changes possible
23    Locked,
24}
25
26impl StateTransition for SecurityState {
27    fn can_transition_to(&self, next: &SecurityState) -> bool {
28        use SecurityState::*;
29
30        matches!(
31            (self, next),
32            (Privileged, CapabilitiesDropped)
33                | (CapabilitiesDropped, SeccompApplied)
34                | (SeccompApplied, LandlockApplied)
35                | (LandlockApplied, Locked)
36                | (Privileged, Privileged)
37                | (CapabilitiesDropped, CapabilitiesDropped)
38                | (SeccompApplied, SeccompApplied)
39                | (LandlockApplied, LandlockApplied)
40                | (Locked, Locked)
41        )
42    }
43
44    fn is_terminal(&self) -> bool {
45        matches!(self, SecurityState::Locked)
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_valid_transitions() {
55        assert!(SecurityState::Privileged.can_transition_to(&SecurityState::CapabilitiesDropped));
56        assert!(
57            SecurityState::CapabilitiesDropped.can_transition_to(&SecurityState::SeccompApplied)
58        );
59        assert!(SecurityState::SeccompApplied.can_transition_to(&SecurityState::LandlockApplied));
60        assert!(SecurityState::LandlockApplied.can_transition_to(&SecurityState::Locked));
61    }
62
63    #[test]
64    fn test_self_transitions() {
65        assert!(SecurityState::Privileged.can_transition_to(&SecurityState::Privileged));
66        assert!(
67            SecurityState::CapabilitiesDropped
68                .can_transition_to(&SecurityState::CapabilitiesDropped)
69        );
70        assert!(SecurityState::SeccompApplied.can_transition_to(&SecurityState::SeccompApplied));
71        assert!(SecurityState::LandlockApplied.can_transition_to(&SecurityState::LandlockApplied));
72        assert!(SecurityState::Locked.can_transition_to(&SecurityState::Locked));
73    }
74
75    #[test]
76    fn test_invalid_transitions() {
77        // Cannot skip states
78        assert!(!SecurityState::Privileged.can_transition_to(&SecurityState::SeccompApplied));
79        assert!(!SecurityState::Privileged.can_transition_to(&SecurityState::LandlockApplied));
80        assert!(!SecurityState::Privileged.can_transition_to(&SecurityState::Locked));
81        assert!(
82            !SecurityState::CapabilitiesDropped
83                .can_transition_to(&SecurityState::LandlockApplied)
84        );
85        assert!(!SecurityState::CapabilitiesDropped.can_transition_to(&SecurityState::Locked));
86        assert!(!SecurityState::SeccompApplied.can_transition_to(&SecurityState::Locked));
87
88        // Cannot go backwards (no privilege escalation)
89        assert!(!SecurityState::CapabilitiesDropped.can_transition_to(&SecurityState::Privileged));
90        assert!(!SecurityState::SeccompApplied.can_transition_to(&SecurityState::Privileged));
91        assert!(
92            !SecurityState::SeccompApplied.can_transition_to(&SecurityState::CapabilitiesDropped)
93        );
94        assert!(!SecurityState::LandlockApplied.can_transition_to(&SecurityState::Privileged));
95        assert!(
96            !SecurityState::LandlockApplied.can_transition_to(&SecurityState::CapabilitiesDropped)
97        );
98        assert!(!SecurityState::LandlockApplied.can_transition_to(&SecurityState::SeccompApplied));
99        assert!(!SecurityState::Locked.can_transition_to(&SecurityState::LandlockApplied));
100        assert!(!SecurityState::Locked.can_transition_to(&SecurityState::SeccompApplied));
101    }
102
103    #[test]
104    fn test_terminal_state() {
105        assert!(!SecurityState::Privileged.is_terminal());
106        assert!(!SecurityState::CapabilitiesDropped.is_terminal());
107        assert!(!SecurityState::SeccompApplied.is_terminal());
108        assert!(!SecurityState::LandlockApplied.is_terminal());
109        assert!(SecurityState::Locked.is_terminal());
110    }
111}