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::{
24 Anon, DirEntry, FileHandle, FileObject, FileSystem, FileSystemOps, FsNode, OutputBuffer,
25};
26use audit::{Auditable, audit_decision, audit_todo_decision};
27use indexmap::IndexSet;
28use selinux::permission_check::PermissionCheck;
29use selinux::policy::{FsUseType, XpermsKind};
30use selinux::{
31 ClassPermission, CommonFilePermission, CommonFsNodePermission, DirPermission, FdPermission,
32 FileClass, FileSystemLabel, FileSystemLabelingScheme, FileSystemMountOptions, ForClass,
33 FsNodeClass, InitialSid, KernelPermission, PolicyCap, ProcessPermission, SecurityId,
34 SecurityServer, TaskAttrs,
35};
36use smallvec;
37use starnix_logging::{BugRef, CATEGORY_STARNIX_SECURITY, bug_ref, trace_duration, track_stub};
38use starnix_sync::Mutex;
39use starnix_uapi::arc_key::WeakKey;
40use starnix_uapi::errors::Errno;
41use starnix_uapi::file_mode::FileMode;
42use starnix_uapi::{errno, error};
43use std::cell::Ref;
44use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
45use std::sync::{Arc, OnceLock};
46
47const NO_PERMISSIONS: &[KernelPermission] = &[];
50
51type PermissionFlagsVec = smallvec::SmallVec<[KernelPermission; 3]>;
53
54fn permissions_from_flags(flags: PermissionFlags, class: FsNodeClass) -> PermissionFlagsVec {
56 let mut result = PermissionFlagsVec::new();
57
58 if flags.contains(PermissionFlags::READ) {
59 result.push(CommonFsNodePermission::Read.for_class(class));
60 }
61 if flags.contains(PermissionFlags::WRITE) {
62 if flags.contains(PermissionFlags::APPEND) {
65 result.push(CommonFsNodePermission::Append.for_class(class));
66 } else {
67 result.push(CommonFsNodePermission::Write.for_class(class));
68 }
69 }
70
71 if let FsNodeClass::File(class) = class {
72 if flags.contains(PermissionFlags::EXEC) {
73 if class == FileClass::Dir {
74 result.push(DirPermission::Search.into());
75 } else {
76 result.push(CommonFilePermission::Execute.for_class(class));
77 }
78 }
79 }
80 result
81}
82
83fn is_internal_operation(current_task: &CurrentTask) -> bool {
84 current_task_state(current_task).internal_operation
85}
86
87fn has_file_permissions(
90 permission_check: &PermissionCheck<'_>,
91 current_task: &CurrentTask,
92 subject_sid: SecurityId,
93 file: &FileObject,
94 permissions: &[impl ForClass<FsNodeClass>],
95 audit_context: Auditable<'_>,
96) -> Result<(), Errno> {
97 if is_internal_operation(current_task) {
98 return Ok(());
99 };
100 let file_sid = file.security_state.state.sid;
103 if subject_sid != file_sid {
104 let node = file.node().as_ref().as_ref();
105 let audit_context = [audit_context, file.into(), node.into()];
106 check_permission(
107 permission_check,
108 current_task,
109 subject_sid,
110 file_sid,
111 FdPermission::Use,
112 (&audit_context).into(),
113 )?;
114 }
115
116 if !permissions.is_empty() {
118 let audit_context = [audit_context, file.into()];
119 has_fs_node_permissions(
120 permission_check,
121 current_task,
122 subject_sid,
123 file.node(),
124 permissions,
125 (&audit_context).into(),
126 )?;
127 }
128
129 Ok(())
130}
131
132fn has_file_ioctl_permission(
133 permission_check: &PermissionCheck<'_>,
134 current_task: &CurrentTask,
135 subject_sid: SecurityId,
136 file: &FileObject,
137 ioctl: u16,
138 audit_context: Auditable<'_>,
139) -> Result<(), Errno> {
140 has_file_permissions(
142 permission_check,
143 current_task,
144 subject_sid,
145 file,
146 NO_PERMISSIONS,
147 audit_context,
148 )?;
149
150 let fs_node = file.node().as_ref().as_ref();
153 if Anon::is_private(fs_node) {
154 return Ok(());
155 }
156 let FsNodeSidAndClass { sid: target_sid, class: target_class } =
157 fs_node_effective_sid_and_class(fs_node);
158
159 let audit_context =
160 &[audit_context, file.into(), fs_node.into(), Auditable::IoctlCommand(ioctl)];
161
162 check_permission_and_xperms(
164 permission_check,
165 current_task,
166 subject_sid,
167 target_sid,
168 CommonFsNodePermission::Ioctl.for_class(target_class),
169 XpermsKind::Ioctl,
170 ioctl,
171 audit_context.into(),
172 )
173}
174
175fn check_permission_and_xperms(
176 permission_check: &PermissionCheck<'_>,
177 current_task: &CurrentTask,
178 subject_sid: SecurityId,
179 target_sid: SecurityId,
180 permission: KernelPermission,
181 xperms_kind: XpermsKind,
182 xperm: u16,
183 audit_context: Auditable<'_>,
184) -> Result<(), Errno> {
185 if is_internal_operation(current_task) {
186 return Ok(());
187 }
188 let result = permission_check.has_extended_permission(
189 xperms_kind,
190 subject_sid,
191 target_sid,
192 permission.clone(),
193 xperm,
194 );
195
196 if result.audit {
197 if !result.permit {
198 current_task
199 .kernel()
200 .security_state
201 .state
202 .as_ref()
203 .unwrap()
204 .access_denial_count
205 .fetch_add(1, Ordering::Release);
206 }
207
208 audit_decision(
209 current_task,
210 permission_check,
211 result.clone(),
212 subject_sid,
213 target_sid,
214 permission.into(),
215 audit_context.into(),
216 );
217 }
218
219 result.permit.then_some(()).ok_or_else(|| errno!(EACCES))
220}
221
222fn has_fs_node_permissions_dontaudit(
224 permission_check: &PermissionCheck<'_>,
225 _current_task: &CurrentTask,
226 subject_sid: SecurityId,
227 fs_node: &FsNode,
228 permissions: &[impl ForClass<FsNodeClass>],
229) -> Result<(), Errno> {
230 trace_duration!(
231 CATEGORY_STARNIX_SECURITY,
232 "security.selinux.has_fs_node_permissions_dontaudit"
233 );
234
235 if Anon::is_private(fs_node) {
236 return Ok(());
237 }
238
239 let target = fs_node_effective_sid_and_class(fs_node);
240 for permission in permissions {
241 if !permission_check
242 .has_permission(subject_sid, target.sid, permission.for_class(target.class))
243 .permit
244 {
245 return error!(EACCES);
246 }
247 }
248
249 Ok(())
250}
251
252fn has_fs_node_permissions(
254 permission_check: &PermissionCheck<'_>,
255 current_task: &CurrentTask,
256 subject_sid: SecurityId,
257 fs_node: &FsNode,
258 permissions: &[impl ForClass<FsNodeClass>],
259 audit_context: Auditable<'_>,
260) -> Result<(), Errno> {
261 trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.has_fs_node_permissions");
262
263 if Anon::is_private(fs_node) {
264 return Ok(());
265 }
266
267 let target = fs_node_effective_sid_and_class(fs_node);
268
269 let fs = fs_node.fs();
270 let audit_context = [audit_context, fs_node.into(), fs.as_ref().into()];
271 for permission in permissions {
272 check_permission(
273 permission_check,
274 current_task,
275 subject_sid,
276 target.sid,
277 permission.for_class(target.class),
278 (&audit_context).into(),
279 )?;
280 }
281
282 Ok(())
283}
284
285#[allow(dead_code)]
287fn todo_has_fs_node_permissions(
288 bug: BugRef,
289 permission_check: &PermissionCheck<'_>,
290 current_task: &CurrentTask,
291 subject_sid: SecurityId,
292 fs_node: &FsNode,
293 permissions: &[impl ForClass<FsNodeClass>],
294 audit_context: Auditable<'_>,
295) -> Result<(), Errno> {
296 trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.todo_has_fs_node_permissions");
297
298 if Anon::is_private(fs_node) {
299 return Ok(());
300 }
301
302 let target = fs_node_effective_sid_and_class(fs_node);
303
304 let fs = fs_node.fs();
305 let audit_context = [audit_context, fs_node.into(), fs.as_ref().into()];
306 for permission in permissions {
307 todo_check_permission(
308 bug.clone(),
309 permission_check,
310 current_task,
311 subject_sid,
312 target.sid,
313 permission.for_class(target.class),
314 (&audit_context).into(),
315 )?;
316 }
317
318 Ok(())
319}
320
321fn file_class_from_file_mode(mode: FileMode) -> Result<FileClass, Errno> {
322 let file_type = mode.bits() & starnix_uapi::S_IFMT;
323 match file_type {
324 starnix_uapi::S_IFLNK => Ok(FileClass::Link),
325 starnix_uapi::S_IFREG => Ok(FileClass::File),
326 starnix_uapi::S_IFDIR => Ok(FileClass::Dir),
327 starnix_uapi::S_IFCHR => Ok(FileClass::Character),
328 starnix_uapi::S_IFBLK => Ok(FileClass::Block),
329 starnix_uapi::S_IFIFO => Ok(FileClass::Fifo),
330 starnix_uapi::S_IFSOCK => Ok(FileClass::SockFile),
331 0 => {
332 track_stub!(TODO("https://fxbug.dev/378864191"), "File with zero IFMT?");
333 Ok(FileClass::File)
334 }
335 _ => error!(EINVAL, format!("mode: {:?}", mode)),
336 }
337}
338
339#[macro_export]
340macro_rules! TODO_DENY {
341 ($bug_url:literal, $message:literal) => {{
342 use starnix_logging::bug_ref;
343 bug_ref!($bug_url)
344 }};
345}
346
347fn fs_node_effective_sid_and_class(fs_node: &FsNode) -> FsNodeSidAndClass {
349 let state = fs_node.security_state.lock().clone();
350 let sid = match state.label {
351 FsNodeLabel::SecurityId { sid } => sid,
352 FsNodeLabel::FromTask { task_state } => task_state.real_creds().security_state.current_sid,
353 FsNodeLabel::Uninitialized => {
354 if cfg!(any(test, debug_assertions)) {
356 panic!(
357 "Unlabeled FsNode@{} of class {:?} in {} (label {:?})",
358 fs_node.ino,
359 file_class_from_file_mode(fs_node.info().mode),
360 fs_node.fs().name(),
361 fs_node.fs().security_state.state.label(),
362 );
363 } else {
364 track_stub!(
365 TODO("https://fxbug.dev/381210513"),
366 "SID requested for unlabeled FsNode"
367 );
368 InitialSid::Unlabeled.into()
369 }
370 }
371 };
372 FsNodeSidAndClass { sid, class: state.class }
373}
374
375fn todo_check_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
378 bug: BugRef,
379 permission_check: &PermissionCheck<'_>,
380 current_task: &CurrentTask,
381 source_sid: SecurityId,
382 target_sid: SecurityId,
383 permission: P,
384 audit_context: Auditable<'_>,
385) -> Result<(), Errno> {
386 if is_internal_operation(current_task) {
387 return Ok(());
388 }
389 let kernel = current_task.kernel();
390 if kernel.features.selinux_test_suite {
391 check_permission(
392 permission_check,
393 current_task,
394 source_sid,
395 target_sid,
396 permission,
397 audit_context,
398 )
399 } else {
400 trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.todo_check_permission");
401
402 let result = permission_check.has_permission(source_sid, target_sid, permission.clone());
403
404 if result.audit {
405 audit_todo_decision(
406 current_task,
407 bug,
408 permission_check,
409 result,
410 source_sid,
411 target_sid,
412 permission.into(),
413 audit_context,
414 );
415 }
416
417 Ok(())
418 }
419}
420
421fn check_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
423 permission_check: &PermissionCheck<'_>,
424 current_task: &CurrentTask,
425 source_sid: SecurityId,
426 target_sid: SecurityId,
427 permission: P,
428 audit_context: Auditable<'_>,
429) -> Result<(), Errno> {
430 trace_duration!(CATEGORY_STARNIX_SECURITY, "security.selinux.check_permission");
431
432 if is_internal_operation(current_task) {
433 return Ok(());
434 }
435 let result = permission_check.has_permission(source_sid, target_sid, permission.clone());
436
437 if result.audit {
438 if !result.permit {
439 current_task
440 .kernel()
441 .security_state
442 .state
443 .as_ref()
444 .unwrap()
445 .access_denial_count
446 .fetch_add(1, Ordering::Release);
447 }
448
449 audit_decision(
450 current_task,
451 permission_check,
452 result.clone(),
453 source_sid,
454 target_sid,
455 permission.into(),
456 audit_context,
457 );
458 };
459
460 result.permit.then_some(()).ok_or_else(|| errno!(EACCES))
461}
462
463fn check_self_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
465 permission_check: &PermissionCheck<'_>,
466 current_task: &CurrentTask,
467 subject_sid: SecurityId,
468 permission: P,
469 audit_context: Auditable<'_>,
470) -> Result<(), Errno> {
471 check_permission(
472 permission_check,
473 current_task,
474 subject_sid,
475 subject_sid,
476 permission,
477 audit_context,
478 )
479}
480
481async fn create_inspect_values(
482 security_server: Arc<SecurityServer>,
483) -> Result<fuchsia_inspect::Inspector, anyhow::Error> {
484 let inspector = fuchsia_inspect::Inspector::default();
485
486 let policy_bytes = if let Some(policy_data) = security_server.get_binary_policy() {
487 policy_data.len().try_into()?
488 } else {
489 0
490 };
491 inspector.root().record_uint("policy_bytes", policy_bytes);
492
493 Ok(inspector)
494}
495
496pub(super) fn kernel_init_security(
498 options: String,
499 exceptions: Vec<String>,
500 inspect_node: &fuchsia_inspect::Node,
501) -> KernelState {
502 let server = SecurityServer::new(options, exceptions);
503 let inspect_node = inspect_node.create_child("selinux");
504
505 let server_for_inspect = server.clone();
506 inspect_node.record_lazy_values("server", move || {
507 Box::pin(create_inspect_values(server_for_inspect.clone()))
508 });
509
510 KernelState {
511 server,
512 pending_file_systems: Mutex::default(),
513 selinuxfs_null: OnceLock::default(),
514 access_denial_count: AtomicU64::new(0u64),
515 has_policy: false.into(),
516 _inspect_node: inspect_node,
517 }
518}
519
520pub(super) struct KernelState {
522 pub(super) server: Arc<SecurityServer>,
524
525 pub(super) pending_file_systems: Mutex<IndexSet<WeakKey<FileSystem>>>,
530
531 pub(super) has_policy: AtomicBool,
533
534 pub(super) selinuxfs_null: OnceLock<FileHandle>,
537
538 pub(super) access_denial_count: AtomicU64,
540
541 pub(super) _inspect_node: fuchsia_inspect::Node,
543}
544
545impl KernelState {
546 pub(super) fn access_denial_count(&self) -> u64 {
547 self.access_denial_count.load(Ordering::Acquire)
548 }
549
550 pub(super) fn has_policy(&self) -> bool {
551 self.has_policy.load(Ordering::Acquire)
552 }
553}
554
555#[derive(Clone, Debug, PartialEq)]
557pub(super) struct PerfEventState {
558 sid: SecurityId,
559}
560
561pub(in crate::security) fn current_task_state(current_task: &CurrentTask) -> Ref<'_, TaskAttrs> {
562 Ref::map(current_task.current_creds(), |creds| &creds.security_state)
563}
564
565pub(in crate::security) fn task_consistent_attrs(current_task: &CurrentTask) -> Ref<'_, TaskAttrs> {
567 assert!(!current_task.has_overridden_creds());
568 current_task_state(current_task)
569}
570
571#[derive(Debug)]
574pub(super) struct FileObjectState {
575 sid: SecurityId,
576}
577
578#[derive(Debug)]
581pub(super) struct FileSystemState {
582 mount_options: FileSystemMountOptions,
584 pending_entries: Mutex<IndexSet<WeakKey<DirEntry>>>,
585
586 label: OnceLock<FileSystemLabel>,
588}
589
590impl FileSystemState {
591 fn new(mount_options: FileSystemMountOptions, _ops: &dyn FileSystemOps) -> Self {
592 let pending_entries = Mutex::new(IndexSet::new());
593 let label = OnceLock::new();
594
595 Self { mount_options, pending_entries, label }
596 }
597
598 pub fn label(&self) -> Option<&FileSystemLabel> {
600 self.label.get()
601 }
602
603 pub fn supports_relabel(&self) -> bool {
605 let Some(label) = self.label() else {
606 return false;
607 };
608 match label.scheme {
609 FileSystemLabelingScheme::Mountpoint { .. } => false,
610 FileSystemLabelingScheme::FsUse { .. } => true,
611 FileSystemLabelingScheme::GenFsCon { supports_seclabel } => supports_seclabel,
612 }
613 }
614
615 pub fn supports_xattr(&self) -> bool {
617 let Some(label) = self.label() else {
618 return false;
619 };
620 match label.scheme {
621 FileSystemLabelingScheme::Mountpoint { .. }
622 | FileSystemLabelingScheme::GenFsCon { .. } => false,
623 FileSystemLabelingScheme::FsUse { fs_use_type, .. } => fs_use_type == FsUseType::Xattr,
624 }
625 }
626
627 fn write_mount_options(
637 &self,
638 security_server: &SecurityServer,
639 buf: &mut impl OutputBuffer,
640 ) -> Result<(), Errno> {
641 let Some(label) = self.label() else {
642 return Self::write_mount_options_to_buf(buf, &self.mount_options);
643 };
644
645 let to_context = |sid| security_server.sid_to_security_context(sid);
646 let mount_options = FileSystemMountOptions {
647 context: label.mount_sids.context.and_then(to_context),
648 fs_context: label.mount_sids.fs_context.and_then(to_context),
649 def_context: label.mount_sids.def_context.and_then(to_context),
650 root_context: label.mount_sids.root_context.and_then(to_context),
651 };
652
653 if self.supports_relabel() {
654 buf.write_all(b",seclabel").map(|_| ())?;
655 }
656
657 Self::write_mount_options_to_buf(buf, &mount_options)
658 }
659
660 fn write_mount_options_to_buf(
662 buf: &mut impl OutputBuffer,
663 mount_options: &FileSystemMountOptions,
664 ) -> Result<(), Errno> {
665 let mut write_option = |prefix: &[u8], option: &Option<Vec<u8>>| -> Result<(), Errno> {
666 let Some(value) = option else {
667 return Ok(());
668 };
669 buf.write_all(prefix).map(|_| ())?;
670 buf.write_all(value).map(|_| ())
671 };
672 write_option(b",context=", &mount_options.context)?;
673 write_option(b",fscontext=", &mount_options.fs_context)?;
674 write_option(b",defcontext=", &mount_options.def_context)?;
675 write_option(b",rootcontext=", &mount_options.root_context)
676 }
677}
678
679#[derive(Debug, Clone)]
681pub struct FsNodeState {
682 label: FsNodeLabel,
683 class: FsNodeClass,
684}
685
686impl Default for FsNodeState {
687 fn default() -> Self {
688 Self { label: FsNodeLabel::Uninitialized, class: FileClass::File.into() }
689 }
690}
691
692#[derive(Debug, Clone)]
694pub(super) enum FsNodeLabel {
695 Uninitialized,
696 SecurityId { sid: SecurityId },
697 FromTask { task_state: TaskPersistentInfo },
699}
700
701impl FsNodeLabel {
702 fn is_initialized(&self) -> bool {
703 !matches!(self, FsNodeLabel::Uninitialized)
704 }
705}
706
707#[derive(Debug, PartialEq)]
709struct FsNodeSidAndClass {
710 sid: SecurityId,
711 class: FsNodeClass,
712}
713
714#[derive(Clone, Debug, PartialEq)]
717pub(super) struct BinderConnectionState {
718 sid: SecurityId,
719}
720
721#[derive(Debug, Default)]
724pub(super) struct SocketState {
725 peer_sid: Mutex<Option<SecurityId>>,
726}
727
728#[derive(Clone, Debug, PartialEq)]
731pub(super) struct BpfMapState {
732 sid: SecurityId,
733}
734
735#[derive(Clone, Debug, PartialEq)]
738pub(super) struct BpfProgState {
739 sid: SecurityId,
740}
741
742pub(super) fn set_cached_sid(fs_node: &FsNode, sid: SecurityId) {
746 fs_node.security_state.lock().label = FsNodeLabel::SecurityId { sid };
747}
748
749fn fs_node_set_label_with_task(fs_node: &FsNode, task_persistent_info: &TaskPersistentInfo) {
753 fs_node.security_state.lock().label =
754 FsNodeLabel::FromTask { task_state: task_persistent_info.clone() };
755}
756
757fn fs_node_ensure_class(fs_node: &FsNode) -> Result<FsNodeClass, Errno> {
761 let class = fs_node.security_state.lock().class;
763 if class != FileClass::File.into() {
764 return Ok(class);
765 }
766
767 let file_mode = fs_node.info().mode;
768 let class = file_class_from_file_mode(file_mode)?.into();
769 fs_node.security_state.lock().class = class;
770 Ok(class)
771}
772
773#[cfg(test)]
774pub(super) fn get_cached_sid(fs_node: &FsNode) -> Option<SecurityId> {
776 let state = fs_node.security_state.lock().clone();
777 if matches!(state.label, FsNodeLabel::Uninitialized) {
778 None
779 } else {
780 Some(fs_node_effective_sid_and_class(fs_node).sid)
781 }
782}
783
784#[derive(Debug)]
787pub enum PolicyCapSupport {
788 AlwaysOn(BugRef),
789 AlwaysOff(BugRef),
790 Configurable,
791 NotImplemented,
792}
793
794fn policycap_support(policy_cap: PolicyCap) -> PolicyCapSupport {
797 match policy_cap {
798 PolicyCap::AlwaysCheckNetwork => {
799 PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
800 }
801 PolicyCap::CgroupSeclabel => PolicyCapSupport::Configurable,
802 PolicyCap::ExtendedSocketClass => PolicyCapSupport::Configurable,
803 PolicyCap::FunctionfsSeclabel => PolicyCapSupport::Configurable,
804 PolicyCap::GenfsSeclabelSymlinks => PolicyCapSupport::Configurable,
805 PolicyCap::GenfsSeclabelWildcard => {
806 PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
807 }
808 PolicyCap::IoctlSkipCloexec => PolicyCapSupport::Configurable,
809 PolicyCap::MemfdClass => PolicyCapSupport::Configurable,
810 PolicyCap::NetifWildcard => {
811 PolicyCapSupport::AlwaysOff(bug_ref!("https://fxbug.dev/452453565"))
812 }
813 PolicyCap::NetlinkXperm => PolicyCapSupport::Configurable,
814 PolicyCap::NetworkPeerControls => PolicyCapSupport::NotImplemented,
815 PolicyCap::NnpNosuidTransition => PolicyCapSupport::Configurable,
816 PolicyCap::OpenPerms => PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565")),
817 PolicyCap::UserspaceInitialContext => {
818 PolicyCapSupport::AlwaysOn(bug_ref!("https://fxbug.dev/452453565"))
819 }
820 }
821}