Skip to main content

selinux/
lib.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
5pub mod permission_check;
6pub mod policy;
7pub mod security_server;
8
9pub use security_server::SecurityServer;
10
11mod access_cache;
12mod access_vector_cache;
13mod exceptions_config;
14mod kernel_permissions;
15mod sid_table;
16mod sync;
17
18/// Allow callers to use the kernel class & permission definitions.
19pub use kernel_permissions::*;
20
21/// Numeric class Ids are provided to the userspace AVC surfaces (e.g. "create", "access", etc).
22pub use policy::ClassId;
23
24pub use starnix_uapi::selinux::{InitialSid, ReferenceInitialSid, SecurityId, TaskAttrs};
25
26use policy::arrays::FsUseType;
27use strum::VariantArray as _;
28use strum_macros::VariantArray;
29
30/// Identifies a specific class by its policy-defined Id, or as a kernel object class enum Id.
31#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
32pub enum ObjectClass {
33    /// Refers to a well-known SELinux kernel object class (e.g. "process", "file", "capability").
34    Kernel(KernelClass),
35    /// Refers to a policy-defined class by its policy-defined numeric Id. This is most commonly
36    /// used when handling queries from userspace, which refer to classes by-Id.
37    ClassId(ClassId),
38}
39
40impl From<ClassId> for ObjectClass {
41    fn from(id: ClassId) -> Self {
42        Self::ClassId(id)
43    }
44}
45
46impl<T: Into<KernelClass>> From<T> for ObjectClass {
47    fn from(class: T) -> Self {
48        Self::Kernel(class.into())
49    }
50}
51
52/// A borrowed byte slice that contains no `NUL` characters by truncating the input slice at the
53/// first `NUL` (if any) upon construction.
54#[derive(Clone, Copy, Debug, PartialEq)]
55pub struct NullessByteStr<'a>(&'a [u8]);
56
57impl<'a> NullessByteStr<'a> {
58    /// Returns a non-null-terminated representation of the security context string.
59    pub fn as_bytes(&self) -> &[u8] {
60        &self.0
61    }
62}
63
64impl<'a, S: AsRef<[u8]> + ?Sized> From<&'a S> for NullessByteStr<'a> {
65    /// Any `AsRef<[u8]>` can be processed into a [`NullessByteStr`]. The [`NullessByteStr`] will
66    /// retain everything up to (but not including) a null character, or else the complete byte
67    /// string.
68    fn from(s: &'a S) -> Self {
69        let value = s.as_ref();
70        match value.iter().position(|c| *c == 0) {
71            Some(end) => Self(&value[..end]),
72            None => Self(value),
73        }
74    }
75}
76
77#[derive(Clone, Debug, PartialEq)]
78pub struct FileSystemMountSids {
79    pub context: Option<SecurityId>,
80    pub fs_context: Option<SecurityId>,
81    pub def_context: Option<SecurityId>,
82    pub root_context: Option<SecurityId>,
83}
84
85#[derive(Clone, Debug, PartialEq)]
86pub struct FileSystemLabel {
87    pub sid: SecurityId,
88    pub scheme: FileSystemLabelingScheme,
89    // Sids obtained by parsing the mount options of the FileSystem.
90    pub mount_sids: FileSystemMountSids,
91}
92
93#[derive(Clone, Debug, PartialEq)]
94pub enum FileSystemLabelingScheme {
95    /// This filesystem was mounted with "context=".
96    Mountpoint { sid: SecurityId },
97    /// This filesystem has an "fs_use_xattr", "fs_use_task", or "fs_use_trans" entry in the
98    /// policy. If the `fs_use_type` is "fs_use_xattr" then the `default_sid` specifies the SID
99    /// with which to label `FsNode`s of files that do not have the "security.selinux" xattr.
100    FsUse { fs_use_type: FsUseType, default_sid: SecurityId },
101    /// This filesystem has one or more "genfscon" statements associated with it in the policy.
102    /// If `supports_seclabel` is true then nodes in the filesystem may be dynamically relabeled.
103    GenFsCon { supports_seclabel: bool },
104}
105
106/// SELinux security context-related filesystem mount options. These options are documented in the
107/// `context=context, fscontext=context, defcontext=context, and rootcontext=context` section of
108/// the `mount(8)` manpage.
109#[derive(Clone, Debug, Default, PartialEq)]
110pub struct FileSystemMountOptions {
111    /// Specifies the effective security context to use for all nodes in the filesystem, and the
112    /// filesystem itself. If the filesystem already contains security attributes then these are
113    /// ignored. May not be combined with any of the other options.
114    pub context: Option<Vec<u8>>,
115    /// Specifies an effective security context to use for un-labeled nodes in the filesystem,
116    /// rather than falling-back to the policy-defined "file" context.
117    pub def_context: Option<Vec<u8>>,
118    /// The value of the `fscontext=[security-context]` mount option. This option is used to
119    /// label the filesystem (superblock) itself.
120    pub fs_context: Option<Vec<u8>>,
121    /// The value of the `rootcontext=[security-context]` mount option. This option is used to
122    /// (re)label the inode located at the filesystem mountpoint.
123    pub root_context: Option<Vec<u8>>,
124}
125
126/// Status information parameter for the [`SeLinuxStatusPublisher`] interface.
127pub struct SeLinuxStatus {
128    /// SELinux-wide enforcing vs. permissive mode  bit.
129    pub is_enforcing: bool,
130    /// Number of times the policy has been changed since SELinux started.
131    pub change_count: u32,
132    /// Bit indicating whether operations unknown SELinux abstractions will be denied.
133    pub deny_unknown: bool,
134}
135
136/// Interface for security server to interact with selinuxfs status file.
137pub trait SeLinuxStatusPublisher: Send + Sync {
138    /// Sets the value part of the associated selinuxfs status file.
139    fn set_status(&mut self, policy_status: SeLinuxStatus);
140}
141
142/// Reference policy capability Ids.
143#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, VariantArray)]
144pub enum PolicyCap {
145    NetworkPeerControls = 0,
146    OpenPerms = 1,
147    ExtendedSocketClass = 2,
148    AlwaysCheckNetwork = 3,
149    CgroupSeclabel = 4,
150    NnpNosuidTransition = 5,
151    GenfsSeclabelSymlinks = 6,
152    IoctlSkipCloexec = 7,
153    UserspaceInitialContext = 8,
154    NetlinkXperm = 9,
155    NetifWildcard = 10,
156    GenfsSeclabelWildcard = 11,
157    FunctionfsSeclabel = 12,
158    MemfdClass = 13,
159}
160
161impl PolicyCap {
162    pub fn name(&self) -> &str {
163        match self {
164            Self::NetworkPeerControls => "network_peer_controls",
165            Self::OpenPerms => "open_perms",
166            Self::ExtendedSocketClass => "extended_socket_class",
167            Self::AlwaysCheckNetwork => "always_check_network",
168            Self::CgroupSeclabel => "cgroup_seclabel",
169            Self::NnpNosuidTransition => "nnp_nosuid_transition",
170            Self::GenfsSeclabelSymlinks => "genfs_seclabel_symlinks",
171            Self::IoctlSkipCloexec => "ioctl_skip_cloexec",
172            Self::UserspaceInitialContext => "userspace_initial_context",
173            Self::NetlinkXperm => "netlink_xperm",
174            Self::NetifWildcard => "netif_wildcard",
175            Self::GenfsSeclabelWildcard => "genfs_seclabel_wildcard",
176            Self::FunctionfsSeclabel => "functionfs_seclabel",
177            Self::MemfdClass => "memfd_class",
178        }
179    }
180
181    pub fn by_name(name: &str) -> Option<Self> {
182        Self::VARIANTS.iter().find(|x| x.name() == name).copied()
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use super::*;
189    use std::num::NonZeroU32;
190
191    #[test]
192    fn object_class_permissions() {
193        let test_class_id = ClassId::new(NonZeroU32::new(20).unwrap());
194        assert_eq!(ObjectClass::ClassId(test_class_id), test_class_id.into());
195        for variant in ProcessPermission::VARIANTS {
196            assert_eq!(KernelClass::Process, variant.class());
197            assert_eq!("process", variant.class().name());
198            let permission = (*variant).into();
199            assert_eq!(KernelPermission::Process(*variant), permission);
200            assert_eq!(ObjectClass::Kernel(KernelClass::Process), variant.class().into());
201        }
202    }
203
204    #[test]
205    fn policy_capabilities() {
206        for capability in PolicyCap::VARIANTS {
207            assert_eq!(Some(*capability), PolicyCap::by_name(capability.name()));
208        }
209    }
210
211    #[test]
212    fn nulless_byte_str_equivalence() {
213        let unterminated: NullessByteStr<'_> = b"u:object_r:test_valid_t:s0".into();
214        let nul_terminated: NullessByteStr<'_> = b"u:object_r:test_valid_t:s0\0".into();
215        let nul_containing: NullessByteStr<'_> =
216            b"u:object_r:test_valid_t:s0\0IGNORE THIS\0!\0".into();
217
218        for context in [nul_terminated, nul_containing] {
219            assert_eq!(unterminated, context);
220            assert_eq!(unterminated.as_bytes(), context.as_bytes());
221        }
222    }
223}