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::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
49const NO_PERMISSIONS: &[KernelPermission] = &[];
52
53type PermissionFlagsVec = smallvec::SmallVec<[KernelPermission; 3]>;
55
56fn 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 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
89fn 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 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 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 has_file_permissions(
144 permission_check,
145 current_task,
146 subject_sid,
147 file,
148 NO_PERMISSIONS,
149 audit_context,
150 )?;
151
152 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_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
224fn 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
254fn 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
313fn 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 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
334fn 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
376fn 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
409pub(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
433pub(super) struct KernelState {
435 pub(super) server: Arc<SecurityServer>,
437
438 pub(super) pending_file_systems:
443 LockDepMutex<IndexSet<WeakKey<FileSystem>>, SeLinuxPendingFileSystemsLock>,
444
445 pub(super) has_policy: AtomicBool,
447
448 pub(super) selinuxfs_null: OnceLock<FileHandle>,
451
452 pub(super) access_denial_count: AtomicU64,
454
455 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#[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
479pub(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
485pub(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(¤t_task.security_state.state.local_cache)
491}
492
493#[derive(Debug)]
496pub(super) struct FileObjectState {
497 sid: SecurityId,
498}
499
500#[derive(Debug)]
503pub(super) struct FileSystemState {
504 mount_options: FileSystemMountOptions,
506 pending_entries: LockDepMutex<IndexSet<WeakKey<DirEntry>>, SeLinuxPendingEntriesLock>,
507
508 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 pub fn label(&self) -> Option<&FileSystemLabel> {
522 self.label.get()
523 }
524
525 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 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#[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#[derive(Debug, Clone)]
606pub(super) enum FsNodeLabel {
607 Uninitialized,
608 SecurityId { sid: SecurityId },
609 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#[derive(Debug, PartialEq)]
643pub(super) struct FsNodeSidAndClass {
644 pub sid: SecurityId,
645 pub class: FsNodeClass,
646}
647
648#[derive(Clone, Debug, PartialEq)]
651pub(super) struct BinderConnectionState {
652 sid: SecurityId,
653}
654
655#[derive(Debug, Default)]
658pub(super) struct SocketState {
659 peer_sid: LockDepMutex<Option<SecurityId>, SeLinuxPeerSidLock>,
660}
661
662#[derive(Clone, Debug, PartialEq)]
665pub(super) struct BpfMapState {
666 sid: SecurityId,
667}
668
669#[derive(Clone, Debug, PartialEq)]
672pub(super) struct BpfProgState {
673 sid: SecurityId,
674}
675
676pub(super) fn set_cached_sid(fs_node: &FsNode, sid: SecurityId) {
680 fs_node.security_state.0.update_label(FsNodeLabel::SecurityId { sid });
681}
682
683fn 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
693fn 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)]
716pub(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#[derive(Debug)]
729pub enum PolicyCapSupport {
730 AlwaysOn(BugRef),
731 AlwaysOff(BugRef),
732 Configurable,
733 NotImplemented,
734}
735
736fn 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#[derive(Default, Debug)]
765pub struct CurrentTaskState {
766 pub local_cache: selinux::permission_check::PerThreadCache,
767}