1#![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::{
22 NonPrivateDirEntry, NonPrivateFsNode, NonPrivateSocket, NonPrivateSocketFile, PermissionFlags,
23};
24use crate::task::{CurrentTask, TaskPersistentInfo};
25#[cfg(test)]
26use crate::vfs::FsNode;
27use crate::vfs::{DirEntry, FileHandle, FileObject, FileSystem, FileSystemOps};
28use audit::{Auditable, audit_decision};
29use fuchsia_rcu::{RcuBox, RcuDroppable, RcuReadGuard};
30use indexmap::IndexSet;
31use selinux::permission_check::PermissionCheck;
32use selinux::policy::{FsUseType, XpermsKind};
33use selinux::{
34 ClassPermission, CommonFilePermission, CommonFsNodePermission, DirPermission, FdPermission,
35 FileClass, FileSystemLabel, FileSystemLabelingScheme, FileSystemMountOptions, ForClass,
36 FsNodeClass, InitialSid, KernelPermission, PolicyCap, ProcessPermission, SecurityId,
37 SecurityServer, TaskAttrs,
38};
39use smallvec;
40use starnix_logging::{BugRef, CATEGORY_STARNIX_SECURITY, bug_ref, track_stub};
41use starnix_sync::{
42 LockDepMutex, SeLinuxPeerSidLock, SeLinuxPendingEntriesLock, SeLinuxPendingFileSystemsLock,
43 SeLinuxUpdateLock,
44};
45use starnix_uapi::arc_key::WeakKey;
46use starnix_uapi::errors::Errno;
47use starnix_uapi::file_mode::FileMode;
48use starnix_uapi::{errno, error};
49use std::cell::Ref;
50use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
51use std::sync::{Arc, OnceLock};
52
53const NO_PERMISSIONS: &[KernelPermission] = &[];
56
57type PermissionFlagsVec = smallvec::SmallVec<[KernelPermission; 3]>;
59
60fn permissions_from_flags(flags: PermissionFlags, class: FsNodeClass) -> PermissionFlagsVec {
62 let mut result = PermissionFlagsVec::new();
63
64 if flags.contains(PermissionFlags::READ) {
65 result.push(CommonFsNodePermission::Read.for_class(class));
66 }
67 if flags.contains(PermissionFlags::WRITE) {
68 if flags.contains(PermissionFlags::APPEND) {
71 result.push(CommonFsNodePermission::Append.for_class(class));
72 } else {
73 result.push(CommonFsNodePermission::Write.for_class(class));
74 }
75 }
76
77 if let FsNodeClass::File(class) = class {
78 if flags.contains(PermissionFlags::EXEC) {
79 if class == FileClass::Dir {
80 result.push(DirPermission::Search.into());
81 } else {
82 result.push(CommonFilePermission::Execute.for_class(class));
83 }
84 }
85 }
86 result
87}
88
89fn is_internal_operation(current_task: &CurrentTask) -> bool {
90 current_task_state(current_task).internal_operation
91}
92
93fn has_file_permissions(
96 permission_check: &PermissionCheck<'_>,
97 current_task: &CurrentTask,
98 subject_sid: SecurityId,
99 file: &FileObject,
100 permissions: &[impl ForClass<FsNodeClass>],
101 audit_context: Auditable<'_>,
102) -> Result<(), Errno> {
103 if is_internal_operation(current_task) {
104 return Ok(());
105 };
106 let file_sid = file.security_state.state.sid;
109 if subject_sid != file_sid {
110 let node = file.node().as_ref().as_ref();
111 let audit_context = [audit_context, file.into(), node.into()];
112 check_permission(
113 permission_check,
114 current_task,
115 subject_sid,
116 file_sid,
117 FdPermission::Use,
118 (&audit_context).into(),
119 )?;
120 }
121
122 if !permissions.is_empty()
124 && let Some(fs_node) = NonPrivateFsNode::new(file.node())
125 {
126 let audit_context = [audit_context, file.into()];
127 has_fs_node_permissions(
128 permission_check,
129 current_task,
130 subject_sid,
131 fs_node,
132 permissions,
133 (&audit_context).into(),
134 )?;
135 }
136
137 Ok(())
138}
139
140fn has_file_ioctl_permission(
141 permission_check: &PermissionCheck<'_>,
142 current_task: &CurrentTask,
143 subject_sid: SecurityId,
144 file: &FileObject,
145 ioctl: u16,
146 audit_context: Auditable<'_>,
147) -> Result<(), Errno> {
148 has_file_permissions(
150 permission_check,
151 current_task,
152 subject_sid,
153 file,
154 NO_PERMISSIONS,
155 audit_context,
156 )?;
157
158 let Some(fs_node) = NonPrivateFsNode::new(file.node()) else {
161 return Ok(());
162 };
163 let FsNodeSidAndClass { sid: target_sid, class: target_class } =
164 fs_node_effective_sid_and_class(fs_node);
165
166 let audit_context =
167 &[audit_context, file.into(), fs_node.into(), Auditable::IoctlCommand(ioctl)];
168
169 check_permission_and_xperms(
171 permission_check,
172 current_task,
173 subject_sid,
174 target_sid,
175 CommonFsNodePermission::Ioctl.for_class(target_class),
176 XpermsKind::Ioctl,
177 ioctl,
178 audit_context.into(),
179 )
180}
181
182fn check_permission_and_xperms(
183 permission_check: &PermissionCheck<'_>,
184 current_task: &CurrentTask,
185 subject_sid: SecurityId,
186 target_sid: SecurityId,
187 permission: KernelPermission,
188 xperms_kind: XpermsKind,
189 xperm: u16,
190 audit_context: Auditable<'_>,
191) -> Result<(), Errno> {
192 if is_internal_operation(current_task) {
193 return Ok(());
194 }
195 let result = permission_check.has_extended_permission(
196 xperms_kind,
197 subject_sid,
198 target_sid,
199 permission.clone(),
200 xperm,
201 );
202
203 if result.audit {
204 if !result.permit() {
205 current_task
206 .kernel()
207 .security_state
208 .state
209 .as_ref()
210 .unwrap()
211 .access_denial_count
212 .fetch_add(1, Ordering::Release);
213 }
214
215 audit_decision(
216 current_task,
217 permission_check,
218 result.clone(),
219 subject_sid,
220 target_sid,
221 permission.into(),
222 audit_context.into(),
223 );
224 }
225
226 result.permit().then_some(()).ok_or_else(|| errno!(EACCES))
227}
228
229fn has_fs_node_permissions_dontaudit(
231 permission_check: &PermissionCheck<'_>,
232 _current_task: &CurrentTask,
233 subject_sid: SecurityId,
234 fs_node: NonPrivateFsNode<'_>,
235 permissions: &[impl ForClass<FsNodeClass>],
236) -> Result<(), Errno> {
237 fuchsia_trace::duration!(
238 CATEGORY_STARNIX_SECURITY,
239 "security.selinux.has_fs_node_permissions_dontaudit"
240 );
241
242 let target = fs_node_effective_sid_and_class(fs_node);
243 for permission in permissions {
244 if !permission_check
245 .has_permission(subject_sid, target.sid, permission.for_class(target.class))
246 .permit()
247 {
248 return error!(EACCES);
249 }
250 }
251
252 Ok(())
253}
254
255fn has_fs_node_permissions(
257 permission_check: &PermissionCheck<'_>,
258 current_task: &CurrentTask,
259 subject_sid: SecurityId,
260 fs_node: NonPrivateFsNode<'_>,
261 permissions: &[impl ForClass<FsNodeClass>],
262 audit_context: Auditable<'_>,
263) -> Result<(), Errno> {
264 fuchsia_trace::duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.has_fs_node_permissions");
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
310fn fs_node_effective_sid_and_class(fs_node: NonPrivateFsNode<'_>) -> FsNodeSidAndClass {
313 let label_class = fs_node.selinux_state().read();
314 if matches!(label_class.label, FsNodeLabel::Uninitialized) {
315 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/381210515"), "SID requested for unlabeled FsNode");
326 }
327 }
328 FsNodeSidAndClass { sid: label_class.sid(), class: label_class.class() }
329}
330
331fn 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 fuchsia_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
373fn 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: u64 = security_server.binary_policy_size().unwrap_or(0).try_into()?;
397 inspector.root().record_uint("policy_bytes", policy_bytes);
398
399 Ok(inspector)
400}
401
402pub(super) fn kernel_init_security(
404 options: String,
405 exceptions: Vec<String>,
406 inspect_node: &fuchsia_inspect::Node,
407) -> KernelState {
408 let server = SecurityServer::new(options, exceptions);
409 let inspect_node = inspect_node.create_child("selinux");
410
411 let server_for_inspect = server.clone();
412 inspect_node.record_lazy_values("server", move || {
413 Box::pin(create_inspect_values(server_for_inspect.clone()))
414 });
415
416 KernelState {
417 server,
418 pending_file_systems: LockDepMutex::default(),
419 selinuxfs_null: OnceLock::default(),
420 access_denial_count: AtomicU64::new(0u64),
421 has_policy: false.into(),
422 _inspect_node: inspect_node,
423 }
424}
425
426pub(super) struct KernelState {
428 pub(super) server: Arc<SecurityServer>,
430
431 pub(super) pending_file_systems:
436 LockDepMutex<IndexSet<WeakKey<FileSystem>>, SeLinuxPendingFileSystemsLock>,
437
438 pub(super) has_policy: AtomicBool,
440
441 pub(super) selinuxfs_null: OnceLock<FileHandle>,
444
445 pub(super) access_denial_count: AtomicU64,
447
448 pub(super) _inspect_node: fuchsia_inspect::Node,
450}
451
452impl KernelState {
453 pub(super) fn access_denial_count(&self) -> u64 {
454 self.access_denial_count.load(Ordering::Acquire)
455 }
456
457 pub(super) fn has_policy(&self) -> bool {
458 self.has_policy.load(Ordering::Acquire)
459 }
460}
461
462#[derive(Clone, Debug, PartialEq)]
464pub(super) struct PerfEventState {
465 sid: SecurityId,
466}
467
468pub(in crate::security) fn current_task_state(current_task: &CurrentTask) -> Ref<'_, TaskAttrs> {
469 Ref::map(current_task.current_creds(), |creds| &creds.security_state)
470}
471
472pub(in crate::security) fn task_consistent_attrs(current_task: &CurrentTask) -> Ref<'_, TaskAttrs> {
474 assert!(!current_task.has_overridden_creds());
475 current_task_state(current_task)
476}
477
478pub(in crate::security) fn build_permission_check<'a>(
480 current_task: &'a CurrentTask,
481 security_server: &'a SecurityServer,
482) -> PermissionCheck<'a> {
483 security_server.as_permission_check(¤t_task.security_state.state.local_cache)
484}
485
486#[derive(Debug)]
488pub(super) struct FileObjectState {
489 sid: SecurityId,
490 open_state: OnceLock<FileObjectOpenState>,
491}
492
493#[derive(Debug, Clone, PartialEq, Eq)]
494pub(super) struct FileObjectOpenState {
495 node_sid: SecurityId,
497 policy_seqno: selinux::PolicySeqNo,
499}
500
501#[derive(Debug)]
504pub(super) struct FileSystemState {
505 mount_options: FileSystemMountOptions,
507 pending_entries: LockDepMutex<IndexSet<WeakKey<DirEntry>>, SeLinuxPendingEntriesLock>,
508
509 label: OnceLock<FileSystemLabel>,
511}
512
513impl FileSystemState {
514 fn new(mount_options: FileSystemMountOptions, _ops: &dyn FileSystemOps) -> Self {
515 let pending_entries = IndexSet::new().into();
516 let label = OnceLock::new();
517
518 Self { mount_options, pending_entries, label }
519 }
520
521 pub fn label(&self) -> Option<&FileSystemLabel> {
523 self.label.get()
524 }
525
526 pub fn supports_relabel(&self) -> bool {
528 let Some(label) = self.label() else {
529 return false;
530 };
531 match label.scheme {
532 FileSystemLabelingScheme::Mountpoint { .. } => false,
533 FileSystemLabelingScheme::FsUse { .. } => true,
534 FileSystemLabelingScheme::GenFsCon { supports_seclabel } => supports_seclabel,
535 }
536 }
537
538 pub fn supports_xattr(&self) -> bool {
540 let Some(label) = self.label() else {
541 return false;
542 };
543 match label.scheme {
544 FileSystemLabelingScheme::Mountpoint { .. }
545 | FileSystemLabelingScheme::GenFsCon { .. } => false,
546 FileSystemLabelingScheme::FsUse { fs_use_type, .. } => fs_use_type == FsUseType::Xattr,
547 }
548 }
549}
550
551#[derive(Debug)]
553pub(super) struct FsNodeState {
554 label: RcuBox<FsNodeLabelAndClass>,
555 update_lock: LockDepMutex<(), SeLinuxUpdateLock>,
556}
557
558impl Default for FsNodeState {
559 fn default() -> Self {
560 Self {
561 label: RcuBox::new(FsNodeLabelAndClass {
562 label: FsNodeLabel::Uninitialized,
563 class: None,
564 }),
565 update_lock: Default::default(),
566 }
567 }
568}
569
570impl FsNodeState {
571 pub(super) fn read(&self) -> RcuReadGuard<FsNodeLabelAndClass> {
572 self.label.read()
573 }
574
575 pub(super) fn update(&self, new_label: FsNodeLabel, new_class: FsNodeClass) {
576 let _lock = self.update_lock.lock();
577 let mut new_label_class = (*self.label.read()).clone();
578 new_label_class.label = new_label;
579 new_label_class.class = Some(new_class);
580 self.label.update(new_label_class);
581 }
582
583 pub(super) fn update_label(&self, new_label: FsNodeLabel) {
584 let _lock = self.update_lock.lock();
585 let mut new_label_class = (*self.label.read()).clone();
586 new_label_class.label = new_label;
587 self.label.update(new_label_class);
588 }
589
590 pub(super) fn update_class(&self, new_class: FsNodeClass) {
591 let _lock = self.update_lock.lock();
592 let mut new_label_class = (*self.label.read()).clone();
593 new_label_class.class = Some(new_class);
594 self.label.update(new_label_class);
595 }
596
597 #[cfg(test)]
598 pub(super) fn clear_label_for_test(&self) {
599 let _lock = self.update_lock.lock();
600 let class = self.label.read().class;
601 self.label.update(FsNodeLabelAndClass { label: FsNodeLabel::Uninitialized, class });
602 }
603}
604
605#[derive(Debug, Clone, RcuDroppable)]
607pub(super) enum FsNodeLabel {
608 Uninitialized,
609 SecurityId { sid: SecurityId },
610 FromTask { task_state: TaskPersistentInfo },
612}
613
614impl FsNodeLabel {
615 pub fn sid(&self) -> SecurityId {
616 match self {
617 FsNodeLabel::Uninitialized => InitialSid::Unlabeled.into(),
618 FsNodeLabel::SecurityId { sid } => *sid,
619 FsNodeLabel::FromTask { task_state } => {
620 task_state.real_creds().security_state.current_sid
621 }
622 }
623 }
624}
625
626#[derive(Debug, Clone, RcuDroppable)]
627pub(super) struct FsNodeLabelAndClass {
628 pub label: FsNodeLabel,
629 pub class: Option<FsNodeClass>,
630}
631
632impl FsNodeLabelAndClass {
633 pub fn class(&self) -> FsNodeClass {
634 self.class.unwrap_or(FsNodeClass::File(FileClass::File))
635 }
636
637 pub fn sid(&self) -> SecurityId {
638 self.label.sid()
639 }
640}
641
642#[derive(Debug, PartialEq)]
644pub(super) struct FsNodeSidAndClass {
645 pub sid: SecurityId,
646 pub class: FsNodeClass,
647}
648
649#[derive(Debug, Default)]
652pub(super) struct SocketState {
653 peer_sid: LockDepMutex<Option<SecurityId>, SeLinuxPeerSidLock>,
654}
655
656#[derive(Clone, Debug, PartialEq)]
659pub(super) struct BpfMapState {
660 sid: SecurityId,
661}
662
663#[derive(Clone, Debug, PartialEq)]
666pub(super) struct BpfProgState {
667 sid: SecurityId,
668}
669
670pub(super) fn set_cached_sid(fs_node: NonPrivateFsNode<'_>, sid: SecurityId) {
674 fs_node.selinux_state().update_label(FsNodeLabel::SecurityId { sid });
675}
676
677fn fs_node_set_label_with_task(
681 fs_node: NonPrivateFsNode<'_>,
682 task_persistent_info: &TaskPersistentInfo,
683) {
684 fs_node
685 .selinux_state()
686 .update_label(FsNodeLabel::FromTask { task_state: task_persistent_info.clone() });
687}
688
689fn fs_node_ensure_class(fs_node: NonPrivateFsNode<'_>) -> Result<FsNodeClass, Errno> {
693 let label_class = fs_node.selinux_state().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.selinux_state().update_lock.lock();
700 let label_class = fs_node.selinux_state().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.selinux_state().label.update(new_label_class);
708 Ok(class)
709}
710
711#[cfg(test)]
712pub(super) fn get_cached_sid(fs_node: &FsNode) -> Option<SecurityId> {
714 let label_class = fs_node.security_state.selinux_for_test().read();
715 if !matches!(label_class.label, FsNodeLabel::Uninitialized) {
716 Some(label_class.sid())
717 } else {
718 None
719 }
720}
721
722#[derive(Debug)]
725pub enum PolicyCapSupport {
726 AlwaysOn(BugRef),
727 AlwaysOff(BugRef),
728 Configurable,
729 NotImplemented,
730}
731
732fn 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 => PolicyCapSupport::Configurable,
756 }
757}
758
759#[derive(Default, Debug)]
761pub struct CurrentTaskState {
762 pub local_cache: selinux::permission_check::PerThreadCache,
763}