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