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::{CurrentCreds, 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::errors::Errno;
41use starnix_uapi::file_mode::FileMode;
42use starnix_uapi::{errno, error};
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_or_else(|| errno!(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_or_else(|| errno!(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, TaskState>),
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.deref()
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(Ref::map(
635            current_task.current_creds.borrow(),
636            |creds| match creds {
637                CurrentCreds::Overridden(_, security_state) => security_state,
638                CurrentCreds::Cached(_) => unreachable!("Inconsistent state"),
639            },
640        ))
641    } else {
642        CurrentTaskStateHolder::TaskState(&current_task.security_state)
643    }
644}
645
646/// Returns the SID of a task. Panics if the task is using overridden credentials.
647pub(in crate::security) fn task_consistent_attrs(
648    current_task: &CurrentTask,
649) -> MutexGuard<'_, TaskAttrs> {
650    assert!(!current_task.has_overridden_creds());
651    current_task.security_state.lock()
652}
653
654/// Creates a copy of the credentials with the given `fscreate_sid`.
655pub fn creds_with_fscreate_sid(
656    current_task: &CurrentTask,
657    fscreate_sid: SecurityId,
658) -> FullCredentials {
659    let creds = current_task.full_current_creds();
660    creds.security_state.lock().fscreate_sid = Some(fscreate_sid);
661    creds
662}
663
664/// Security state for a [`crate::vfs::FileObject`] instance. This currently just holds the SID
665/// that the [`crate::task::Task`] that created the file object had.
666#[derive(Debug)]
667pub(super) struct FileObjectState {
668    sid: SecurityId,
669}
670
671/// Security state for a [`crate::vfs::FileSystem`] instance. This holds the security fields
672/// parsed from the mount options and the selected labeling scheme.
673#[derive(Debug)]
674pub(super) struct FileSystemState {
675    // Fields used prior to policy-load, to hold mount options, etc.
676    mount_options: FileSystemMountOptions,
677    pending_entries: Mutex<IndexSet<WeakKey<DirEntry>>>,
678
679    // Set once the initial policy has been loaded, taking into account `mount_options`.
680    label: OnceLock<FileSystemLabel>,
681}
682
683impl FileSystemState {
684    fn new(mount_options: FileSystemMountOptions, _ops: &dyn FileSystemOps) -> Self {
685        let pending_entries = Mutex::new(IndexSet::new());
686        let label = OnceLock::new();
687
688        Self { mount_options, pending_entries, label }
689    }
690
691    /// Returns the resolved `FileSystemLabel`, or `None` if no policy has yet been loaded.
692    pub fn label(&self) -> Option<&FileSystemLabel> {
693        self.label.get()
694    }
695
696    /// Returns true if this file system supports dynamic re-labeling of file nodes.
697    pub fn supports_relabel(&self) -> bool {
698        let Some(label) = self.label() else {
699            return false;
700        };
701        match label.scheme {
702            FileSystemLabelingScheme::Mountpoint { .. } => false,
703            FileSystemLabelingScheme::FsUse { .. } => true,
704            FileSystemLabelingScheme::GenFsCon { supports_seclabel } => supports_seclabel,
705        }
706    }
707
708    /// Returns true if this file system persists labels in extended attributes.
709    pub fn supports_xattr(&self) -> bool {
710        let Some(label) = self.label() else {
711            return false;
712        };
713        match label.scheme {
714            FileSystemLabelingScheme::Mountpoint { .. }
715            | FileSystemLabelingScheme::GenFsCon { .. } => false,
716            FileSystemLabelingScheme::FsUse { fs_use_type, .. } => fs_use_type == FsUseType::Xattr,
717        }
718    }
719
720    /// Writes the Security mount options for the `FileSystemState` into `buf`.
721    /// This is used where the mount options need to be stringified to expose to userspace, as
722    /// is the case for `/proc/mounts`
723    ///
724    /// This function always writes a leading comma because it is only ever called to append to a
725    /// non-empty list of comma-separated values.
726    ///
727    /// Example output:
728    ///     ",context=foo,root_context=bar"
729    fn write_mount_options(
730        &self,
731        security_server: &SecurityServer,
732        buf: &mut impl OutputBuffer,
733    ) -> Result<(), Errno> {
734        let Some(label) = self.label() else {
735            return Self::write_mount_options_to_buf(buf, &self.mount_options);
736        };
737
738        let to_context = |sid| security_server.sid_to_security_context(sid);
739        let mount_options = FileSystemMountOptions {
740            context: label.mount_sids.context.and_then(to_context),
741            fs_context: label.mount_sids.fs_context.and_then(to_context),
742            def_context: label.mount_sids.def_context.and_then(to_context),
743            root_context: label.mount_sids.root_context.and_then(to_context),
744        };
745
746        if self.supports_relabel() {
747            buf.write_all(b",seclabel").map(|_| ())?;
748        }
749
750        Self::write_mount_options_to_buf(buf, &mount_options)
751    }
752
753    /// Writes the supplied `mount_options` to the `OutputBuffer`.
754    fn write_mount_options_to_buf(
755        buf: &mut impl OutputBuffer,
756        mount_options: &FileSystemMountOptions,
757    ) -> Result<(), Errno> {
758        let mut write_option = |prefix: &[u8], option: &Option<Vec<u8>>| -> Result<(), Errno> {
759            let Some(value) = option else {
760                return Ok(());
761            };
762            buf.write_all(prefix).map(|_| ())?;
763            buf.write_all(value).map(|_| ())
764        };
765        write_option(b",context=", &mount_options.context)?;
766        write_option(b",fscontext=", &mount_options.fs_context)?;
767        write_option(b",defcontext=", &mount_options.def_context)?;
768        write_option(b",rootcontext=", &mount_options.root_context)
769    }
770}
771
772/// Holds security state associated with a [`crate::vfs::FsNode`].
773#[derive(Debug, Clone)]
774pub struct FsNodeState {
775    label: FsNodeLabel,
776    class: FsNodeClass,
777}
778
779impl Default for FsNodeState {
780    fn default() -> Self {
781        Self { label: FsNodeLabel::Uninitialized, class: FileClass::File.into() }
782    }
783}
784
785/// Describes the security label for a [`crate::vfs::FsNode`].
786#[derive(Debug, Clone)]
787pub(super) enum FsNodeLabel {
788    Uninitialized,
789    SecurityId { sid: SecurityId },
790    // TODO(https://fxbug.dev/451613626): Consider replacing by a reference to a task-or-zombie.
791    FromTask { task_state: Arc<Mutex<TaskAttrs>> },
792}
793
794impl FsNodeLabel {
795    fn is_initialized(&self) -> bool {
796        !matches!(self, FsNodeLabel::Uninitialized)
797    }
798}
799
800/// Holds the SID and class with which an `FsNode` is labeled, for use in permissions checks.
801#[derive(Debug, PartialEq)]
802struct FsNodeSidAndClass {
803    sid: SecurityId,
804    class: FsNodeClass,
805}
806
807/// Security state for a [`crate::binderfs::BinderConnection`] instance. This holds the
808/// [`selinux::SecurityId`] of the task as it was when it created the connection.
809#[derive(Clone, Debug, PartialEq)]
810pub(super) struct BinderConnectionState {
811    sid: SecurityId,
812}
813
814/// Security state for a [`crate::vfs::Socket`] instance. This holds the [`selinux::SecurityId`] of
815/// the peer socket.
816#[derive(Debug, Default)]
817pub(super) struct SocketState {
818    peer_sid: Mutex<Option<SecurityId>>,
819}
820
821/// Security state for a bpf [`ebpf_api::maps::Map`] instance. This currently just holds the
822/// SID that the [`crate::task::Task`] that created the file object had.
823#[derive(Clone, Debug, PartialEq)]
824pub(super) struct BpfMapState {
825    sid: SecurityId,
826}
827
828/// Security state for a bpf [`starnix_core::bpf::program::Program`]. instance. This currently just
829/// holds the SID that the [`crate::task::Task`] that created the file object had.
830#[derive(Clone, Debug, PartialEq)]
831pub(super) struct BpfProgState {
832    sid: SecurityId,
833}
834
835/// Sets the cached security id associated with `fs_node` to `sid`. Storing the security id will
836/// cause the security id to *not* be recomputed by the SELinux LSM when determining the effective
837/// security id of this [`FsNode`].
838pub(super) fn set_cached_sid(fs_node: &FsNode, sid: SecurityId) {
839    fs_node.security_state.lock().label = FsNodeLabel::SecurityId { sid };
840}
841
842/// Sets the Task associated with `fs_node` to `task`.
843/// The effective security id of the [`FsNode`] will be that of the task, even if the security id
844/// of the task changes.
845fn fs_node_set_label_with_task(fs_node: &FsNode, task_state: Arc<Mutex<TaskAttrs>>) {
846    fs_node.security_state.lock().label = FsNodeLabel::FromTask { task_state };
847}
848
849/// Ensures that the `fs_node`'s security state has an appropriate security class set.
850/// As per the NSA report description, the security class is chosen based on the `FileMode`, unless
851/// a security class more specific than "file" has already been set on the node.
852fn fs_node_ensure_class(fs_node: &FsNode) -> Result<FsNodeClass, Errno> {
853    // TODO: Consider moving the class into a `OnceLock`.
854    let class = fs_node.security_state.lock().class;
855    if class != FileClass::File.into() {
856        return Ok(class);
857    }
858
859    let file_mode = fs_node.info().mode;
860    let class = file_class_from_file_mode(file_mode)?.into();
861    fs_node.security_state.lock().class = class;
862    Ok(class)
863}
864
865#[cfg(test)]
866/// Returns the SID with which the node is labeled, if any, for use by `FsNode` labeling tests.
867pub(super) fn get_cached_sid(fs_node: &FsNode) -> Option<SecurityId> {
868    let state = fs_node.security_state.lock().clone();
869    if matches!(state.label, FsNodeLabel::Uninitialized) {
870        None
871    } else {
872        Some(fs_node_effective_sid_and_class(fs_node).sid)
873    }
874}
875
876/// Returned by `policycap_support()` to indicate whether a policy is always-on, always-off,
877/// the affected functionality is not-implemented, or it is fully supported/configurable.
878#[derive(Debug)]
879pub enum PolicyCapSupport {
880    AlwaysOn(BugRef),
881    AlwaysOff(BugRef),
882    Configurable,
883    NotImplemented,
884}
885
886/// Returns a `PolicyCapSupport` indicating the state of support, and the `BugRef` to report if
887/// emitting a partial support warning.
888fn policycap_support(policy_cap: PolicyCap) -> PolicyCapSupport {
889    match policy_cap {
890        PolicyCap::AlwaysCheckNetwork => {
891            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
892        }
893        PolicyCap::CgroupSeclabel => PolicyCapSupport::Configurable,
894        PolicyCap::ExtendedSocketClass => {
895            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
896        }
897        PolicyCap::FunctionfsSeclabel => PolicyCapSupport::Configurable,
898        PolicyCap::GenfsSeclabelSymlinks => PolicyCapSupport::Configurable,
899        PolicyCap::GenfsSeclabelWildcard => {
900            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
901        }
902        PolicyCap::IoctlSkipCloexec => {
903            PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565"))
904        }
905        PolicyCap::MemfdClass => PolicyCapSupport::Configurable,
906        PolicyCap::NetifWildcard => {
907            PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
908        }
909        PolicyCap::NetlinkXperm => PolicyCapSupport::Configurable,
910        PolicyCap::NetworkPeerControls => PolicyCapSupport::NotImplemented,
911        PolicyCap::NnpNosuidTransition => PolicyCapSupport::Configurable,
912        PolicyCap::OpenPerms => PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565")),
913        PolicyCap::UserspaceInitialContext => {
914            PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565"))
915        }
916    }
917}