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