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