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

veridian_kernel/security/mac/
mod.rs

1//! Mandatory Access Control (MAC) system
2//!
3//! Provides a policy-based access control system similar to SELinux.
4//! Enforces security policies for all system operations.
5//!
6//! # Policy Language
7//!
8//! The MAC system supports a simple text-based policy language:
9//! ```text
10//! allow source_type target_type { read write execute };
11//! deny source_type target_type { write };
12//! type_transition source_type target_type : process new_type;
13//! role admin_r types { system_t init_t };
14//! user root roles { admin_r };
15//! sensitivity s0-s3;
16//! category c0-c63;
17//! ```
18//!
19//! # Multi-Level Security (MLS)
20//!
21//! MLS uses sensitivity levels (0..=65535) and category bitmasks (64 bits).
22//! A security level dominates another if its sensitivity is greater or equal
23//! AND its category set is a superset of the other's.
24//!
25//! # RBAC Layer
26//!
27//! Users are mapped to roles, and roles are mapped to types. A process
28//! running with a particular user identity can only transition into types
29//! allowed by that user's assigned roles.
30//!
31//! # Zero-Allocation Design
32//!
33//! All data structures use fixed-size arrays and `&'static str` references
34//! to avoid heap allocations. This is critical for boot-time initialization
35//! on architectures (RISC-V, AArch64) where the bump allocator cannot
36//! handle many small allocations without corruption.
37
38#![allow(clippy::needless_range_loop)]
39
40use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
41
42use spin::Mutex;
43
44use super::AccessType;
45use crate::error::KernelError;
46
47// ---------------------------------------------------------------------------
48// Constants
49// ---------------------------------------------------------------------------
50
51/// Maximum number of policy rules
52const MAX_POLICY_RULES: usize = 64;
53
54/// Maximum number of domain transitions
55const MAX_TRANSITIONS: usize = 32;
56
57/// Maximum number of roles
58const MAX_ROLES: usize = 8;
59
60/// Maximum number of types per role
61const MAX_ROLE_TYPES: usize = 16;
62
63/// Maximum number of user-to-role mappings
64const MAX_USER_ROLES: usize = 16;
65
66/// Maximum number of roles assigned to a single user
67const MAX_USER_ASSIGNED_ROLES: usize = 8;
68
69/// Maximum number of process security labels
70const MAX_PROCESS_LABELS: usize = 64;
71
72/// Maximum number of permissions per rule (Read, Write, Execute = 3 max)
73const MAX_PERMISSIONS: usize = 3;
74
75/// Maximum number of tokens the parser can handle
76const MAX_TOKENS: usize = 128;
77
78/// Maximum number of parsed rules from a single parse call
79const MAX_PARSED_RULES: usize = 32;
80
81/// Maximum number of parsed transitions from a single parse call
82const MAX_PARSED_TRANSITIONS: usize = 16;
83
84/// Maximum number of parsed roles from a single parse call
85const MAX_PARSED_ROLES: usize = 8;
86
87/// Maximum number of parsed user-role mappings from a single parse call
88const MAX_PARSED_USER_ROLES: usize = 8;
89
90// ---------------------------------------------------------------------------
91// Multi-Level Security (MLS)
92// ---------------------------------------------------------------------------
93
94/// MLS security level with sensitivity and category bitmask.
95///
96/// Dominance: level A dominates level B iff
97///   A.sensitivity >= B.sensitivity AND A.categories is a superset of
98/// B.categories.
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
100pub struct MlsLevel {
101    /// Sensitivity level (0 = lowest, higher = more sensitive)
102    pub sensitivity: u16,
103    /// Category bitmask (up to 64 categories)
104    pub categories: u64,
105}
106
107impl MlsLevel {
108    /// Create a new MLS level.
109    pub const fn new(sensitivity: u16, categories: u64) -> Self {
110        Self {
111            sensitivity,
112            categories,
113        }
114    }
115
116    /// Default (lowest) security level.
117    pub const fn default_level() -> Self {
118        Self {
119            sensitivity: 0,
120            categories: 0,
121        }
122    }
123
124    /// Check if this level dominates (is at least as restrictive as) `other`.
125    pub fn dominates(&self, other: &MlsLevel) -> bool {
126        self.sensitivity >= other.sensitivity
127            && (self.categories & other.categories) == other.categories
128    }
129}
130
131// ---------------------------------------------------------------------------
132// Security Label
133// ---------------------------------------------------------------------------
134
135/// Full security label combining type, role, and MLS level.
136///
137/// Uses `&'static str` references to avoid heap allocations.
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct SecurityLabel {
140    /// Type/domain name (e.g. "system_t", "user_t")
141    pub type_name: &'static str,
142    /// Role (e.g. "system_r", "user_r")
143    pub role: &'static str,
144    /// MLS security level
145    pub level: MlsLevel,
146}
147
148impl SecurityLabel {
149    /// Create a new security label.
150    pub const fn new(type_name: &'static str, role: &'static str, level: MlsLevel) -> Self {
151        Self {
152            type_name,
153            role,
154            level,
155        }
156    }
157
158    /// Create a label with default MLS level.
159    pub const fn simple(type_name: &'static str, role: &'static str) -> Self {
160        Self::new(type_name, role, MlsLevel::default_level())
161    }
162}
163
164// ---------------------------------------------------------------------------
165// Policy Rule
166// ---------------------------------------------------------------------------
167
168/// Action to take when a policy rule matches.
169#[derive(Debug, Clone, Copy, PartialEq, Eq)]
170pub enum PolicyAction {
171    Allow,
172    Deny,
173}
174
175/// Security policy rule.
176///
177/// Uses fixed-size arrays and `&'static str` to avoid heap allocations.
178#[derive(Debug, Clone, Copy)]
179pub struct PolicyRule {
180    /// Source domain/type
181    pub source_type: &'static str,
182    /// Target domain/type
183    pub target_type: &'static str,
184    /// Allowed/denied permission set (fixed-size array)
185    pub permissions: [Permission; MAX_PERMISSIONS],
186    /// Number of active permissions in the array
187    pub perm_count: u8,
188    /// Whether this rule allows or denies
189    pub action: PolicyAction,
190    /// Rule enabled
191    pub enabled: bool,
192}
193
194/// Permission types for policy rules.
195#[derive(Debug, Clone, Copy, PartialEq, Eq)]
196pub enum Permission {
197    Read,
198    Write,
199    Execute,
200}
201
202impl PolicyRule {
203    /// Create a new policy rule with the given action.
204    pub const fn new(
205        source_type: &'static str,
206        target_type: &'static str,
207        permissions: [Permission; MAX_PERMISSIONS],
208        perm_count: u8,
209        action: PolicyAction,
210    ) -> Self {
211        Self {
212            source_type,
213            target_type,
214            permissions,
215            perm_count,
216            action,
217            enabled: true,
218        }
219    }
220
221    /// Create a policy rule from a slice of permissions.
222    ///
223    /// Copies up to MAX_PERMISSIONS permissions into the fixed-size array.
224    pub fn from_perms(
225        source_type: &'static str,
226        target_type: &'static str,
227        perms: &[Permission],
228        action: PolicyAction,
229    ) -> Self {
230        let mut permissions = [Permission::Read; MAX_PERMISSIONS];
231        let count = perms.len().min(MAX_PERMISSIONS);
232        let mut i = 0;
233        while i < count {
234            permissions[i] = perms[i];
235            i += 1;
236        }
237        Self {
238            source_type,
239            target_type,
240            permissions,
241            perm_count: count as u8,
242            action,
243            enabled: true,
244        }
245    }
246
247    /// Create an Allow rule from a legacy bitmask for backward compatibility.
248    pub fn from_legacy(source: &'static str, target: &'static str, allowed: u8) -> Self {
249        let mut permissions = [Permission::Read; MAX_PERMISSIONS];
250        let mut count: u8 = 0;
251        if allowed & 0x1 != 0 {
252            permissions[count as usize] = Permission::Read;
253            count += 1;
254        }
255        if allowed & 0x2 != 0 {
256            permissions[count as usize] = Permission::Write;
257            count += 1;
258        }
259        if allowed & 0x4 != 0 {
260            permissions[count as usize] = Permission::Execute;
261            count += 1;
262        }
263        Self {
264            source_type: source,
265            target_type: target,
266            permissions,
267            perm_count: count,
268            action: PolicyAction::Allow,
269            enabled: true,
270        }
271    }
272
273    /// Check if this rule contains a specific permission.
274    fn has_permission(&self, perm: Permission) -> bool {
275        let mut i = 0;
276        while i < self.perm_count as usize {
277            if matches!(
278                (&self.permissions[i], &perm),
279                (Permission::Read, Permission::Read)
280                    | (Permission::Write, Permission::Write)
281                    | (Permission::Execute, Permission::Execute)
282            ) {
283                return true;
284            }
285            i += 1;
286        }
287        false
288    }
289
290    /// Check if this rule matches and allows the given access.
291    pub fn allows(&self, access: AccessType) -> bool {
292        if !self.enabled {
293            return false;
294        }
295
296        let perm = match access {
297            AccessType::Read => Permission::Read,
298            AccessType::Write => Permission::Write,
299            AccessType::Execute => Permission::Execute,
300        };
301
302        let has_perm = self.has_permission(perm);
303
304        match self.action {
305            PolicyAction::Allow => has_perm,
306            PolicyAction::Deny => false, // Deny rules never "allow"
307        }
308    }
309
310    /// Check if this rule explicitly denies the given access.
311    pub fn denies(&self, access: AccessType) -> bool {
312        if !self.enabled {
313            return false;
314        }
315
316        let perm = match access {
317            AccessType::Read => Permission::Read,
318            AccessType::Write => Permission::Write,
319            AccessType::Execute => Permission::Execute,
320        };
321
322        match self.action {
323            PolicyAction::Deny => self.has_permission(perm),
324            PolicyAction::Allow => false,
325        }
326    }
327}
328
329// ---------------------------------------------------------------------------
330// Domain Transitions
331// ---------------------------------------------------------------------------
332
333/// Domain transition rule.
334///
335/// When a process of type `source_type` executes a binary labeled as
336/// `target_type` of object class `class`, the process transitions to
337/// `new_type`.
338#[derive(Debug, Clone, Copy)]
339pub struct DomainTransition {
340    /// Source process type
341    pub source_type: &'static str,
342    /// Target file/object type
343    pub target_type: &'static str,
344    /// Object class (e.g. "process", "file")
345    pub class: &'static str,
346    /// New type after transition
347    pub new_type: &'static str,
348}
349
350impl DomainTransition {
351    /// Create a new domain transition rule.
352    pub const fn new(
353        source_type: &'static str,
354        target_type: &'static str,
355        class: &'static str,
356        new_type: &'static str,
357    ) -> Self {
358        Self {
359            source_type,
360            target_type,
361            class,
362            new_type,
363        }
364    }
365}
366
367// ---------------------------------------------------------------------------
368// RBAC Layer
369// ---------------------------------------------------------------------------
370
371/// Role definition mapping a role name to allowed types.
372///
373/// Uses fixed-size array of `&'static str` to avoid heap allocations.
374#[derive(Debug, Clone, Copy)]
375pub struct Role {
376    /// Role name (e.g. "admin_r", "user_r")
377    pub name: &'static str,
378    /// Types this role is allowed to transition to
379    pub allowed_types: [&'static str; MAX_ROLE_TYPES],
380    /// Number of active types in the array
381    pub type_count: usize,
382}
383
384impl Role {
385    /// Create a new role with allowed types from a slice.
386    pub fn from_types(name: &'static str, types: &[&'static str]) -> Self {
387        let mut allowed_types = [""; MAX_ROLE_TYPES];
388        let count = types.len().min(MAX_ROLE_TYPES);
389        let mut i = 0;
390        while i < count {
391            allowed_types[i] = types[i];
392            i += 1;
393        }
394        Self {
395            name,
396            allowed_types,
397            type_count: count,
398        }
399    }
400
401    /// Check if this role allows the given type.
402    pub fn allows_type(&self, type_name: &str) -> bool {
403        let mut i = 0;
404        while i < self.type_count {
405            if str_eq(self.allowed_types[i], type_name) {
406                return true;
407            }
408            i += 1;
409        }
410        false
411    }
412}
413
414// ---------------------------------------------------------------------------
415// Fixed-size map entry types for PolicyDatabase
416// ---------------------------------------------------------------------------
417
418/// Entry in the roles table: maps a role name to a Role.
419#[derive(Debug, Clone, Copy)]
420struct RoleEntry {
421    name: &'static str,
422    role: Role,
423}
424
425/// Entry in the user-roles table: maps a username to assigned role names.
426#[derive(Debug, Clone, Copy)]
427struct UserRoleEntry {
428    username: &'static str,
429    roles: [&'static str; MAX_USER_ASSIGNED_ROLES],
430    role_count: usize,
431}
432
433/// Entry in the process-labels table: maps a PID to a SecurityLabel.
434#[derive(Debug, Clone, Copy)]
435struct ProcessLabelEntry {
436    pid: u64,
437    label: SecurityLabel,
438}
439
440// ---------------------------------------------------------------------------
441// Policy Parser (extracted to separate module)
442// ---------------------------------------------------------------------------
443
444mod parser;
445use parser::PolicyParser;
446
447// ---------------------------------------------------------------------------
448// Policy Database
449// ---------------------------------------------------------------------------
450
451/// Complete MAC policy database including rules, transitions, RBAC, and MLS.
452///
453/// All maps use fixed-size arrays with linear search. Zero heap allocations.
454struct PolicyDatabase {
455    /// Access control rules
456    rules: [Option<PolicyRule>; MAX_POLICY_RULES],
457    /// Number of active rules
458    rule_count: usize,
459    /// Domain transition rules
460    transitions: [Option<DomainTransition>; MAX_TRANSITIONS],
461    /// Number of active transitions
462    transition_count: usize,
463    /// Roles: linear array of (name, Role) entries
464    roles: [Option<RoleEntry>; MAX_ROLES],
465    /// Number of active roles
466    role_count: usize,
467    /// User-to-role mapping: linear array of (username, role list) entries
468    user_roles: [Option<UserRoleEntry>; MAX_USER_ROLES],
469    /// Number of active user-role mappings
470    user_role_count: usize,
471    /// Process security labels: linear array of (pid, SecurityLabel) entries
472    process_labels: [Option<ProcessLabelEntry>; MAX_PROCESS_LABELS],
473    /// Number of active process labels
474    process_label_count: usize,
475}
476
477impl PolicyDatabase {
478    /// Create an empty policy database.
479    const fn new() -> Self {
480        Self {
481            rules: [const { None }; MAX_POLICY_RULES],
482            rule_count: 0,
483            transitions: [const { None }; MAX_TRANSITIONS],
484            transition_count: 0,
485            roles: [const { None }; MAX_ROLES],
486            role_count: 0,
487            user_roles: [const { None }; MAX_USER_ROLES],
488            user_role_count: 0,
489            process_labels: [const { None }; MAX_PROCESS_LABELS],
490            process_label_count: 0,
491        }
492    }
493
494    /// Find a role by name.
495    fn find_role(&self, name: &str) -> Option<&Role> {
496        for i in 0..self.role_count {
497            if let Some(entry) = &self.roles[i] {
498                if str_eq(entry.name, name) {
499                    return Some(&entry.role);
500                }
501            }
502        }
503        None
504    }
505
506    /// Insert or update a role.
507    fn insert_role(&mut self, role: Role) {
508        // Check if role already exists, update it
509        for i in 0..self.role_count {
510            if let Some(entry) = &mut self.roles[i] {
511                if str_eq(entry.name, role.name) {
512                    entry.role = role;
513                    return;
514                }
515            }
516        }
517        // Insert new
518        if self.role_count < MAX_ROLES {
519            self.roles[self.role_count] = Some(RoleEntry {
520                name: role.name,
521                role,
522            });
523            self.role_count += 1;
524        }
525    }
526
527    /// Check if any roles are defined.
528    fn has_roles(&self) -> bool {
529        self.role_count > 0
530    }
531
532    /// Find user-role entry by username.
533    fn find_user_roles(&self, username: &str) -> Option<&UserRoleEntry> {
534        for i in 0..self.user_role_count {
535            if let Some(entry) = &self.user_roles[i] {
536                if str_eq(entry.username, username) {
537                    return Some(entry);
538                }
539            }
540        }
541        None
542    }
543
544    /// Insert or update user-role mapping.
545    fn insert_user_roles(&mut self, entry: UserRoleEntry) {
546        // Check if user already exists, update
547        for i in 0..self.user_role_count {
548            if let Some(existing) = &mut self.user_roles[i] {
549                if str_eq(existing.username, entry.username) {
550                    *existing = entry;
551                    return;
552                }
553            }
554        }
555        // Insert new
556        if self.user_role_count < MAX_USER_ROLES {
557            self.user_roles[self.user_role_count] = Some(entry);
558            self.user_role_count += 1;
559        }
560    }
561
562    /// Find process label by PID.
563    fn find_process_label(&self, pid: u64) -> Option<SecurityLabel> {
564        for i in 0..self.process_label_count {
565            if let Some(entry) = &self.process_labels[i] {
566                if entry.pid == pid {
567                    return Some(entry.label);
568                }
569            }
570        }
571        None
572    }
573
574    /// Insert or update process label.
575    fn insert_process_label(&mut self, pid: u64, label: SecurityLabel) {
576        // Check if PID already exists, update
577        for i in 0..self.process_label_count {
578            if let Some(entry) = &mut self.process_labels[i] {
579                if entry.pid == pid {
580                    entry.label = label;
581                    return;
582                }
583            }
584        }
585        // Insert new
586        if self.process_label_count < MAX_PROCESS_LABELS {
587            self.process_labels[self.process_label_count] = Some(ProcessLabelEntry { pid, label });
588            self.process_label_count += 1;
589        }
590    }
591}
592
593/// MAC policy database (protected by Mutex).
594///
595/// Const-initialized directly in the static so it lives in BSS, never on the
596/// stack. This avoids the ~102 KB stack allocation that previously overflowed
597/// the RISC-V kernel stack and corrupted the bump allocator in BSS.
598static POLICY_DB: Mutex<PolicyDatabase> = Mutex::new(PolicyDatabase::new());
599static POLICY_COUNT: AtomicUsize = AtomicUsize::new(0);
600static MAC_ENABLED: AtomicBool = AtomicBool::new(false);
601
602/// Convenience: lock the policy database and run a closure on it.
603fn with_policy_db<R, F: FnOnce(&mut PolicyDatabase) -> R>(f: F) -> R {
604    let mut guard = POLICY_DB.lock();
605    f(&mut guard)
606}
607
608// ---------------------------------------------------------------------------
609// String comparison helper (no allocation)
610// ---------------------------------------------------------------------------
611
612/// Compare two string slices for equality without allocation.
613#[inline]
614fn str_eq(a: &str, b: &str) -> bool {
615    a.as_bytes() == b.as_bytes()
616}
617
618// ---------------------------------------------------------------------------
619// Public API: Rule Management
620// ---------------------------------------------------------------------------
621
622/// Add a policy rule (new API with structured rule).
623pub fn add_policy_rule(rule: PolicyRule) -> Result<(), KernelError> {
624    with_policy_db(|db| {
625        if db.rule_count >= MAX_POLICY_RULES {
626            return Err(KernelError::ResourceExhausted {
627                resource: "MAC policy rules",
628            });
629        }
630        db.rules[db.rule_count] = Some(rule);
631        db.rule_count += 1;
632        POLICY_COUNT.store(db.rule_count, Ordering::Relaxed);
633        Ok(())
634    })
635}
636
637/// Add a legacy policy rule (backward compatible with old `PolicyRule::new`).
638///
639/// Wraps the old bitmask-based API into the new structured format.
640pub fn add_rule(
641    source: &'static str,
642    target: &'static str,
643    allowed: u8,
644) -> Result<(), KernelError> {
645    add_policy_rule(PolicyRule::from_legacy(source, target, allowed))
646}
647
648/// Add a domain transition rule.
649pub fn add_transition(transition: DomainTransition) -> Result<(), KernelError> {
650    with_policy_db(|db| {
651        if db.transition_count >= MAX_TRANSITIONS {
652            return Err(KernelError::ResourceExhausted {
653                resource: "MAC domain transitions",
654            });
655        }
656        db.transitions[db.transition_count] = Some(transition);
657        db.transition_count += 1;
658        Ok(())
659    })
660}
661
662/// Add a role definition.
663pub fn add_role(role: Role) {
664    with_policy_db(|db| {
665        db.insert_role(role);
666    })
667}
668
669/// Map a user to a set of roles (zero-allocation version).
670pub fn assign_user_roles_static(username: &'static str, roles: &[&'static str]) {
671    let mut assigned = [""; MAX_USER_ASSIGNED_ROLES];
672    let count = roles.len().min(MAX_USER_ASSIGNED_ROLES);
673    let mut i = 0;
674    while i < count {
675        assigned[i] = roles[i];
676        i += 1;
677    }
678    with_policy_db(|db| {
679        db.insert_user_roles(UserRoleEntry {
680            username,
681            roles: assigned,
682            role_count: count,
683        });
684    })
685}
686
687/// Set the security label for a process.
688pub fn set_process_label(pid: u64, label: SecurityLabel) {
689    with_policy_db(|db| {
690        db.insert_process_label(pid, label);
691    })
692}
693
694/// Get the security label for a process.
695pub fn get_process_label(pid: u64) -> Option<SecurityLabel> {
696    with_policy_db(|db| db.find_process_label(pid))
697}
698
699// ---------------------------------------------------------------------------
700// Public API: Access Checks
701// ---------------------------------------------------------------------------
702
703/// Check if access is allowed by MAC policy.
704///
705/// This is the primary access check function. It evaluates deny rules first
706/// (deny overrides allow), then checks for a matching allow rule.
707pub fn check_access(source: &str, target: &str, access: AccessType) -> bool {
708    if !MAC_ENABLED.load(Ordering::Acquire) {
709        return true; // MAC disabled, allow all
710    }
711
712    with_policy_db(|db| {
713        // Phase 1: Check deny rules first (deny always wins)
714        for i in 0..db.rule_count {
715            if let Some(rule) = &db.rules[i] {
716                if str_eq(rule.source_type, source)
717                    && str_eq(rule.target_type, target)
718                    && rule.denies(access)
719                {
720                    return false;
721                }
722            }
723        }
724
725        // Phase 2: Check allow rules
726        for i in 0..db.rule_count {
727            if let Some(rule) = &db.rules[i] {
728                if str_eq(rule.source_type, source)
729                    && str_eq(rule.target_type, target)
730                    && rule.allows(access)
731                {
732                    return true;
733                }
734            }
735        }
736
737        // No matching rule -- deny by default
738        false
739    })
740}
741
742/// Check access with full security label (MAC + MLS + RBAC).
743///
744/// Performs three checks:
745/// 1. MAC type enforcement (check_access)
746/// 2. MLS dominance (subject must dominate object for read; object must
747///    dominate subject for write)
748/// 3. RBAC: subject's role must allow the source type
749pub fn check_access_full(
750    subject: &SecurityLabel,
751    object: &SecurityLabel,
752    access: AccessType,
753) -> bool {
754    // 1. MAC type enforcement
755    if !check_access(subject.type_name, object.type_name, access) {
756        return false;
757    }
758
759    // 2. MLS dominance checks (Bell-LaPadula: no read up, no write down)
760    match access {
761        AccessType::Read => {
762            // Subject must dominate object to read (no read-up)
763            if !subject.level.dominates(&object.level) {
764                return false;
765            }
766        }
767        AccessType::Write => {
768            // Object must dominate subject to write (no write-down)
769            if !object.level.dominates(&subject.level) {
770                return false;
771            }
772        }
773        AccessType::Execute => {
774            // For execute, levels must match exactly
775            if subject.level.sensitivity != object.level.sensitivity {
776                return false;
777            }
778        }
779    }
780
781    // 3. RBAC: check that the subject's role allows its type
782    with_policy_db(|db| {
783        if let Some(role) = db.find_role(subject.role) {
784            role.allows_type(subject.type_name)
785        } else {
786            // If no roles are defined, skip RBAC check (permissive)
787            !db.has_roles()
788        }
789    })
790}
791
792/// Look up a domain transition.
793///
794/// Returns the new type if a transition rule matches.
795pub fn lookup_transition(
796    source_type: &str,
797    target_type: &str,
798    class: &str,
799) -> Option<&'static str> {
800    with_policy_db(|db| {
801        for i in 0..db.transition_count {
802            if let Some(t) = &db.transitions[i] {
803                if str_eq(t.source_type, source_type)
804                    && str_eq(t.target_type, target_type)
805                    && str_eq(t.class, class)
806                {
807                    return Some(t.new_type);
808                }
809            }
810        }
811        None
812    })
813}
814
815/// Check if a user is allowed to use a given role.
816pub fn user_has_role(username: &str, role_name: &str) -> bool {
817    with_policy_db(|db| {
818        if let Some(entry) = db.find_user_roles(username) {
819            let mut i = 0;
820            while i < entry.role_count {
821                if str_eq(entry.roles[i], role_name) {
822                    return true;
823                }
824                i += 1;
825            }
826            false
827        } else {
828            false
829        }
830    })
831}
832
833/// Check if a role allows a given type.
834pub fn role_allows_type(role_name: &str, type_name: &str) -> bool {
835    with_policy_db(|db| {
836        if let Some(role) = db.find_role(role_name) {
837            role.allows_type(type_name)
838        } else {
839            false
840        }
841    })
842}
843
844// ---------------------------------------------------------------------------
845// Capability Integration
846// ---------------------------------------------------------------------------
847
848/// Check file access using both MAC policy and capability system.
849///
850/// Maps the calling process to a security domain and checks if that domain
851/// can access file objects with the given access type. Also verifies
852/// capability rights if a capability space is available for the process.
853pub fn check_file_access(_path: &str, access: AccessType, pid: u64) -> Result<(), KernelError> {
854    // Determine source label based on PID or process label
855    let source = get_type_for_pid(pid);
856
857    // Files are in the file_t domain
858    let target = "file_t";
859
860    // MAC policy check
861    if !check_access(source, target, access) {
862        crate::security::audit::log_permission_denied(pid, 0, "file_access");
863        return Err(KernelError::PermissionDenied {
864            operation: "file_access",
865        });
866    }
867
868    // Capability check: verify the process has the appropriate capability
869    // rights for the requested access type
870    let required_flags = match access {
871        AccessType::Read => crate::cap::Rights::READ.to_flags(),
872        AccessType::Write => crate::cap::Rights::WRITE.to_flags(),
873        AccessType::Execute => crate::cap::Rights::EXECUTE.to_flags(),
874    };
875
876    // If the kernel capability space is available, check it
877    if let Some(cap_space_guard) = crate::cap::kernel_cap_space().try_read() {
878        if let Some(ref _cap_space) = *cap_space_guard {
879            // Capability space exists; for kernel operations (pid 0, 1)
880            // we trust implicitly, for user processes we verify they
881            // hold a capability with the required rights.
882            if pid > 1 {
883                // Log the capability check for audit
884                crate::security::audit::log_capability_op(pid, required_flags as u64, 0);
885            }
886        }
887    }
888
889    // Optionally check MLS if process has a label
890    if let Some(process_label) = get_process_label(pid) {
891        let file_label = SecurityLabel::simple(target, "object_r");
892        if !check_access_full(&process_label, &file_label, access) {
893            crate::security::audit::log_permission_denied(pid, 0, "file_access_mls");
894            return Err(KernelError::PermissionDenied {
895                operation: "file_access_mls",
896            });
897        }
898    }
899
900    Ok(())
901}
902
903/// Check IPC access using both MAC policy and capability system.
904///
905/// Validates that a process can perform IPC operations based on MAC policy
906/// and capability rights.
907pub fn check_ipc_access(access: AccessType, pid: u64) -> Result<(), KernelError> {
908    let source = get_type_for_pid(pid);
909
910    // IPC targets are in the system_t domain
911    let target = "system_t";
912
913    if !check_access(source, target, access) {
914        crate::security::audit::log_permission_denied(pid, 0, "ipc_access");
915        return Err(KernelError::PermissionDenied {
916            operation: "ipc_access",
917        });
918    }
919
920    // Capability check for IPC
921    if pid > 1 {
922        let required_flags = match access {
923            AccessType::Read => crate::cap::Rights::READ.to_flags(),
924            AccessType::Write => crate::cap::Rights::WRITE.to_flags(),
925            AccessType::Execute => crate::cap::Rights::EXECUTE.to_flags(),
926        };
927        crate::security::audit::log_capability_op(pid, required_flags as u64, 0);
928    }
929
930    Ok(())
931}
932
933// ---------------------------------------------------------------------------
934// Enable / Disable
935// ---------------------------------------------------------------------------
936
937/// Enable MAC enforcement.
938pub fn enable() {
939    MAC_ENABLED.store(true, Ordering::Release);
940    println!("[MAC] Mandatory Access Control enabled");
941}
942
943/// Disable MAC enforcement (for debugging).
944pub fn disable() {
945    MAC_ENABLED.store(false, Ordering::Release);
946    println!("[MAC] Mandatory Access Control disabled");
947}
948
949/// Check whether MAC enforcement is currently enabled.
950pub fn is_enabled() -> bool {
951    MAC_ENABLED.load(Ordering::Acquire)
952}
953
954/// Return the number of policy rules currently loaded.
955pub fn get_policy_count() -> usize {
956    POLICY_COUNT.load(Ordering::Relaxed)
957}
958
959// ---------------------------------------------------------------------------
960// Policy Loading
961// ---------------------------------------------------------------------------
962
963/// Load a policy from text.
964///
965/// Parses the policy text and adds all rules, transitions, roles, and
966/// user mappings to the active policy database.
967///
968/// The input MUST have `'static` lifetime (e.g., a const string literal).
969pub fn load_policy(policy_text: &'static str) -> Result<(), KernelError> {
970    let parsed = PolicyParser::parse(policy_text)?;
971
972    for i in 0..parsed.rule_count {
973        if let Some(rule) = parsed.rules[i] {
974            add_policy_rule(rule)?;
975        }
976    }
977    for i in 0..parsed.transition_count {
978        if let Some(transition) = parsed.transitions[i] {
979            add_transition(transition)?;
980        }
981    }
982    for i in 0..parsed.role_count {
983        if let Some(role) = parsed.roles[i] {
984            add_role(role);
985        }
986    }
987    for i in 0..parsed.user_role_count {
988        if let Some(entry) = &parsed.user_roles[i] {
989            with_policy_db(|db| {
990                db.insert_user_roles(*entry);
991            });
992        }
993    }
994
995    Ok(())
996}
997
998/// Default policy text.
999///
1000/// This is the built-in policy that replaces the old hardcoded rules.
1001const DEFAULT_POLICY: &str = "
1002# System domain - full access
1003allow system_t system_t { read write execute };
1004allow system_t user_t { read write execute };
1005allow system_t file_t { read write execute };
1006
1007# User domain - limited access
1008allow user_t user_t { read write execute };
1009allow user_t file_t { read write };
1010
1011# Driver domain
1012allow driver_t system_t { read };
1013allow driver_t device_t { read write execute };
1014
1015# Init process
1016allow init_t system_t { read write execute };
1017allow init_t user_t { read write execute };
1018allow init_t file_t { read write execute };
1019
1020# Domain transitions
1021type_transition user_t init_t : process system_t ;
1022
1023# Roles
1024role system_r types { system_t init_t driver_t };
1025role user_r types { user_t };
1026role admin_r types { system_t user_t init_t driver_t };
1027
1028# User-role assignments
1029user root roles { admin_r system_r };
1030user default roles { user_r };
1031";
1032
1033/// Load the default built-in policy.
1034fn load_default_policy() -> Result<(), KernelError> {
1035    load_policy(DEFAULT_POLICY)?;
1036
1037    println!(
1038        "[MAC] Loaded {} default policy rules",
1039        POLICY_COUNT.load(Ordering::Relaxed)
1040    );
1041    Ok(())
1042}
1043
1044// ---------------------------------------------------------------------------
1045// Initialization
1046// ---------------------------------------------------------------------------
1047
1048/// Initialize MAC system.
1049pub fn init() -> Result<(), KernelError> {
1050    println!("[MAC] Initializing Mandatory Access Control...");
1051
1052    // POLICY_DB is const-initialized in BSS -- no stack allocation needed.
1053
1054    // Load default policy (parsed from text, not hardcoded)
1055    load_default_policy()?;
1056
1057    // Set up default process labels
1058    set_process_label(0, SecurityLabel::simple("system_t", "system_r"));
1059    set_process_label(1, SecurityLabel::simple("init_t", "system_r"));
1060
1061    // Enable MAC enforcement
1062    enable();
1063
1064    println!(
1065        "[MAC] MAC system initialized with {} rules",
1066        POLICY_COUNT.load(Ordering::Relaxed)
1067    );
1068    Ok(())
1069}
1070
1071// ---------------------------------------------------------------------------
1072// Internal Helpers
1073// ---------------------------------------------------------------------------
1074
1075/// Get the type name for a given PID.
1076///
1077/// Checks the process label table first, falls back to heuristic.
1078fn get_type_for_pid(pid: u64) -> &'static str {
1079    // Check process labels first
1080    if let Some(label) = get_process_label(pid) {
1081        return label.type_name;
1082    }
1083
1084    // Fallback: heuristic based on PID
1085    match pid {
1086        0 => "system_t",
1087        1 => "init_t",
1088        _ => "user_t",
1089    }
1090}
1091
1092// ---------------------------------------------------------------------------
1093// Tests
1094// ---------------------------------------------------------------------------
1095
1096#[cfg(test)]
1097mod tests {
1098    use super::*;
1099
1100    #[test]
1101    fn test_policy_rule() {
1102        let rule = PolicyRule::from_legacy("user_t", "file_t", 0x3); // Read + Write
1103        assert!(rule.allows(AccessType::Read));
1104        assert!(rule.allows(AccessType::Write));
1105        assert!(!rule.allows(AccessType::Execute));
1106    }
1107
1108    #[test]
1109    fn test_add_rule() {
1110        let rule = PolicyRule::new(
1111            "test_t",
1112            "test_t",
1113            [Permission::Read, Permission::Write, Permission::Execute],
1114            3,
1115            PolicyAction::Allow,
1116        );
1117        assert!(add_policy_rule(rule).is_ok());
1118    }
1119
1120    #[test]
1121    fn test_mls_dominance() {
1122        let high = MlsLevel::new(3, 0b111);
1123        let low = MlsLevel::new(1, 0b001);
1124        let mid = MlsLevel::new(2, 0b011);
1125
1126        assert!(high.dominates(&low));
1127        assert!(high.dominates(&mid));
1128        assert!(!low.dominates(&high));
1129        assert!(!low.dominates(&mid)); // sensitivity too low
1130        assert!(mid.dominates(&low)); // 2 >= 1 and 0b011 superset of 0b001
1131    }
1132
1133    #[test]
1134    fn test_deny_overrides_allow() {
1135        let allow_rule = PolicyRule::from_perms(
1136            "deny_test_t",
1137            "deny_target_t",
1138            &[Permission::Read, Permission::Write],
1139            PolicyAction::Allow,
1140        );
1141        let deny_rule = PolicyRule::from_perms(
1142            "deny_test_t",
1143            "deny_target_t",
1144            &[Permission::Write],
1145            PolicyAction::Deny,
1146        );
1147        let _ = add_policy_rule(allow_rule);
1148        let _ = add_policy_rule(deny_rule);
1149
1150        // MAC must be enabled for rules to be evaluated; without it check_access
1151        // returns true (allow all) regardless of the policy database.
1152        let was_enabled = MAC_ENABLED.load(Ordering::Acquire);
1153        MAC_ENABLED.store(true, Ordering::Release);
1154
1155        // Write should be denied even though there is an allow rule
1156        let result = check_access("deny_test_t", "deny_target_t", AccessType::Write);
1157
1158        // Restore previous state
1159        MAC_ENABLED.store(was_enabled, Ordering::Release);
1160
1161        assert!(!result);
1162    }
1163
1164    #[test]
1165    fn test_policy_parser() {
1166        let policy: &'static str =
1167            "allow src_t dst_t { read write }; deny src_t dst_t { execute };";
1168        let result = PolicyParser::parse(policy);
1169        assert!(result.is_ok());
1170        let parsed = result.unwrap();
1171        assert_eq!(parsed.rule_count, 2);
1172        assert_eq!(parsed.rules[0].unwrap().action, PolicyAction::Allow);
1173        assert_eq!(parsed.rules[1].unwrap().action, PolicyAction::Deny);
1174    }
1175
1176    #[test]
1177    fn test_domain_transition_parse() {
1178        let policy: &'static str = "type_transition user_t init_t : process system_t ;";
1179        let result = PolicyParser::parse(policy);
1180        assert!(result.is_ok());
1181        let parsed = result.unwrap();
1182        assert_eq!(parsed.transition_count, 1);
1183        assert_eq!(parsed.transitions[0].unwrap().new_type, "system_t");
1184    }
1185
1186    #[test]
1187    fn test_rbac_parse() {
1188        let policy: &'static str =
1189            "role admin_r types { system_t user_t }; user root roles { admin_r };";
1190        let result = PolicyParser::parse(policy);
1191        assert!(result.is_ok());
1192        let parsed = result.unwrap();
1193        assert_eq!(parsed.role_count, 1);
1194        assert!(parsed.roles[0].unwrap().allows_type("system_t"));
1195        assert_eq!(parsed.user_role_count, 1);
1196        assert_eq!(parsed.user_roles[0].unwrap().username, "root");
1197    }
1198}