Skip to main content

starnix_core/security/selinux_hooks/
mod.rs

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