Skip to main content

starnix_core/security/selinux_hooks/
mod.rs

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