Skip to main content

starnix_core/security/selinux_hooks/
mod.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// TODO(https://github.com/rust-lang/rust/issues/39371): remove
6#![allow(non_upper_case_globals)]
7
8pub(super) mod audit;
9pub(super) mod binder;
10pub(super) mod bpf;
11pub(super) mod file;
12pub(super) mod fs_node;
13pub(super) mod netlink_socket;
14pub(super) mod perf_event;
15pub(super) mod selinuxfs;
16pub(super) mod socket;
17pub(super) mod superblock;
18pub(super) mod task;
19pub(super) mod testing;
20
21use super::PermissionFlags;
22use crate::task::{CurrentTask, TaskPersistentInfo};
23use crate::vfs::{DirEntry, FileHandle, FileObject, FileSystem, FileSystemOps, FsNode};
24use audit::{Auditable, audit_decision, audit_todo_decision};
25use fuchsia_rcu::{RcuCell, RcuReadGuard};
26use indexmap::IndexSet;
27use selinux::permission_check::PermissionCheck;
28use selinux::policy::{FsUseType, XpermsKind};
29use selinux::{
30    ClassPermission, CommonFilePermission, CommonFsNodePermission, DirPermission, FdPermission,
31    FileClass, FileSystemLabel, FileSystemLabelingScheme, FileSystemMountOptions, ForClass,
32    FsNodeClass, InitialSid, KernelPermission, PolicyCap, ProcessPermission, SecurityId,
33    SecurityServer, TaskAttrs,
34};
35use smallvec;
36use starnix_logging::{BugRef, CATEGORY_STARNIX_SECURITY, bug_ref, trace_duration, track_stub};
37use starnix_sync::Mutex;
38use starnix_uapi::arc_key::WeakKey;
39use starnix_uapi::errors::Errno;
40use starnix_uapi::file_mode::FileMode;
41use starnix_uapi::{errno, error};
42use std::cell::Ref;
43use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
44use std::sync::{Arc, OnceLock};
45
46/// Rust cannot infer the permission type from an empty slice, so define an explicitly-typed empty
47/// permissions slice to use.
48const NO_PERMISSIONS: &[KernelPermission] = &[];
49
50/// Iterable set of permissions returned by `permissions_from_flags()`.
51type PermissionFlagsVec = smallvec::SmallVec<[KernelPermission; 3]>;
52
53/// Returns the set of `Permissions` on `class`, corresponding to the specified `flags`.
54fn permissions_from_flags(flags: PermissionFlags, class: FsNodeClass) -> PermissionFlagsVec {
55    let mut result = PermissionFlagsVec::new();
56
57    if flags.contains(PermissionFlags::READ) {
58        result.push(CommonFsNodePermission::Read.for_class(class));
59    }
60    if flags.contains(PermissionFlags::WRITE) {
61        // SELinux uses the `APPEND` bit to distinguish which of the "append" or the more general
62        // "write" permission to check for.
63        if flags.contains(PermissionFlags::APPEND) {
64            result.push(CommonFsNodePermission::Append.for_class(class));
65        } else {
66            result.push(CommonFsNodePermission::Write.for_class(class));
67        }
68    }
69
70    if let FsNodeClass::File(class) = class {
71        if flags.contains(PermissionFlags::EXEC) {
72            if class == FileClass::Dir {
73                result.push(DirPermission::Search.into());
74            } else {
75                result.push(CommonFilePermission::Execute.for_class(class));
76            }
77        }
78    }
79    result
80}
81
82fn is_internal_operation(current_task: &CurrentTask) -> bool {
83    current_task_state(current_task).internal_operation
84}
85
86/// Checks that `current_task` has permission to "use" the specified `file`, and the specified
87/// `permissions` to the underlying [`crate::vfs::FsNode`].
88fn has_file_permissions(
89    permission_check: &PermissionCheck<'_>,
90    current_task: &CurrentTask,
91    subject_sid: SecurityId,
92    file: &FileObject,
93    permissions: &[impl ForClass<FsNodeClass>],
94    audit_context: Auditable<'_>,
95) -> Result<(), Errno> {
96    if is_internal_operation(current_task) {
97        return Ok(());
98    };
99    // Validate that the `subject` has the "fd { use }" permission to the `file`.
100    // If the file and task security domains are identical then `fd { use }` is implicitly granted.
101    let file_sid = file.security_state.state.sid;
102    if subject_sid != file_sid {
103        let node = file.node().as_ref().as_ref();
104        let audit_context = [audit_context, file.into(), node.into()];
105        check_permission(
106            permission_check,
107            current_task,
108            subject_sid,
109            file_sid,
110            FdPermission::Use,
111            (&audit_context).into(),
112        )?;
113    }
114
115    // Validate that the `subject` has the desired `permissions`, if any, to the underlying node.
116    if !permissions.is_empty() {
117        let audit_context = [audit_context, file.into()];
118        has_fs_node_permissions(
119            permission_check,
120            current_task,
121            subject_sid,
122            file.node(),
123            permissions,
124            (&audit_context).into(),
125        )?;
126    }
127
128    Ok(())
129}
130
131fn has_file_ioctl_permission(
132    permission_check: &PermissionCheck<'_>,
133    current_task: &CurrentTask,
134    subject_sid: SecurityId,
135    file: &FileObject,
136    ioctl: u16,
137    audit_context: Auditable<'_>,
138) -> Result<(), Errno> {
139    // Validate that the `subject` has the "fd { use }" permission to the `file`.
140    has_file_permissions(
141        permission_check,
142        current_task,
143        subject_sid,
144        file,
145        NO_PERMISSIONS,
146        audit_context,
147    )?;
148
149    // Validate that the `subject` has the `ioctl` permission on the underlying node,
150    // as well as the specified ioctl extended permission.
151    let fs_node = file.node().as_ref().as_ref();
152    if fs_node.is_private() {
153        return Ok(());
154    }
155    let FsNodeSidAndClass { sid: target_sid, class: target_class } =
156        fs_node_effective_sid_and_class(fs_node);
157
158    let audit_context =
159        &[audit_context, file.into(), fs_node.into(), Auditable::IoctlCommand(ioctl)];
160
161    // Check the `ioctl` permission and extended permission on the underlying node.
162    check_permission_and_xperms(
163        permission_check,
164        current_task,
165        subject_sid,
166        target_sid,
167        CommonFsNodePermission::Ioctl.for_class(target_class),
168        XpermsKind::Ioctl,
169        ioctl,
170        audit_context.into(),
171    )
172}
173
174fn check_permission_and_xperms(
175    permission_check: &PermissionCheck<'_>,
176    current_task: &CurrentTask,
177    subject_sid: SecurityId,
178    target_sid: SecurityId,
179    permission: KernelPermission,
180    xperms_kind: XpermsKind,
181    xperm: u16,
182    audit_context: Auditable<'_>,
183) -> Result<(), Errno> {
184    if is_internal_operation(current_task) {
185        return Ok(());
186    }
187    let result = permission_check.has_extended_permission(
188        xperms_kind,
189        subject_sid,
190        target_sid,
191        permission.clone(),
192        xperm,
193    );
194
195    if result.audit {
196        if !result.permit() {
197            current_task
198                .kernel()
199                .security_state
200                .state
201                .as_ref()
202                .unwrap()
203                .access_denial_count
204                .fetch_add(1, Ordering::Release);
205        }
206
207        audit_decision(
208            current_task,
209            permission_check,
210            result.clone(),
211            subject_sid,
212            target_sid,
213            permission.into(),
214            audit_context.into(),
215        );
216    }
217
218    result.permit().then_some(()).ok_or_else(|| errno!(EACCES))
219}
220
221/// Checks that `current_task` has the specified `permissions` to the `node`, without auditing.
222fn has_fs_node_permissions_dontaudit(
223    permission_check: &PermissionCheck<'_>,
224    _current_task: &CurrentTask,
225    subject_sid: SecurityId,
226    fs_node: &FsNode,
227    permissions: &[impl ForClass<FsNodeClass>],
228) -> Result<(), Errno> {
229    trace_duration!(
230        CATEGORY_STARNIX_SECURITY,
231        "security.selinux.has_fs_node_permissions_dontaudit"
232    );
233
234    if fs_node.is_private() {
235        return Ok(());
236    }
237
238    let target = fs_node_effective_sid_and_class(fs_node);
239    for permission in permissions {
240        if !permission_check
241            .has_permission(subject_sid, target.sid, permission.for_class(target.class))
242            .permit()
243        {
244            return error!(EACCES);
245        }
246    }
247
248    Ok(())
249}
250
251/// Checks that `current_task` has the specified `permissions` to the `node`.
252fn has_fs_node_permissions(
253    permission_check: &PermissionCheck<'_>,
254    current_task: &CurrentTask,
255    subject_sid: SecurityId,
256    fs_node: &FsNode,
257    permissions: &[impl ForClass<FsNodeClass>],
258    audit_context: Auditable<'_>,
259) -> Result<(), Errno> {
260    trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.has_fs_node_permissions");
261
262    if fs_node.is_private() {
263        return Ok(());
264    }
265
266    let target = fs_node_effective_sid_and_class(fs_node);
267
268    let fs = fs_node.fs();
269    let audit_context = [audit_context, fs_node.into(), fs.as_ref().into()];
270    for permission in permissions {
271        check_permission(
272            permission_check,
273            current_task,
274            subject_sid,
275            target.sid,
276            permission.for_class(target.class),
277            (&audit_context).into(),
278        )?;
279    }
280
281    Ok(())
282}
283
284/// Checks that `current_task` has `permissions` to `node`, with "todo_deny" on denial.
285#[allow(dead_code)]
286fn todo_has_fs_node_permissions(
287    bug: BugRef,
288    permission_check: &PermissionCheck<'_>,
289    current_task: &CurrentTask,
290    subject_sid: SecurityId,
291    fs_node: &FsNode,
292    permissions: &[impl ForClass<FsNodeClass>],
293    audit_context: Auditable<'_>,
294) -> Result<(), Errno> {
295    trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.todo_has_fs_node_permissions");
296
297    if fs_node.is_private() {
298        return Ok(());
299    }
300
301    let target = fs_node_effective_sid_and_class(fs_node);
302
303    let fs = fs_node.fs();
304    let audit_context = [audit_context, fs_node.into(), fs.as_ref().into()];
305    for permission in permissions {
306        todo_check_permission(
307            bug.clone(),
308            permission_check,
309            current_task,
310            subject_sid,
311            target.sid,
312            permission.for_class(target.class),
313            (&audit_context).into(),
314        )?;
315    }
316
317    Ok(())
318}
319
320fn file_class_from_file_mode(mode: FileMode) -> Result<FileClass, Errno> {
321    let file_type = mode.bits() & starnix_uapi::S_IFMT;
322    match file_type {
323        starnix_uapi::S_IFLNK => Ok(FileClass::LnkFile),
324        starnix_uapi::S_IFDIR => Ok(FileClass::Dir),
325        starnix_uapi::S_IFREG => Ok(FileClass::File),
326        starnix_uapi::S_IFCHR => Ok(FileClass::ChrFile),
327        starnix_uapi::S_IFBLK => Ok(FileClass::BlkFile),
328        starnix_uapi::S_IFIFO => Ok(FileClass::FifoFile),
329        starnix_uapi::S_IFSOCK => Ok(FileClass::SockFile),
330        0 => {
331            track_stub!(TODO("https://fxbug.dev/378864191"), "File with zero IFMT?");
332            Ok(FileClass::File)
333        }
334        _ => error!(EINVAL, format!("mode: {:?}", mode)),
335    }
336}
337
338#[macro_export]
339macro_rules! TODO_DENY {
340    ($bug_url:literal, $message:literal) => {{
341        use starnix_logging::bug_ref;
342        bug_ref!($bug_url)
343    }};
344}
345
346/// Returns the `SecurityId` and `FsNodeClass` that should be used for SELinux access control checks
347/// against `fs_node`.
348fn fs_node_effective_sid_and_class(fs_node: &FsNode) -> FsNodeSidAndClass {
349    let label_class = fs_node.security_state.0.read();
350    if matches!(label_class.label, FsNodeLabel::Uninitialized) {
351        // We should never reach here, but for now enforce it in debug builds.
352        if cfg!(any(test, debug_assertions)) {
353            panic!(
354                "Unlabeled FsNode@{} of class {:?} in {} (label {:?})",
355                fs_node.ino,
356                file_class_from_file_mode(fs_node.info().mode),
357                fs_node.fs().name(),
358                fs_node.fs().security_state.state.label(),
359            );
360        } else {
361            track_stub!(TODO("https://fxbug.dev/381210513"), "SID requested for unlabeled FsNode");
362        }
363    }
364    FsNodeSidAndClass { sid: label_class.sid(), class: label_class.class() }
365}
366
367/// Perform the specified check as would `check_permission()`, but report denials as "todo_deny" in
368/// the audit output, without actually denying access.
369fn todo_check_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
370    bug: BugRef,
371    permission_check: &PermissionCheck<'_>,
372    current_task: &CurrentTask,
373    source_sid: SecurityId,
374    target_sid: SecurityId,
375    permission: P,
376    audit_context: Auditable<'_>,
377) -> Result<(), Errno> {
378    if is_internal_operation(current_task) {
379        return Ok(());
380    }
381    let kernel = current_task.kernel();
382    if kernel.features.selinux_test_suite {
383        check_permission(
384            permission_check,
385            current_task,
386            source_sid,
387            target_sid,
388            permission,
389            audit_context,
390        )
391    } else {
392        trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.todo_check_permission");
393
394        let result = permission_check.has_permission(source_sid, target_sid, permission.clone());
395
396        if result.audit {
397            audit_todo_decision(
398                current_task,
399                bug,
400                permission_check,
401                result,
402                source_sid,
403                target_sid,
404                permission.into(),
405                audit_context,
406            );
407        }
408
409        Ok(())
410    }
411}
412
413/// Checks whether `source_sid` is allowed the specified `permission` on `target_sid`.
414fn check_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
415    permission_check: &PermissionCheck<'_>,
416    current_task: &CurrentTask,
417    source_sid: SecurityId,
418    target_sid: SecurityId,
419    permission: P,
420    audit_context: Auditable<'_>,
421) -> Result<(), Errno> {
422    trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.check_permission");
423
424    if is_internal_operation(current_task) {
425        return Ok(());
426    }
427    let result = permission_check.has_permission(source_sid, target_sid, permission.clone());
428
429    if result.audit {
430        if !result.permit() {
431            current_task
432                .kernel()
433                .security_state
434                .state
435                .as_ref()
436                .unwrap()
437                .access_denial_count
438                .fetch_add(1, Ordering::Release);
439        }
440
441        audit_decision(
442            current_task,
443            permission_check,
444            result.clone(),
445            source_sid,
446            target_sid,
447            permission.into(),
448            audit_context,
449        );
450    };
451
452    result.permit().then_some(()).ok_or_else(|| errno!(EACCES))
453}
454
455/// Checks that `subject_sid` has the specified process `permission` on `self`.
456fn check_self_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
457    permission_check: &PermissionCheck<'_>,
458    current_task: &CurrentTask,
459    subject_sid: SecurityId,
460    permission: P,
461    audit_context: Auditable<'_>,
462) -> Result<(), Errno> {
463    check_permission(
464        permission_check,
465        current_task,
466        subject_sid,
467        subject_sid,
468        permission,
469        audit_context,
470    )
471}
472
473async fn create_inspect_values(
474    security_server: Arc<SecurityServer>,
475) -> Result<fuchsia_inspect::Inspector, anyhow::Error> {
476    let inspector = fuchsia_inspect::Inspector::default();
477
478    let policy_bytes = if let Some(policy_data) = security_server.get_binary_policy() {
479        policy_data.len().try_into()?
480    } else {
481        0
482    };
483    inspector.root().record_uint("policy_bytes", policy_bytes);
484
485    Ok(inspector)
486}
487
488/// Returns the security state structure for the kernel.
489pub(super) fn kernel_init_security(
490    options: String,
491    exceptions: Vec<String>,
492    inspect_node: &fuchsia_inspect::Node,
493) -> KernelState {
494    let server = SecurityServer::new(options, exceptions);
495    let inspect_node = inspect_node.create_child("selinux");
496
497    let server_for_inspect = server.clone();
498    inspect_node.record_lazy_values("server", move || {
499        Box::pin(create_inspect_values(server_for_inspect.clone()))
500    });
501
502    KernelState {
503        server,
504        pending_file_systems: Mutex::default(),
505        selinuxfs_null: OnceLock::default(),
506        access_denial_count: AtomicU64::new(0u64),
507        has_policy: false.into(),
508        _inspect_node: inspect_node,
509    }
510}
511
512/// The global SELinux security structures, held by the `Kernel`.
513pub(super) struct KernelState {
514    // Owning reference to the SELinux `SecurityServer`.
515    pub(super) server: Arc<SecurityServer>,
516
517    /// Set of [`create::vfs::FileSystem`]s that have been constructed, and must be labeled as soon
518    /// as a policy is loaded into the `server`. Insertion order is retained, via use of `IndexSet`,
519    /// to ensure that filesystems have labels initialized in creation order, which is important
520    /// e.g. when initializing "overlayfs" node labels, based on the labels of the underlying nodes.
521    pub(super) pending_file_systems: Mutex<IndexSet<WeakKey<FileSystem>>>,
522
523    /// True when the `server` has a policy loaded.
524    pub(super) has_policy: AtomicBool,
525
526    /// Stashed reference to "/sys/fs/selinux/null" used for replacing inaccessible file descriptors
527    /// with a null file.
528    pub(super) selinuxfs_null: OnceLock<FileHandle>,
529
530    /// Counts the number of times that an AVC denial is audit-logged.
531    pub(super) access_denial_count: AtomicU64,
532
533    /// Inspect node through which SELinux status is exposed.
534    pub(super) _inspect_node: fuchsia_inspect::Node,
535}
536
537impl KernelState {
538    pub(super) fn access_denial_count(&self) -> u64 {
539        self.access_denial_count.load(Ordering::Acquire)
540    }
541
542    pub(super) fn has_policy(&self) -> bool {
543        self.has_policy.load(Ordering::Acquire)
544    }
545}
546
547// Security state for a PerfEventFileState instance.
548#[derive(Clone, Debug, PartialEq)]
549pub(super) struct PerfEventState {
550    sid: SecurityId,
551}
552
553pub(in crate::security) fn current_task_state(current_task: &CurrentTask) -> Ref<'_, TaskAttrs> {
554    Ref::map(current_task.current_creds(), |creds| &creds.security_state)
555}
556
557/// Returns the SID of a task. Panics if the task is using overridden credentials.
558pub(in crate::security) fn task_consistent_attrs(current_task: &CurrentTask) -> Ref<'_, TaskAttrs> {
559    assert!(!current_task.has_overridden_creds());
560    current_task_state(current_task)
561}
562
563/// Security state for a [`crate::vfs::FileObject`] instance. This currently just holds the SID
564/// that the [`crate::task::Task`] that created the file object had.
565#[derive(Debug)]
566pub(super) struct FileObjectState {
567    sid: SecurityId,
568}
569
570/// Security state for a [`crate::vfs::FileSystem`] instance. This holds the security fields
571/// parsed from the mount options and the selected labeling scheme.
572#[derive(Debug)]
573pub(super) struct FileSystemState {
574    // Fields used prior to policy-load, to hold mount options, etc.
575    mount_options: FileSystemMountOptions,
576    pending_entries: Mutex<IndexSet<WeakKey<DirEntry>>>,
577
578    // Set once the initial policy has been loaded, taking into account `mount_options`.
579    label: OnceLock<FileSystemLabel>,
580}
581
582impl FileSystemState {
583    fn new(mount_options: FileSystemMountOptions, _ops: &dyn FileSystemOps) -> Self {
584        let pending_entries = Mutex::new(IndexSet::new());
585        let label = OnceLock::new();
586
587        Self { mount_options, pending_entries, label }
588    }
589
590    /// Returns the resolved `FileSystemLabel`, or `None` if no policy has yet been loaded.
591    pub fn label(&self) -> Option<&FileSystemLabel> {
592        self.label.get()
593    }
594
595    /// Returns true if this file system supports dynamic re-labeling of file nodes.
596    pub fn supports_relabel(&self) -> bool {
597        let Some(label) = self.label() else {
598            return false;
599        };
600        match label.scheme {
601            FileSystemLabelingScheme::Mountpoint { .. } => false,
602            FileSystemLabelingScheme::FsUse { .. } => true,
603            FileSystemLabelingScheme::GenFsCon { supports_seclabel } => supports_seclabel,
604        }
605    }
606
607    /// Returns true if this file system persists labels in extended attributes.
608    pub fn supports_xattr(&self) -> bool {
609        let Some(label) = self.label() else {
610            return false;
611        };
612        match label.scheme {
613            FileSystemLabelingScheme::Mountpoint { .. }
614            | FileSystemLabelingScheme::GenFsCon { .. } => false,
615            FileSystemLabelingScheme::FsUse { fs_use_type, .. } => fs_use_type == FsUseType::Xattr,
616        }
617    }
618}
619
620/// Holds security state associated with a [`crate::vfs::FsNode`].
621#[derive(Debug)]
622pub(super) struct FsNodeState {
623    label: RcuCell<FsNodeLabelAndClass>,
624    update_lock: Mutex<()>,
625}
626
627impl Default for FsNodeState {
628    fn default() -> Self {
629        Self {
630            label: RcuCell::new(FsNodeLabelAndClass {
631                label: FsNodeLabel::Uninitialized,
632                class: None,
633            }),
634            update_lock: Mutex::new(()),
635        }
636    }
637}
638
639impl FsNodeState {
640    pub(super) fn read(&self) -> RcuReadGuard<FsNodeLabelAndClass> {
641        self.label.read()
642    }
643
644    pub(super) fn update(&self, new_label: FsNodeLabel, new_class: FsNodeClass) {
645        let _lock = self.update_lock.lock();
646        let mut new_label_class = (*self.label.read()).clone();
647        new_label_class.label = new_label;
648        new_label_class.class = Some(new_class);
649        self.label.update(new_label_class);
650    }
651
652    pub(super) fn update_label(&self, new_label: FsNodeLabel) {
653        let _lock = self.update_lock.lock();
654        let mut new_label_class = (*self.label.read()).clone();
655        new_label_class.label = new_label;
656        self.label.update(new_label_class);
657    }
658
659    pub(super) fn update_class(&self, new_class: FsNodeClass) {
660        let _lock = self.update_lock.lock();
661        let mut new_label_class = (*self.label.read()).clone();
662        new_label_class.class = Some(new_class);
663        self.label.update(new_label_class);
664    }
665
666    #[cfg(test)]
667    pub(super) fn clear_label_for_test(&self) {
668        let _lock = self.update_lock.lock();
669        let class = self.label.read().class;
670        self.label.update(FsNodeLabelAndClass { label: FsNodeLabel::Uninitialized, class });
671    }
672}
673
674/// Describes the security label for a [`crate::vfs::FsNode`].
675#[derive(Debug, Clone)]
676pub(super) enum FsNodeLabel {
677    Uninitialized,
678    SecurityId { sid: SecurityId },
679    // TODO(https://fxbug.dev/451613626): Consider replacing by a reference to a task-or-zombie.
680    FromTask { task_state: TaskPersistentInfo },
681}
682
683impl FsNodeLabel {
684    pub fn sid(&self) -> SecurityId {
685        match self {
686            FsNodeLabel::Uninitialized => InitialSid::Unlabeled.into(),
687            FsNodeLabel::SecurityId { sid } => *sid,
688            FsNodeLabel::FromTask { task_state } => {
689                task_state.real_creds().security_state.current_sid
690            }
691        }
692    }
693}
694
695#[derive(Debug, Clone)]
696pub(super) struct FsNodeLabelAndClass {
697    pub label: FsNodeLabel,
698    pub class: Option<FsNodeClass>,
699}
700
701impl FsNodeLabelAndClass {
702    pub fn class(&self) -> FsNodeClass {
703        self.class.unwrap_or(FsNodeClass::File(FileClass::File))
704    }
705
706    pub fn sid(&self) -> SecurityId {
707        self.label.sid()
708    }
709}
710
711/// Holds the SID and class with which an `FsNode` is labeled, for use in permissions checks.
712#[derive(Debug, PartialEq)]
713pub(super) struct FsNodeSidAndClass {
714    pub sid: SecurityId,
715    pub class: FsNodeClass,
716}
717
718/// Security state for a [`crate::binderfs::BinderConnection`] instance. This holds the
719/// [`starnix_uapi::selinux::SecurityId`] of the task as it was when it created the connection.
720#[derive(Clone, Debug, PartialEq)]
721pub(super) struct BinderConnectionState {
722    sid: SecurityId,
723}
724
725/// Security state for a [`crate::vfs::Socket`] instance. This holds the [`starnix_uapi::selinux::SecurityId`] of
726/// the peer socket.
727#[derive(Debug, Default)]
728pub(super) struct SocketState {
729    peer_sid: Mutex<Option<SecurityId>>,
730}
731
732/// Security state for a bpf [`ebpf_api::maps::Map`] instance. This currently just holds the
733/// SID that the [`crate::task::Task`] that created the file object had.
734#[derive(Clone, Debug, PartialEq)]
735pub(super) struct BpfMapState {
736    sid: SecurityId,
737}
738
739/// Security state for a bpf [`starnix_core::bpf::program::Program`]. instance. This currently just
740/// holds the SID that the [`crate::task::Task`] that created the file object had.
741#[derive(Clone, Debug, PartialEq)]
742pub(super) struct BpfProgState {
743    sid: SecurityId,
744}
745
746/// Sets the cached security id associated with `fs_node` to `sid`. Storing the security id will
747/// cause the security id to *not* be recomputed by the SELinux LSM when determining the effective
748/// security id of this [`FsNode`].
749pub(super) fn set_cached_sid(fs_node: &FsNode, sid: SecurityId) {
750    fs_node.security_state.0.update_label(FsNodeLabel::SecurityId { sid });
751}
752
753/// Sets the Task associated with `fs_node` to `task`.
754/// The effective security id of the [`FsNode`] will be that of the task, even if the security id
755/// of the task changes.
756fn fs_node_set_label_with_task(fs_node: &FsNode, task_persistent_info: &TaskPersistentInfo) {
757    fs_node
758        .security_state
759        .0
760        .update_label(FsNodeLabel::FromTask { task_state: task_persistent_info.clone() });
761}
762
763/// Ensures that the `fs_node`'s security state has an appropriate security class set.
764/// As per the NSA report description, the security class is chosen based on the `FileMode`, unless
765/// a security class more specific than "file" has already been set on the node.
766fn fs_node_ensure_class(fs_node: &FsNode) -> Result<FsNodeClass, Errno> {
767    let label_class = fs_node.security_state.0.read();
768    if let Some(class) = label_class.class {
769        return Ok(class);
770    }
771
772    let file_mode = fs_node.info().mode;
773    let _lock = fs_node.security_state.0.update_lock.lock();
774    let label_class = fs_node.security_state.0.read();
775    if let Some(class) = label_class.class {
776        return Ok(class);
777    }
778    let mut new_label_class = (*label_class).clone();
779    let class = file_class_from_file_mode(file_mode)?.into();
780    new_label_class.class = Some(class);
781    fs_node.security_state.0.label.update(new_label_class);
782    Ok(class)
783}
784
785#[cfg(test)]
786/// Returns the SID with which the node is labeled, if any, for use by `FsNode` labeling tests.
787pub(super) fn get_cached_sid(fs_node: &FsNode) -> Option<SecurityId> {
788    let label_class = fs_node.security_state.0.read();
789    if !matches!(label_class.label, FsNodeLabel::Uninitialized) {
790        Some(label_class.sid())
791    } else {
792        None
793    }
794}
795
796/// Returned by `policycap_support()` to indicate whether a policy is always-on, always-off,
797/// the affected functionality is not-implemented, or it is fully supported/configurable.
798#[derive(Debug)]
799pub enum PolicyCapSupport {
800    AlwaysOn(BugRef),
801    AlwaysOff(BugRef),
802    Configurable,
803    NotImplemented,
804}
805
806/// Returns a `PolicyCapSupport` indicating the state of support, and the `BugRef` to report if
807/// emitting a partial support warning.
808fn policycap_support(policy_cap: PolicyCap) -> PolicyCapSupport {
809    match policy_cap {
810        PolicyCap::AlwaysCheckNetwork => {
811            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
812        }
813        PolicyCap::CgroupSeclabel => PolicyCapSupport::Configurable,
814        PolicyCap::ExtendedSocketClass => PolicyCapSupport::Configurable,
815        PolicyCap::FunctionfsSeclabel => PolicyCapSupport::Configurable,
816        PolicyCap::GenfsSeclabelSymlinks => PolicyCapSupport::Configurable,
817        PolicyCap::GenfsSeclabelWildcard => {
818            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
819        }
820        PolicyCap::IoctlSkipCloexec => PolicyCapSupport::Configurable,
821        PolicyCap::MemfdClass => PolicyCapSupport::Configurable,
822        PolicyCap::NetifWildcard => {
823            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
824        }
825        PolicyCap::NetlinkXperm => PolicyCapSupport::Configurable,
826        PolicyCap::NetworkPeerControls => PolicyCapSupport::NotImplemented,
827        PolicyCap::NnpNosuidTransition => PolicyCapSupport::Configurable,
828        PolicyCap::OpenPerms => PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565")),
829        PolicyCap::UserspaceInitialContext => {
830            PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565"))
831        }
832    }
833}