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, 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
46const NO_PERMISSIONS: &[KernelPermission] = &[];
49
50type PermissionFlagsVec = smallvec::SmallVec<[KernelPermission; 3]>;
52
53fn 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 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
86fn 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 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 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 has_file_permissions(
141 permission_check,
142 current_task,
143 subject_sid,
144 file,
145 NO_PERMISSIONS,
146 audit_context,
147 )?;
148
149 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_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
221fn 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
251fn 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
310fn 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 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
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 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 = 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
406pub(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
430pub(super) struct KernelState {
432 pub(super) server: Arc<SecurityServer>,
434
435 pub(super) pending_file_systems: Mutex<IndexSet<WeakKey<FileSystem>>>,
440
441 pub(super) has_policy: AtomicBool,
443
444 pub(super) selinuxfs_null: OnceLock<FileHandle>,
447
448 pub(super) access_denial_count: AtomicU64,
450
451 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#[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
475pub(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
481pub(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(¤t_task.security_state.state.local_cache)
487}
488
489#[derive(Debug)]
492pub(super) struct FileObjectState {
493 sid: SecurityId,
494}
495
496#[derive(Debug)]
499pub(super) struct FileSystemState {
500 mount_options: FileSystemMountOptions,
502 pending_entries: Mutex<IndexSet<WeakKey<DirEntry>>>,
503
504 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 pub fn label(&self) -> Option<&FileSystemLabel> {
518 self.label.get()
519 }
520
521 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 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#[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#[derive(Debug, Clone)]
602pub(super) enum FsNodeLabel {
603 Uninitialized,
604 SecurityId { sid: SecurityId },
605 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#[derive(Debug, PartialEq)]
639pub(super) struct FsNodeSidAndClass {
640 pub sid: SecurityId,
641 pub class: FsNodeClass,
642}
643
644#[derive(Clone, Debug, PartialEq)]
647pub(super) struct BinderConnectionState {
648 sid: SecurityId,
649}
650
651#[derive(Debug, Default)]
654pub(super) struct SocketState {
655 peer_sid: Mutex<Option<SecurityId>>,
656}
657
658#[derive(Clone, Debug, PartialEq)]
661pub(super) struct BpfMapState {
662 sid: SecurityId,
663}
664
665#[derive(Clone, Debug, PartialEq)]
668pub(super) struct BpfProgState {
669 sid: SecurityId,
670}
671
672pub(super) fn set_cached_sid(fs_node: &FsNode, sid: SecurityId) {
676 fs_node.security_state.0.update_label(FsNodeLabel::SecurityId { sid });
677}
678
679fn 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
689fn 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)]
712pub(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#[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 => {
756 PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
757 }
758 }
759}
760
761#[derive(Default, Debug)]
763pub struct CurrentTaskState {
764 pub local_cache: selinux::permission_check::PerThreadCache,
765}