1#![recursion_limit = "512"]
6
7use starnix_sync::LockEqualOrBefore;
8
9use seq_lock::{SeqLock, SeqLockable, WriteSize};
10
11use selinux::policy::metadata::POLICYDB_VERSION_MAX;
12use selinux::policy::{AccessDecision, AccessVector};
13use selinux::{
14 ClassId, InitialSid, PolicyCap, SeLinuxStatus, SeLinuxStatusPublisher, SecurityId,
15 SecurityPermission, SecurityServer,
16};
17use starnix_core::device::mem::DevNull;
18use starnix_core::mm::memory::MemoryObject;
19use starnix_core::security;
20use starnix_core::task::{CurrentTask, Kernel};
21use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
22use starnix_core::vfs::pseudo::simple_directory::{SimpleDirectory, SimpleDirectoryMutator};
23use starnix_core::vfs::pseudo::simple_file::{
24 BytesFile, BytesFileOps, SimpleFileNode, parse_unsigned_file,
25};
26use starnix_core::vfs::pseudo::vec_directory::{VecDirectory, VecDirectoryEntry};
27use starnix_core::vfs::{
28 CacheMode, DirEntry, DirectoryEntryType, DirentSink, FileObject, FileOps, FileSystem,
29 FileSystemHandle, FileSystemOps, FileSystemOptions, FsNode, FsNodeHandle, FsNodeInfo,
30 FsNodeOps, FsStr, FsString, MemoryRegularNode, NamespaceNode, emit_dotdot,
31 fileops_impl_directory, fileops_impl_noop_sync, fileops_impl_seekable,
32 fileops_impl_unbounded_seek, fs_node_impl_dir_readonly, fs_node_impl_not_dir,
33};
34use starnix_logging::{
35 __track_stub_inner, BugRef, impossible_error, log_error, log_info, track_stub,
36};
37use starnix_sync::{FileOpsCore, Locked, Mutex, Unlocked};
38use starnix_types::vfs::default_statfs;
39use starnix_uapi::auth::FsCred;
40use starnix_uapi::device_id::DeviceId;
41use starnix_uapi::errors::Errno;
42use starnix_uapi::file_mode::mode;
43use starnix_uapi::open_flags::OpenFlags;
44use starnix_uapi::{AUDIT_AVC, SELINUX_MAGIC, errno, error, statfs};
45use std::borrow::Cow;
46use std::num::{NonZeroU32, NonZeroU64};
47use std::ops::Deref;
48use std::str::FromStr;
49use std::sync::{Arc, OnceLock, Weak};
50use strum::VariantArray as _;
51use zerocopy::{Immutable, IntoBytes};
52
53const SELINUX_STATUS_VERSION: u32 = 1;
55
56#[derive(IntoBytes, Copy, Clone, Immutable)]
60#[repr(C, align(4))]
61struct SeLinuxStatusHeader {
62 version: u32,
64}
65
66impl Default for SeLinuxStatusHeader {
67 fn default() -> Self {
68 Self { version: SELINUX_STATUS_VERSION }
69 }
70}
71
72#[derive(IntoBytes, Copy, Clone, Default, Immutable)]
76#[repr(C, align(4))]
77struct SeLinuxStatusValue {
78 enforcing: u32,
80 policyload: u32,
82 deny_unknown: u32,
84}
85
86unsafe impl SeqLockable for SeLinuxStatusValue {
89 const WRITE_SIZE: WriteSize = WriteSize::Four;
90 const HAS_INLINE_SEQUENCE: bool = false;
91 const VMO_NAME: &'static [u8] = b"starnix:selinux";
92}
93
94type StatusSeqLock = SeqLock<SeLinuxStatusHeader, SeLinuxStatusValue>;
95
96struct StatusPublisher(StatusSeqLock);
97
98impl StatusPublisher {
99 pub fn new_default() -> Result<Self, zx::Status> {
100 let seq_lock = StatusSeqLock::new_default()?;
101 Ok(StatusPublisher(seq_lock))
102 }
103}
104
105impl SeLinuxStatusPublisher for StatusPublisher {
106 fn set_status(&mut self, policy_status: SeLinuxStatus) {
107 self.0.set_value(SeLinuxStatusValue {
108 enforcing: policy_status.is_enforcing as u32,
109 policyload: policy_status.change_count,
110 deny_unknown: policy_status.deny_unknown as u32,
111 })
112 }
113}
114
115struct SeLinuxFs;
116impl FileSystemOps for SeLinuxFs {
117 fn statfs(
118 &self,
119 _locked: &mut Locked<FileOpsCore>,
120 _fs: &FileSystem,
121 _current_task: &CurrentTask,
122 ) -> Result<statfs, Errno> {
123 Ok(default_statfs(SELINUX_MAGIC))
124 }
125 fn name(&self) -> &'static FsStr {
126 "selinuxfs".into()
127 }
128}
129
130impl SeLinuxFs {
134 fn new_fs<L>(
135 locked: &mut Locked<L>,
136 current_task: &CurrentTask,
137 options: FileSystemOptions,
138 ) -> Result<FileSystemHandle, Errno>
139 where
140 L: LockEqualOrBefore<FileOpsCore>,
141 {
142 let security_server = security::selinuxfs_get_admin_api(current_task)
144 .ok_or_else(|| errno!(ENODEV, "selinuxfs"))?;
145
146 let kernel = current_task.kernel();
147 let fs = FileSystem::new(locked, kernel, CacheMode::Permanent, SeLinuxFs, options)?;
148 let root = SimpleDirectory::new();
149 fs.create_root(fs.allocate_ino(), root.clone());
150 let dir = SimpleDirectoryMutator::new(fs.clone(), root);
151
152 dir.subdir("avc", 0o555, |dir| {
154 dir.entry(
155 "cache_stats",
156 AvcCacheStatsFile::new_node(security_server.clone()),
157 mode!(IFREG, 0o444),
158 );
159 });
160 dir.entry("checkreqprot", CheckReqProtApi::new_node(), mode!(IFREG, 0o644));
161 dir.entry("class", ClassDirectory::new(security_server.clone()), mode!(IFDIR, 0o555));
162 dir.entry(
163 "deny_unknown",
164 DenyUnknownFile::new_node(security_server.clone()),
165 mode!(IFREG, 0o444),
166 );
167 dir.entry(
168 "reject_unknown",
169 RejectUnknownFile::new_node(security_server.clone()),
170 mode!(IFREG, 0o444),
171 );
172 dir.subdir("initial_contexts", 0o555, |dir| {
173 for initial_sid in InitialSid::all_variants() {
174 dir.entry(
175 initial_sid.name(),
176 InitialContextFile::new_node(security_server.clone(), *initial_sid),
177 mode!(IFREG, 0o444),
178 );
179 }
180 });
181 dir.entry("mls", BytesFile::new_node(b"1".to_vec()), mode!(IFREG, 0o444));
182 dir.entry("policy", PolicyFile::new_node(security_server.clone()), mode!(IFREG, 0o600));
183 dir.subdir("policy_capabilities", 0o555, |dir| {
184 for capability in PolicyCap::VARIANTS {
185 dir.entry(
186 capability.name(),
187 PolicyCapFile::new_node(security_server.clone(), *capability),
188 mode!(IFREG, 0o444),
189 );
190 }
191 });
192 dir.entry(
193 "policyvers",
194 BytesFile::new_node(format!("{}", POLICYDB_VERSION_MAX).into_bytes()),
195 mode!(IFREG, 0o444),
196 );
197
198 let status_holder = StatusPublisher::new_default().expect("selinuxfs status seqlock");
202 let status_file = status_holder
203 .0
204 .get_readonly_vmo()
205 .duplicate_handle(zx::Rights::SAME_RIGHTS)
206 .map_err(impossible_error)?;
207 dir.entry(
208 "status",
209 MemoryRegularNode::from_memory(Arc::new(MemoryObject::from(status_file))),
210 mode!(IFREG, 0o444),
211 );
212 security_server.set_status_publisher(Box::new(status_holder));
213
214 dir.entry(
216 "access",
217 AccessApi::new_node(security_server.clone(), current_task.kernel()),
218 mode!(IFREG, 0o666),
219 );
220 dir.entry("context", ContextApi::new_node(security_server.clone()), mode!(IFREG, 0o666));
221 dir.entry("create", CreateApi::new_node(security_server.clone()), mode!(IFREG, 0o666));
222 dir.entry("member", MemberApi::new_node(), mode!(IFREG, 0o666));
223 dir.entry("relabel", RelabelApi::new_node(), mode!(IFREG, 0o666));
224 dir.entry("user", UserApi::new_node(), mode!(IFREG, 0o666));
225 dir.entry("load", LoadApi::new_node(security_server.clone()), mode!(IFREG, 0o600));
226 dir.entry(
227 "commit_pending_bools",
228 CommitBooleansApi::new_node(security_server.clone()),
229 mode!(IFREG, 0o200),
230 );
231
232 dir.entry("booleans", BooleansDirectory::new(security_server.clone()), mode!(IFDIR, 0o555));
234 dir.entry("enforce", EnforceApi::new_node(security_server), mode!(IFREG, 0o644));
236
237 let null_ops: Box<dyn FsNodeOps> = (NullFileNode).into();
239 let mut info = FsNodeInfo::new(mode!(IFCHR, 0o666), FsCred::root());
240 info.rdev = DeviceId::NULL;
241 let null_fs_node = fs.create_node_and_allocate_node_id(null_ops, info);
242 dir.node("null".into(), null_fs_node.clone());
243
244 let null_ops: Box<dyn FileOps> = Box::new(DevNull);
248 let null_flags = OpenFlags::empty();
249 let null_name =
250 NamespaceNode::new_anonymous(DirEntry::new(null_fs_node, None, "null".into()));
251 let null_file_object =
252 FileObject::new(locked, current_task, null_ops, null_name, null_flags)
253 .expect("create file object for just-created selinuxfs/null");
254 security::selinuxfs_init_null(current_task, &null_file_object);
255
256 Ok(fs)
257 }
258}
259
260struct LoadApi {
263 security_server: Arc<SecurityServer>,
264}
265
266impl LoadApi {
267 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
268 SeLinuxApi::new_node(move || Ok(Self { security_server: security_server.clone() }))
269 }
270}
271
272impl SeLinuxApiOps for LoadApi {
273 fn api_write_permission() -> SecurityPermission {
274 SecurityPermission::LoadPolicy
275 }
276 fn api_write_with_task(
277 &self,
278 locked: &mut Locked<FileOpsCore>,
279 current_task: &CurrentTask,
280 data: Vec<u8>,
281 ) -> Result<(), Errno> {
282 log_info!("Loading {} byte policy", data.len());
283 self.security_server.load_policy(data).map_err(|error| {
284 log_error!("Policy load error: {}", error);
285 errno!(EINVAL)
286 })?;
287
288 security::selinuxfs_policy_loaded(locked, current_task);
290
291 Ok(())
292 }
293}
294
295struct PolicyFile {
298 security_server: Arc<SecurityServer>,
299}
300
301impl PolicyFile {
302 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
303 SimpleFileNode::new(move |_, _| Ok(Self { security_server: security_server.clone() }))
304 }
305}
306
307impl FileOps for PolicyFile {
308 fileops_impl_seekable!();
309 fileops_impl_noop_sync!();
310
311 fn open(
312 &self,
313 _locked: &mut Locked<FileOpsCore>,
314 _file: &FileObject,
315 current_task: &CurrentTask,
316 ) -> Result<(), Errno> {
317 security::selinuxfs_check_access(current_task, SecurityPermission::ReadPolicy)?;
318 Ok(())
319 }
320
321 fn read(
322 &self,
323 _locked: &mut Locked<FileOpsCore>,
324 _file: &FileObject,
325 _current_task: &CurrentTask,
326 offset: usize,
327 data: &mut dyn OutputBuffer,
328 ) -> Result<usize, Errno> {
329 let policy = self.security_server.get_binary_policy().ok_or_else(|| errno!(EINVAL))?;
330 let policy_bytes: &[u8] = policy.deref();
331
332 if offset >= policy_bytes.len() {
333 return Ok(0);
334 }
335
336 data.write(&policy_bytes[offset..])
337 }
338
339 fn write(
340 &self,
341 _locked: &mut Locked<FileOpsCore>,
342 _file: &FileObject,
343 _current_task: &CurrentTask,
344 _offset: usize,
345 _data: &mut dyn InputBuffer,
346 ) -> Result<usize, Errno> {
347 error!(EACCES)
348 }
349}
350
351struct EnforceApi {
353 security_server: Arc<SecurityServer>,
354}
355
356impl EnforceApi {
357 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
358 SeLinuxApi::new_node(move || Ok(Self { security_server: security_server.clone() }))
359 }
360}
361
362impl SeLinuxApiOps for EnforceApi {
363 fn api_write_permission() -> SecurityPermission {
364 SecurityPermission::SetEnforce
365 }
366
367 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
368 let enforce = parse_unsigned_file::<u32>(&data)? != 0;
370 self.security_server.set_enforcing(enforce);
371 Ok(())
372 }
373
374 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
375 Ok(self.security_server.is_enforcing().then_some(b"1").unwrap_or(b"0").into())
376 }
377}
378
379struct DenyUnknownFile {
382 security_server: Arc<SecurityServer>,
383}
384
385impl DenyUnknownFile {
386 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
387 BytesFile::new_node(Self { security_server })
388 }
389}
390
391impl BytesFileOps for DenyUnknownFile {
392 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
393 Ok(format!("{}", self.security_server.deny_unknown() as u32).into_bytes().into())
394 }
395}
396
397struct RejectUnknownFile {
400 security_server: Arc<SecurityServer>,
401}
402
403impl RejectUnknownFile {
404 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
405 BytesFile::new_node(Self { security_server })
406 }
407}
408
409impl BytesFileOps for RejectUnknownFile {
410 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
411 Ok(format!("{}", self.security_server.reject_unknown() as u32).into_bytes().into())
412 }
413}
414
415struct CreateApi {
418 security_server: Arc<SecurityServer>,
419 result: OnceLock<SecurityId>,
420}
421
422impl CreateApi {
423 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
424 SeLinuxApi::new_node(move || {
425 Ok(Self { security_server: security_server.clone(), result: OnceLock::new() })
426 })
427 }
428}
429
430impl SeLinuxApiOps for CreateApi {
431 fn api_write_permission() -> SecurityPermission {
432 SecurityPermission::ComputeCreate
433 }
434
435 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
436 if self.result.get().is_some() {
437 return error!(EBUSY);
439 }
440
441 let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
442
443 let mut parts = data.split_whitespace();
445
446 let scontext = parts.next().ok_or_else(|| errno!(EINVAL))?;
448 let scontext = self
449 .security_server
450 .security_context_to_sid(scontext.into())
451 .map_err(|_| errno!(EINVAL))?;
452
453 let tcontext = parts.next().ok_or_else(|| errno!(EINVAL))?;
455 let tcontext = self
456 .security_server
457 .security_context_to_sid(tcontext.into())
458 .map_err(|_| errno!(EINVAL))?;
459
460 let tclass = parts.next().ok_or_else(|| errno!(EINVAL))?;
463 let tclass = u32::from_str(tclass).map_err(|_| errno!(EINVAL))?;
464 let tclass = ClassId::new(NonZeroU32::new(tclass).ok_or_else(|| errno!(EINVAL))?);
465
466 let tname = parts.next();
469 if tname.is_some() {
470 track_stub!(TODO("https://fxbug.dev/361552580"), "selinux create with name");
471 return error!(ENOTSUP);
472 }
473
474 if parts.next().is_some() {
476 return error!(EINVAL);
477 }
478
479 let result = self
480 .security_server
481 .compute_create_sid_raw(scontext, tcontext, tclass)
482 .map_err(|_| errno!(EINVAL))?;
483 self.result.set(result).map_err(|_| errno!(EINVAL))?;
484
485 Ok(())
486 }
487
488 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
489 let maybe_context = self
490 .result
491 .get()
492 .map(|sid| self.security_server.sid_to_security_context_with_nul(*sid).unwrap());
493 let context = maybe_context.unwrap_or_else(|| Vec::new());
494 Ok(context.into())
495 }
496}
497
498struct MemberApi;
501
502impl MemberApi {
503 fn new_node() -> impl FsNodeOps {
504 SeLinuxApi::new_node(|| Ok(Self {}))
505 }
506}
507
508impl SeLinuxApiOps for MemberApi {
509 fn api_write_permission() -> SecurityPermission {
510 SecurityPermission::ComputeMember
511 }
512 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
513 track_stub!(TODO("https://fxbug.dev/399069170"), "selinux member");
514 error!(ENOTSUP)
515 }
516 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
517 error!(ENOTSUP)
518 }
519}
520
521struct RelabelApi;
524
525impl RelabelApi {
526 fn new_node() -> impl FsNodeOps {
527 SeLinuxApi::new_node(|| Ok(Self {}))
528 }
529}
530
531impl SeLinuxApiOps for RelabelApi {
532 fn api_write_permission() -> SecurityPermission {
533 SecurityPermission::ComputeRelabel
534 }
535 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
536 track_stub!(TODO("https://fxbug.dev/399069766"), "selinux relabel");
537 error!(ENOTSUP)
538 }
539 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
540 error!(ENOTSUP)
541 }
542}
543
544struct UserApi;
546
547impl UserApi {
548 fn new_node() -> impl FsNodeOps {
549 SeLinuxApi::new_node(|| Ok(Self {}))
550 }
551}
552
553impl SeLinuxApiOps for UserApi {
554 fn api_write_permission() -> SecurityPermission {
555 SecurityPermission::ComputeUser
556 }
557 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
558 track_stub!(TODO("https://fxbug.dev/411433214"), "selinux user");
559 error!(ENOTSUP)
560 }
561 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
562 error!(ENOTSUP)
563 }
564}
565
566struct CheckReqProtApi;
567
568impl CheckReqProtApi {
569 fn new_node() -> impl FsNodeOps {
570 SeLinuxApi::new_node(|| Ok(Self {}))
571 }
572}
573
574impl SeLinuxApiOps for CheckReqProtApi {
575 fn api_write_permission() -> SecurityPermission {
576 SecurityPermission::SetCheckReqProt
577 }
578
579 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
580 error!(ENOTSUP)
583 }
584
585 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
586 Ok(b"0".into())
587 }
588}
589
590struct ContextApi {
594 security_server: Arc<SecurityServer>,
595 context_sid: Mutex<Option<SecurityId>>,
597}
598
599impl ContextApi {
600 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
601 SeLinuxApi::new_node(move || {
602 Ok(Self { security_server: security_server.clone(), context_sid: Mutex::default() })
603 })
604 }
605}
606
607impl SeLinuxApiOps for ContextApi {
608 fn api_write_permission() -> SecurityPermission {
609 SecurityPermission::CheckContext
610 }
611
612 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
613 let mut context_sid = self.context_sid.lock();
615 if context_sid.is_some() {
616 return error!(EBUSY);
617 }
618
619 *context_sid = Some(
622 self.security_server
623 .security_context_to_sid(data.as_slice().into())
624 .map_err(|_| errno!(EINVAL))?,
625 );
626
627 Ok(())
628 }
629
630 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
631 let maybe_sid = *self.context_sid.lock();
637 let result = maybe_sid
638 .and_then(|sid| self.security_server.sid_to_security_context_with_nul(sid))
639 .unwrap_or_default();
640 Ok(result.into())
641 }
642}
643
644struct InitialContextFile {
646 security_server: Arc<SecurityServer>,
647 initial_sid: InitialSid,
648}
649
650impl InitialContextFile {
651 fn new_node(security_server: Arc<SecurityServer>, initial_sid: InitialSid) -> impl FsNodeOps {
652 BytesFile::new_node(Self { security_server, initial_sid })
653 }
654}
655
656impl BytesFileOps for InitialContextFile {
657 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
658 let sid = self.initial_sid.into();
659 if let Some(context) = self.security_server.sid_to_security_context_with_nul(sid) {
660 Ok(context.into())
661 } else {
662 Ok(self.initial_sid.name().as_bytes().into())
666 }
667 }
668}
669
670struct PolicyCapFile {
674 security_server: Arc<SecurityServer>,
675 policy_cap: PolicyCap,
676}
677
678impl PolicyCapFile {
679 fn new_node(security_server: Arc<SecurityServer>, initial_sid: PolicyCap) -> impl FsNodeOps {
680 BytesFile::new_node(Self { security_server, policy_cap: initial_sid })
681 }
682}
683
684impl BytesFileOps for PolicyCapFile {
685 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
686 if self.security_server.is_policycap_enabled(self.policy_cap) {
687 Ok(b"1".into())
688 } else {
689 Ok(b"0".into())
690 }
691 }
692}
693
694struct AccessDecisionAndDecided {
699 decision: AccessDecision,
700 decided: AccessVector,
701}
702
703struct AccessApi {
704 security_server: Arc<SecurityServer>,
705 result: OnceLock<AccessDecisionAndDecided>,
706
707 kernel: Weak<Kernel>,
709}
710
711impl AccessApi {
712 fn new_node(security_server: Arc<SecurityServer>, kernel: &Arc<Kernel>) -> impl FsNodeOps {
713 let kernel = Arc::downgrade(kernel);
714 SeLinuxApi::new_node(move || {
715 Ok(Self {
716 security_server: security_server.clone(),
717 result: OnceLock::default(),
718 kernel: kernel.clone(),
719 })
720 })
721 }
722}
723
724impl SeLinuxApiOps for AccessApi {
725 fn api_write_permission() -> SecurityPermission {
726 SecurityPermission::ComputeAv
727 }
728
729 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
730 if self.result.get().is_some() {
731 return error!(EBUSY);
733 }
734
735 let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
736
737 let mut parts = data.split_whitespace();
739
740 let scontext_str = parts.next().ok_or_else(|| errno!(EINVAL))?;
742 let scontext = self
743 .security_server
744 .security_context_to_sid(scontext_str.into())
745 .map_err(|_| errno!(EINVAL))?;
746
747 let tcontext_str = parts.next().ok_or_else(|| errno!(EINVAL))?;
749 let tcontext = self
750 .security_server
751 .security_context_to_sid(tcontext_str.into())
752 .map_err(|_| errno!(EINVAL))?;
753
754 let tclass = parts.next().ok_or_else(|| errno!(EINVAL))?;
757 let tclass_id = u32::from_str(tclass).map_err(|_| errno!(EINVAL))?;
758 let tclass = ClassId::new(NonZeroU32::new(tclass_id).ok_or_else(|| errno!(EINVAL))?).into();
759
760 let requested = if let Some(requested) = parts.next() {
762 AccessVector::from_str(requested).map_err(|_| errno!(EINVAL))?
763 } else {
764 AccessVector::ALL
765 };
766
767 let mut decision =
771 self.security_server.compute_access_decision_raw(scontext, tcontext, tclass);
772
773 let mut decided = AccessVector::ALL;
777
778 let Some(kernel) = self.kernel.upgrade() else {
781 return error!(EINVAL);
782 };
783 if let Some(todo_bug) = decision.todo_bug {
784 let denied = AccessVector::ALL - decision.allow;
785 let audited_denied = denied & decision.auditdeny;
786
787 let requested_has_audited_denial = audited_denied & requested != AccessVector::NONE;
788
789 if requested_has_audited_denial {
790 __track_stub_inner(
794 BugRef::from(NonZeroU64::new(todo_bug.get() as u64).unwrap()),
795 "Enforce SELinuxFS access API",
796 None,
797 std::panic::Location::caller(),
798 );
799 let audit_message = format!(
800 "avc: todo_deny {{ ACCESS_API }} bug={todo_bug} scontext={scontext_str:?} tcontext={tcontext_str:?} tclass={tclass_id} requested={requested:?}",
801 );
802 kernel.audit_logger().audit_log(AUDIT_AVC as u16, || audit_message);
803 } else {
804 decided -= audited_denied;
809 }
810
811 decision.allow = AccessVector::ALL;
814 }
815
816 self.result
817 .set(AccessDecisionAndDecided { decision, decided })
818 .map_err(|_| errno!(EINVAL))?;
819
820 Ok(())
821 }
822
823 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
824 let Some(AccessDecisionAndDecided { decision, decided }) = self.result.get() else {
825 return Ok(Vec::new().into());
826 };
827
828 let allowed = decision.allow;
829 let auditallow = decision.auditallow;
830 let auditdeny = decision.auditdeny;
831 let flags = decision.flags;
832
833 const SEQNO: u32 = 1;
836
837 let result =
840 format!("{allowed:x} {decided:x} {auditallow:x} {auditdeny:x} {SEQNO} {flags:x}");
841 Ok(result.into_bytes().into())
842 }
843}
844
845struct NullFileNode;
846
847impl FsNodeOps for NullFileNode {
848 fs_node_impl_not_dir!();
849
850 fn create_file_ops(
851 &self,
852 _locked: &mut Locked<FileOpsCore>,
853 _node: &FsNode,
854 _current_task: &CurrentTask,
855 _flags: OpenFlags,
856 ) -> Result<Box<dyn FileOps>, Errno> {
857 Ok(Box::new(DevNull))
858 }
859}
860
861#[derive(Clone)]
862struct BooleansDirectory {
863 security_server: Arc<SecurityServer>,
864}
865
866impl BooleansDirectory {
867 fn new(security_server: Arc<SecurityServer>) -> Self {
868 Self { security_server }
869 }
870}
871
872impl FsNodeOps for BooleansDirectory {
873 fs_node_impl_dir_readonly!();
874
875 fn create_file_ops(
876 &self,
877 _locked: &mut Locked<FileOpsCore>,
878 _node: &FsNode,
879 _current_task: &CurrentTask,
880 _flags: OpenFlags,
881 ) -> Result<Box<dyn FileOps>, Errno> {
882 Ok(Box::new(self.clone()))
883 }
884
885 fn lookup(
886 &self,
887 _locked: &mut Locked<FileOpsCore>,
888 node: &FsNode,
889 current_task: &CurrentTask,
890 name: &FsStr,
891 ) -> Result<FsNodeHandle, Errno> {
892 let utf8_name = String::from_utf8(name.to_vec()).map_err(|_| errno!(ENOENT))?;
893 if self.security_server.conditional_booleans().contains(&utf8_name) {
894 Ok(node.fs().create_node_and_allocate_node_id(
895 BooleanFile::new_node(self.security_server.clone(), utf8_name),
896 FsNodeInfo::new(mode!(IFREG, 0o644), current_task.current_fscred()),
897 ))
898 } else {
899 error!(ENOENT)
900 }
901 }
902}
903
904impl FileOps for BooleansDirectory {
905 fileops_impl_directory!();
906 fileops_impl_noop_sync!();
907 fileops_impl_unbounded_seek!();
908
909 fn readdir(
910 &self,
911 _locked: &mut Locked<FileOpsCore>,
912 file: &FileObject,
913 _current_task: &CurrentTask,
914 sink: &mut dyn DirentSink,
915 ) -> Result<(), Errno> {
916 emit_dotdot(file, sink)?;
917
918 let iter_offset = sink.offset() - 2;
921 for name in self.security_server.conditional_booleans().iter().skip(iter_offset as usize) {
922 sink.add(
923 file.fs.allocate_ino(),
924 sink.offset() + 1,
925 DirectoryEntryType::REG,
926 FsString::from(name.as_bytes()).as_ref(),
927 )?;
928 }
929
930 Ok(())
931 }
932}
933
934struct BooleanFile {
935 security_server: Arc<SecurityServer>,
936 name: String,
937}
938
939impl BooleanFile {
940 fn new_node(security_server: Arc<SecurityServer>, name: String) -> impl FsNodeOps {
941 BytesFile::new_node(BooleanFile { security_server, name })
942 }
943}
944
945impl BytesFileOps for BooleanFile {
946 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
947 let value = parse_unsigned_file::<u32>(&data)? != 0;
948 self.security_server.set_pending_boolean(&self.name, value).map_err(|_| errno!(EIO))
949 }
950
951 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
952 let (active, pending) =
956 self.security_server.get_boolean(&self.name).map_err(|_| errno!(EIO))?;
957 Ok(format!("{} {}", active as u32, pending as u32).into_bytes().into())
958 }
959}
960
961struct CommitBooleansApi {
962 security_server: Arc<SecurityServer>,
963}
964
965impl CommitBooleansApi {
966 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
967 SeLinuxApi::new_node(move || {
968 Ok(CommitBooleansApi { security_server: security_server.clone() })
969 })
970 }
971}
972
973impl SeLinuxApiOps for CommitBooleansApi {
974 fn api_write_permission() -> SecurityPermission {
975 SecurityPermission::SetBool
976 }
977
978 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
979 let commit = parse_unsigned_file::<u32>(&data)? != 0;
983
984 if commit {
985 self.security_server.commit_pending_booleans();
986 }
987 Ok(())
988 }
989}
990
991struct ClassDirectory {
992 security_server: Arc<SecurityServer>,
993}
994
995impl ClassDirectory {
996 fn new(security_server: Arc<SecurityServer>) -> Self {
997 Self { security_server }
998 }
999}
1000
1001impl FsNodeOps for ClassDirectory {
1002 fs_node_impl_dir_readonly!();
1003
1004 fn create_file_ops(
1006 &self,
1007 _locked: &mut Locked<FileOpsCore>,
1008 _node: &FsNode,
1009 _current_task: &CurrentTask,
1010 _flags: OpenFlags,
1011 ) -> Result<Box<dyn FileOps>, Errno> {
1012 Ok(VecDirectory::new_file(
1013 self.security_server
1014 .class_names()
1015 .map_err(|_| errno!(ENOENT))?
1016 .iter()
1017 .map(|class_name| VecDirectoryEntry {
1018 entry_type: DirectoryEntryType::DIR,
1019 name: class_name.clone().into(),
1020 inode: None,
1021 })
1022 .collect(),
1023 ))
1024 }
1025
1026 fn lookup(
1027 &self,
1028 _locked: &mut Locked<FileOpsCore>,
1029 node: &FsNode,
1030 _current_task: &CurrentTask,
1031 name: &FsStr,
1032 ) -> Result<FsNodeHandle, Errno> {
1033 let id: u32 = self
1034 .security_server
1035 .class_id_by_name(&name.to_string())
1036 .map_err(|_| errno!(EINVAL))?
1037 .into();
1038
1039 let fs = node.fs();
1040 let dir = SimpleDirectory::new();
1041 dir.edit(&fs, |dir| {
1042 let index_bytes = format!("{}", id).into_bytes();
1043 dir.entry("index", BytesFile::new_node(index_bytes), mode!(IFREG, 0o444));
1044 dir.entry(
1045 "perms",
1046 PermsDirectory::new(self.security_server.clone(), name.to_string()),
1047 mode!(IFDIR, 0o555),
1048 );
1049 });
1050 Ok(dir.into_node(&fs, 0o555))
1051 }
1052}
1053
1054struct PermsDirectory {
1056 security_server: Arc<SecurityServer>,
1057 class_name: String,
1058}
1059
1060impl PermsDirectory {
1061 fn new(security_server: Arc<SecurityServer>, class_name: String) -> Self {
1062 Self { security_server, class_name }
1063 }
1064}
1065
1066impl FsNodeOps for PermsDirectory {
1067 fs_node_impl_dir_readonly!();
1068
1069 fn create_file_ops(
1071 &self,
1072 _locked: &mut Locked<FileOpsCore>,
1073 _node: &FsNode,
1074 _current_task: &CurrentTask,
1075 _flags: OpenFlags,
1076 ) -> Result<Box<dyn FileOps>, Errno> {
1077 Ok(VecDirectory::new_file(
1078 self.security_server
1079 .class_permissions_by_name(&self.class_name)
1080 .map_err(|_| errno!(ENOENT))?
1081 .iter()
1082 .map(|(_permission_id, permission_name)| VecDirectoryEntry {
1083 entry_type: DirectoryEntryType::DIR,
1084 name: permission_name.clone().into(),
1085 inode: None,
1086 })
1087 .collect(),
1088 ))
1089 }
1090
1091 fn lookup(
1092 &self,
1093 _locked: &mut Locked<FileOpsCore>,
1094 node: &FsNode,
1095 current_task: &CurrentTask,
1096 name: &FsStr,
1097 ) -> Result<FsNodeHandle, Errno> {
1098 let found_permission_id = self
1099 .security_server
1100 .class_permissions_by_name(&(self.class_name))
1101 .map_err(|_| errno!(ENOENT))?
1102 .iter()
1103 .find(|(_permission_id, permission_name)| permission_name == name)
1104 .ok_or_else(|| errno!(ENOENT))?
1105 .0;
1106
1107 Ok(node.fs().create_node_and_allocate_node_id(
1108 BytesFile::new_node(format!("{}", found_permission_id).into_bytes()),
1109 FsNodeInfo::new(mode!(IFREG, 0o444), current_task.current_fscred()),
1110 ))
1111 }
1112}
1113
1114struct AvcCacheStatsFile {
1116 security_server: Arc<SecurityServer>,
1117}
1118
1119impl AvcCacheStatsFile {
1120 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
1121 BytesFile::new_node(Self { security_server })
1122 }
1123}
1124
1125impl BytesFileOps for AvcCacheStatsFile {
1126 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1127 let stats = self.security_server.avc_cache_stats();
1128 Ok(format!(
1129 "lookups hits misses allocations reclaims frees\n{} {} {} {} {} {}\n",
1130 stats.lookups, stats.hits, stats.misses, stats.allocs, stats.reclaims, stats.frees
1131 )
1132 .into_bytes()
1133 .into())
1134 }
1135}
1136
1137struct SeLinuxApi<T: SeLinuxApiOps + Sync + Send + 'static> {
1164 ops: T,
1165}
1166
1167impl<T: SeLinuxApiOps + Sync + Send + 'static> SeLinuxApi<T> {
1168 fn new_node<F>(create_ops: F) -> impl FsNodeOps
1171 where
1172 F: Fn() -> Result<T, Errno> + Send + Sync + 'static,
1173 {
1174 SimpleFileNode::new(move |_, _| create_ops().map(|ops| SeLinuxApi { ops }))
1175 }
1176}
1177
1178trait SeLinuxApiOps {
1180 fn api_write_permission() -> SecurityPermission;
1182
1183 fn api_write_ignores_offset() -> bool {
1185 false
1186 }
1187
1188 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
1190 error!(EINVAL)
1191 }
1192
1193 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
1195 error!(EINVAL)
1196 }
1197
1198 fn api_write_with_task(
1200 &self,
1201 _locked: &mut Locked<FileOpsCore>,
1202 _current_task: &CurrentTask,
1203 data: Vec<u8>,
1204 ) -> Result<(), Errno> {
1205 self.api_write(data)
1206 }
1207}
1208
1209impl<T: SeLinuxApiOps + Sync + Send + 'static> FileOps for SeLinuxApi<T> {
1210 fileops_impl_seekable!();
1211 fileops_impl_noop_sync!();
1212
1213 fn writes_update_seek_offset(&self) -> bool {
1214 false
1215 }
1216
1217 fn read(
1218 &self,
1219 _locked: &mut Locked<FileOpsCore>,
1220 _file: &FileObject,
1221 _current_task: &CurrentTask,
1222 offset: usize,
1223 data: &mut dyn OutputBuffer,
1224 ) -> Result<usize, Errno> {
1225 let response = self.ops.api_read()?;
1226 data.write(&response[offset..])
1227 }
1228
1229 fn write(
1230 &self,
1231 locked: &mut Locked<FileOpsCore>,
1232 _file: &FileObject,
1233 current_task: &CurrentTask,
1234 offset: usize,
1235 data: &mut dyn InputBuffer,
1236 ) -> Result<usize, Errno> {
1237 if offset != 0 && !T::api_write_ignores_offset() {
1238 return error!(EINVAL);
1239 }
1240 security::selinuxfs_check_access(current_task, T::api_write_permission())?;
1241 let data = data.read_all()?;
1242 let data_len = data.len();
1243 self.ops.api_write_with_task(locked, current_task, data)?;
1244 Ok(data_len)
1245 }
1246}
1247
1248pub fn selinux_fs(
1250 locked: &mut Locked<Unlocked>,
1251 current_task: &CurrentTask,
1252 options: FileSystemOptions,
1253) -> Result<FileSystemHandle, Errno> {
1254 struct SeLinuxFsHandle(FileSystemHandle);
1255
1256 Ok(current_task
1257 .kernel()
1258 .expando
1259 .get_or_try_init(|| Ok(SeLinuxFsHandle(SeLinuxFs::new_fs(locked, current_task, options)?)))?
1260 .0
1261 .clone())
1262}
1263
1264#[cfg(test)]
1265mod tests {
1266 use super::*;
1267 use fuchsia_runtime;
1268 use selinux::SecurityServer;
1269 use zerocopy::{FromBytes, KnownLayout};
1270
1271 #[fuchsia::test]
1272 fn status_vmo_has_correct_size_and_rights() {
1273 const STATUS_T_SIZE: usize = size_of::<u32>() * 5;
1276
1277 let status_holder = StatusPublisher::new_default().unwrap();
1278 let status_vmo = status_holder.0.get_readonly_vmo();
1279
1280 let content_size = status_vmo.get_content_size().unwrap() as usize;
1282 assert_eq!(content_size, STATUS_T_SIZE);
1283 let actual_size = status_vmo.get_size().unwrap() as usize;
1284 assert!(actual_size >= STATUS_T_SIZE);
1285
1286 let rights = status_vmo.basic_info().unwrap().rights;
1288 assert_eq!((rights & zx::Rights::MAP), zx::Rights::MAP);
1289 assert_eq!((rights & zx::Rights::READ), zx::Rights::READ);
1290 assert_eq!((rights & zx::Rights::GET_PROPERTY), zx::Rights::GET_PROPERTY);
1291 assert_eq!((rights & zx::Rights::WRITE), zx::Rights::NONE);
1292 assert_eq!((rights & zx::Rights::RESIZE), zx::Rights::NONE);
1293 }
1294
1295 #[derive(KnownLayout, FromBytes)]
1296 #[repr(C, align(4))]
1297 struct TestSeLinuxStatusT {
1298 version: u32,
1299 sequence: u32,
1300 enforcing: u32,
1301 policyload: u32,
1302 deny_unknown: u32,
1303 }
1304
1305 fn with_status_t<R>(
1306 status_vmo: &Arc<zx::Vmo>,
1307 do_test: impl FnOnce(&TestSeLinuxStatusT) -> R,
1308 ) -> R {
1309 let flags = zx::VmarFlags::PERM_READ
1310 | zx::VmarFlags::ALLOW_FAULTS
1311 | zx::VmarFlags::REQUIRE_NON_RESIZABLE;
1312 let map_addr = fuchsia_runtime::vmar_root_self()
1313 .map(0, status_vmo, 0, size_of::<TestSeLinuxStatusT>(), flags)
1314 .unwrap();
1315 #[allow(
1316 clippy::undocumented_unsafe_blocks,
1317 reason = "Force documented unsafe blocks in Starnix"
1318 )]
1319 let mapped_status = unsafe { &mut *(map_addr as *mut TestSeLinuxStatusT) };
1320 let result = do_test(mapped_status);
1321 #[allow(
1322 clippy::undocumented_unsafe_blocks,
1323 reason = "Force documented unsafe blocks in Starnix"
1324 )]
1325 unsafe {
1326 fuchsia_runtime::vmar_root_self()
1327 .unmap(map_addr, size_of::<TestSeLinuxStatusT>())
1328 .unwrap()
1329 };
1330 result
1331 }
1332
1333 #[fuchsia::test]
1334 fn status_file_layout() {
1335 let security_server = SecurityServer::new_default();
1336 let status_holder = StatusPublisher::new_default().unwrap();
1337 let status_vmo = status_holder.0.get_readonly_vmo();
1338 security_server.set_status_publisher(Box::new(status_holder));
1339 security_server.set_enforcing(false);
1340 let mut seq_no: u32 = 0;
1341 with_status_t(&status_vmo, |status| {
1342 assert_eq!(status.version, SELINUX_STATUS_VERSION);
1343 assert_eq!(status.enforcing, 0);
1344 seq_no = status.sequence;
1345 assert_eq!(seq_no % 2, 0);
1346 });
1347 security_server.set_enforcing(true);
1348 with_status_t(&status_vmo, |status| {
1349 assert_eq!(status.version, SELINUX_STATUS_VERSION);
1350 assert_eq!(status.enforcing, 1);
1351 assert_ne!(status.sequence, seq_no);
1352 seq_no = status.sequence;
1353 assert_eq!(seq_no % 2, 0);
1354 });
1355 }
1356
1357 #[fuchsia::test]
1358 fn status_accurate_directly_following_set_status_publisher() {
1359 let security_server = SecurityServer::new_default();
1360 let status_holder = StatusPublisher::new_default().unwrap();
1361 let status_vmo = status_holder.0.get_readonly_vmo();
1362
1363 assert_eq!(false, security_server.is_enforcing());
1366 security_server.set_enforcing(true);
1367
1368 security_server.set_status_publisher(Box::new(status_holder));
1369 with_status_t(&status_vmo, |status| {
1370 assert_eq!(status.enforcing, 1);
1373 });
1374 }
1375}