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
482async fn create_inspect_values(
483    security_server: Arc<SecurityServer>,
484) -> Result<fuchsia_inspect::Inspector, anyhow::Error> {
485    let inspector = fuchsia_inspect::Inspector::default();
486
487    let policy_bytes = if let Some(policy_data) = security_server.get_binary_policy() {
488        policy_data.len().try_into()?
489    } else {
490        0
491    };
492    inspector.root().record_uint("policy_bytes", policy_bytes);
493
494    Ok(inspector)
495}
496
497/// Returns the security state structure for the kernel.
498pub(super) fn kernel_init_security(
499    options: String,
500    exceptions: Vec<String>,
501    inspect_node: &fuchsia_inspect::Node,
502) -> KernelState {
503    let server = SecurityServer::new(options, exceptions);
504    let inspect_node = inspect_node.create_child("selinux");
505
506    let server_for_inspect = server.clone();
507    inspect_node.record_lazy_values("server", move || {
508        Box::pin(create_inspect_values(server_for_inspect.clone()))
509    });
510
511    KernelState {
512        server,
513        pending_file_systems: Mutex::default(),
514        selinuxfs_null: OnceLock::default(),
515        access_denial_count: AtomicU64::new(0u64),
516        has_policy: false.into(),
517        _inspect_node: inspect_node,
518    }
519}
520
521/// The global SELinux security structures, held by the `Kernel`.
522pub(super) struct KernelState {
523    // Owning reference to the SELinux `SecurityServer`.
524    pub(super) server: Arc<SecurityServer>,
525
526    /// Set of [`create::vfs::FileSystem`]s that have been constructed, and must be labeled as soon
527    /// as a policy is loaded into the `server`. Insertion order is retained, via use of `IndexSet`,
528    /// to ensure that filesystems have labels initialized in creation order, which is important
529    /// e.g. when initializing "overlayfs" node labels, based on the labels of the underlying nodes.
530    pub(super) pending_file_systems: Mutex<IndexSet<WeakKey<FileSystem>>>,
531
532    /// True when the `server` has a policy loaded.
533    pub(super) has_policy: AtomicBool,
534
535    /// Stashed reference to "/sys/fs/selinux/null" used for replacing inaccessible file descriptors
536    /// with a null file.
537    pub(super) selinuxfs_null: OnceLock<FileHandle>,
538
539    /// Counts the number of times that an AVC denial is audit-logged.
540    pub(super) access_denial_count: AtomicU64,
541
542    /// Inspect node through which SELinux status is exposed.
543    pub(super) _inspect_node: fuchsia_inspect::Node,
544}
545
546impl KernelState {
547    pub(super) fn access_denial_count(&self) -> u64 {
548        self.access_denial_count.load(Ordering::Acquire)
549    }
550
551    pub(super) fn has_policy(&self) -> bool {
552        self.has_policy.load(Ordering::Acquire)
553    }
554}
555
556// Security state for a PerfEventFileState instance.
557#[derive(Clone, Debug, PartialEq)]
558pub(super) struct PerfEventState {
559    sid: SecurityId,
560}
561
562/// The SELinux security structure for `ThreadGroup`.
563#[derive(Clone, Debug, PartialEq)]
564pub(super) struct TaskAttrs {
565    /// Current SID for the task.
566    pub current_sid: SecurityId,
567
568    /// SID for the task upon the next execve call.
569    pub exec_sid: Option<SecurityId>,
570
571    /// SID for files created by the task.
572    pub fscreate_sid: Option<SecurityId>,
573
574    /// SID for kernel-managed keys created by the task.
575    pub keycreate_sid: Option<SecurityId>,
576
577    /// SID prior to the last execve.
578    pub previous_sid: SecurityId,
579
580    /// SID for sockets created by the task.
581    pub sockcreate_sid: Option<SecurityId>,
582
583    /// Indicates that the task with these credentials is performing an internal operation where
584    /// access checks must be skipped.
585    pub internal_operation: bool,
586}
587
588impl TaskAttrs {
589    /// Returns initial state for kernel tasks.
590    pub(super) fn for_kernel() -> Self {
591        Self::for_sid(InitialSid::Kernel.into())
592    }
593
594    /// Returns placeholder state for use when SELinux is not enabled.
595    pub(super) fn for_selinux_disabled() -> Self {
596        Self::for_sid(InitialSid::Unlabeled.into())
597    }
598
599    /// Used to create initial state for tasks with a specified SID.
600    pub(super) fn for_sid(sid: SecurityId) -> Self {
601        Self {
602            current_sid: sid,
603            previous_sid: sid,
604            exec_sid: None,
605            fscreate_sid: None,
606            keycreate_sid: None,
607            sockcreate_sid: None,
608            internal_operation: false,
609        }
610    }
611}
612
613pub(super) enum CurrentTaskStateHolder<'a> {
614    TaskState(&'a TaskState),
615    OverriddenTaskState(Ref<'a, Option<FullCredentials>>),
616}
617
618impl Deref for CurrentTaskStateHolder<'_> {
619    type Target = TaskState;
620    fn deref(&self) -> &Self::Target {
621        match self {
622            CurrentTaskStateHolder::TaskState(task_attrs) => &task_attrs,
623            CurrentTaskStateHolder::OverriddenTaskState(overridden_creds) => {
624                &overridden_creds.as_ref().unwrap().security_state
625            }
626        }
627    }
628}
629
630pub(in crate::security) fn current_task_state(
631    current_task: &CurrentTask,
632) -> CurrentTaskStateHolder<'_> {
633    if current_task.has_overridden_creds() {
634        CurrentTaskStateHolder::OverriddenTaskState(current_task.overridden_creds.borrow())
635    } else {
636        CurrentTaskStateHolder::TaskState(&current_task.security_state)
637    }
638}
639
640/// Returns the SID of a task. Panics if the task is using overridden credentials.
641pub(in crate::security) fn task_consistent_attrs(
642    current_task: &CurrentTask,
643) -> MutexGuard<'_, TaskAttrs> {
644    assert!(!current_task.has_overridden_creds());
645    current_task.security_state.lock()
646}
647
648/// Security state for a [`crate::vfs::FileObject`] instance. This currently just holds the SID
649/// that the [`crate::task::Task`] that created the file object had.
650#[derive(Debug)]
651pub(super) struct FileObjectState {
652    sid: SecurityId,
653}
654
655/// Security state for a [`crate::vfs::FileSystem`] instance. This holds the security fields
656/// parsed from the mount options and the selected labeling scheme.
657#[derive(Debug)]
658pub(super) struct FileSystemState {
659    // Fields used prior to policy-load, to hold mount options, etc.
660    mount_options: FileSystemMountOptions,
661    pending_entries: Mutex<IndexSet<WeakKey<DirEntry>>>,
662
663    // Set once the initial policy has been loaded, taking into account `mount_options`.
664    label: OnceLock<FileSystemLabel>,
665}
666
667impl FileSystemState {
668    fn new(mount_options: FileSystemMountOptions, _ops: &dyn FileSystemOps) -> Self {
669        let pending_entries = Mutex::new(IndexSet::new());
670        let label = OnceLock::new();
671
672        Self { mount_options, pending_entries, label }
673    }
674
675    /// Returns the resolved `FileSystemLabel`, or `None` if no policy has yet been loaded.
676    pub fn label(&self) -> Option<&FileSystemLabel> {
677        self.label.get()
678    }
679
680    /// Returns true if this file system supports dynamic re-labeling of file nodes.
681    pub fn supports_relabel(&self) -> bool {
682        let Some(label) = self.label() else {
683            return false;
684        };
685        match label.scheme {
686            FileSystemLabelingScheme::Mountpoint { .. } => false,
687            FileSystemLabelingScheme::FsUse { .. } => true,
688            FileSystemLabelingScheme::GenFsCon { supports_seclabel } => supports_seclabel,
689        }
690    }
691
692    /// Returns true if this file system persists labels in extended attributes.
693    pub fn supports_xattr(&self) -> bool {
694        let Some(label) = self.label() else {
695            return false;
696        };
697        match label.scheme {
698            FileSystemLabelingScheme::Mountpoint { .. }
699            | FileSystemLabelingScheme::GenFsCon { .. } => false,
700            FileSystemLabelingScheme::FsUse { fs_use_type, .. } => fs_use_type == FsUseType::Xattr,
701        }
702    }
703
704    /// Writes the Security mount options for the `FileSystemState` into `buf`.
705    /// This is used where the mount options need to be stringified to expose to userspace, as
706    /// is the case for `/proc/mounts`
707    ///
708    /// This function always writes a leading comma because it is only ever called to append to a
709    /// non-empty list of comma-separated values.
710    ///
711    /// Example output:
712    ///     ",context=foo,root_context=bar"
713    fn write_mount_options(
714        &self,
715        security_server: &SecurityServer,
716        buf: &mut impl OutputBuffer,
717    ) -> Result<(), Errno> {
718        let Some(label) = self.label() else {
719            return Self::write_mount_options_to_buf(buf, &self.mount_options);
720        };
721
722        let to_context = |sid| security_server.sid_to_security_context(sid);
723        let mount_options = FileSystemMountOptions {
724            context: label.mount_sids.context.and_then(to_context),
725            fs_context: label.mount_sids.fs_context.and_then(to_context),
726            def_context: label.mount_sids.def_context.and_then(to_context),
727            root_context: label.mount_sids.root_context.and_then(to_context),
728        };
729
730        if self.supports_relabel() {
731            buf.write_all(b",seclabel").map(|_| ())?;
732        }
733
734        Self::write_mount_options_to_buf(buf, &mount_options)
735    }
736
737    /// Writes the supplied `mount_options` to the `OutputBuffer`.
738    fn write_mount_options_to_buf(
739        buf: &mut impl OutputBuffer,
740        mount_options: &FileSystemMountOptions,
741    ) -> Result<(), Errno> {
742        let mut write_option = |prefix: &[u8], option: &Option<Vec<u8>>| -> Result<(), Errno> {
743            let Some(value) = option else {
744                return Ok(());
745            };
746            buf.write_all(prefix).map(|_| ())?;
747            buf.write_all(value).map(|_| ())
748        };
749        write_option(b",context=", &mount_options.context)?;
750        write_option(b",fscontext=", &mount_options.fs_context)?;
751        write_option(b",defcontext=", &mount_options.def_context)?;
752        write_option(b",rootcontext=", &mount_options.root_context)
753    }
754}
755
756/// Holds security state associated with a [`crate::vfs::FsNode`].
757#[derive(Debug, Clone)]
758pub struct FsNodeState {
759    label: FsNodeLabel,
760    class: FsNodeClass,
761}
762
763impl Default for FsNodeState {
764    fn default() -> Self {
765        Self { label: FsNodeLabel::Uninitialized, class: FileClass::File.into() }
766    }
767}
768
769/// Describes the security label for a [`crate::vfs::FsNode`].
770#[derive(Debug, Clone)]
771pub(super) enum FsNodeLabel {
772    Uninitialized,
773    SecurityId { sid: SecurityId },
774    // TODO(https://fxbug.dev/451613626): Consider replacing by a reference to a task-or-zombie.
775    FromTask { task_state: Arc<Mutex<TaskAttrs>> },
776}
777
778impl FsNodeLabel {
779    fn is_initialized(&self) -> bool {
780        !matches!(self, FsNodeLabel::Uninitialized)
781    }
782}
783
784/// Holds the SID and class with which an `FsNode` is labeled, for use in permissions checks.
785#[derive(Debug, PartialEq)]
786struct FsNodeSidAndClass {
787    sid: SecurityId,
788    class: FsNodeClass,
789}
790
791/// Security state for a [`crate::binderfs::BinderConnection`] instance. This holds the
792/// [`selinux::SecurityId`] of the task as it was when it created the connection.
793#[derive(Clone, Debug, PartialEq)]
794pub(super) struct BinderConnectionState {
795    sid: SecurityId,
796}
797
798/// Security state for a [`crate::vfs::Socket`] instance. This holds the [`selinux::SecurityId`] of
799/// the peer socket.
800#[derive(Debug, Default)]
801pub(super) struct SocketState {
802    peer_sid: Mutex<Option<SecurityId>>,
803}
804
805/// Security state for a bpf [`ebpf_api::maps::Map`] instance. This currently just holds the
806/// SID that the [`crate::task::Task`] that created the file object had.
807#[derive(Clone, Debug, PartialEq)]
808pub(super) struct BpfMapState {
809    sid: SecurityId,
810}
811
812/// Security state for a bpf [`starnix_core::bpf::program::Program`]. instance. This currently just
813/// holds the SID that the [`crate::task::Task`] that created the file object had.
814#[derive(Clone, Debug, PartialEq)]
815pub(super) struct BpfProgState {
816    sid: SecurityId,
817}
818
819/// Sets the cached security id associated with `fs_node` to `sid`. Storing the security id will
820/// cause the security id to *not* be recomputed by the SELinux LSM when determining the effective
821/// security id of this [`FsNode`].
822pub(super) fn set_cached_sid(fs_node: &FsNode, sid: SecurityId) {
823    fs_node.security_state.lock().label = FsNodeLabel::SecurityId { sid };
824}
825
826/// Sets the Task associated with `fs_node` to `task`.
827/// The effective security id of the [`FsNode`] will be that of the task, even if the security id
828/// of the task changes.
829fn fs_node_set_label_with_task(fs_node: &FsNode, task_state: Arc<Mutex<TaskAttrs>>) {
830    fs_node.security_state.lock().label = FsNodeLabel::FromTask { task_state };
831}
832
833/// Ensures that the `fs_node`'s security state has an appropriate security class set.
834/// As per the NSA report description, the security class is chosen based on the `FileMode`, unless
835/// a security class more specific than "file" has already been set on the node.
836fn fs_node_ensure_class(fs_node: &FsNode) -> Result<FsNodeClass, Errno> {
837    // TODO: Consider moving the class into a `OnceLock`.
838    let class = fs_node.security_state.lock().class;
839    if class != FileClass::File.into() {
840        return Ok(class);
841    }
842
843    let file_mode = fs_node.info().mode;
844    let class = file_class_from_file_mode(file_mode)?.into();
845    fs_node.security_state.lock().class = class;
846    Ok(class)
847}
848
849#[cfg(test)]
850/// Returns the SID with which the node is labeled, if any, for use by `FsNode` labeling tests.
851pub(super) fn get_cached_sid(fs_node: &FsNode) -> Option<SecurityId> {
852    let state = fs_node.security_state.lock().clone();
853    if matches!(state.label, FsNodeLabel::Uninitialized) {
854        None
855    } else {
856        Some(fs_node_effective_sid_and_class(fs_node).sid)
857    }
858}
859
860/// Returned by `policycap_support()` to indicate whether a policy is always-on, always-off,
861/// the affected functionality is not-implemented, or it is fully supported/configurable.
862#[derive(Debug)]
863pub enum PolicyCapSupport {
864    AlwaysOn(BugRef),
865    AlwaysOff(BugRef),
866    Configurable,
867    NotImplemented,
868}
869
870/// Returns a `PolicyCapSupport` indicating the state of support, and the `BugRef` to report if
871/// emitting a partial support warning.
872fn policycap_support(policy_cap: PolicyCap) -> PolicyCapSupport {
873    match policy_cap {
874        PolicyCap::AlwaysCheckNetwork => {
875            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
876        }
877        PolicyCap::CgroupSeclabel => PolicyCapSupport::Configurable,
878        PolicyCap::ExtendedSocketClass => {
879            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
880        }
881        PolicyCap::FunctionfsSeclabel => PolicyCapSupport::Configurable,
882        PolicyCap::GenfsSeclabelSymlinks => PolicyCapSupport::Configurable,
883        PolicyCap::GenfsSeclabelWildcard => {
884            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
885        }
886        PolicyCap::IoctlSkipCloexec => {
887            PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565"))
888        }
889        PolicyCap::MemfdClass => PolicyCapSupport::Configurable,
890        PolicyCap::NetifWildcard => {
891            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
892        }
893        PolicyCap::NetlinkXperm => PolicyCapSupport::Configurable,
894        PolicyCap::NetworkPeerControls => PolicyCapSupport::NotImplemented,
895        PolicyCap::NnpNosuidTransition => PolicyCapSupport::Configurable,
896        PolicyCap::OpenPerms => PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565")),
897        PolicyCap::UserspaceInitialContext => {
898            PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565"))
899        }
900    }
901}