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