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