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().map(str::as_bytes).unwrap_or(&[]);
447
448 if parts.next().is_some() {
450 return error!(EINVAL);
451 }
452
453 let result = self
454 .security_server
455 .compute_create_sid_raw(scontext, tcontext, tclass, tname)
456 .map_err(|_| errno!(EINVAL))?;
457 self.result.set(result).map_err(|_| errno!(EINVAL))?;
458
459 Ok(())
460 }
461
462 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
463 let maybe_context = self
464 .result
465 .get()
466 .map(|sid| self.security_server.sid_to_security_context_with_nul(*sid).unwrap());
467 let context = maybe_context.unwrap_or_else(|| Vec::new());
468 Ok(context.into())
469 }
470}
471
472struct MemberApi;
475
476impl MemberApi {
477 fn new_node() -> impl FsNodeOps {
478 SeLinuxApi::new_node(|| Ok(Self {}))
479 }
480}
481
482impl SeLinuxApiOps for MemberApi {
483 fn api_write_permission() -> SecurityPermission {
484 SecurityPermission::ComputeMember
485 }
486 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
487 track_stub!(TODO("https://fxbug.dev/399069170"), "selinux member");
488 error!(ENOTSUP)
489 }
490 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
491 error!(ENOTSUP)
492 }
493}
494
495struct RelabelApi;
498
499impl RelabelApi {
500 fn new_node() -> impl FsNodeOps {
501 SeLinuxApi::new_node(|| Ok(Self {}))
502 }
503}
504
505impl SeLinuxApiOps for RelabelApi {
506 fn api_write_permission() -> SecurityPermission {
507 SecurityPermission::ComputeRelabel
508 }
509 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
510 track_stub!(TODO("https://fxbug.dev/399069766"), "selinux relabel");
511 error!(ENOTSUP)
512 }
513 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
514 error!(ENOTSUP)
515 }
516}
517
518struct UserApi;
520
521impl UserApi {
522 fn new_node() -> impl FsNodeOps {
523 SeLinuxApi::new_node(|| Ok(Self {}))
524 }
525}
526
527impl SeLinuxApiOps for UserApi {
528 fn api_write_permission() -> SecurityPermission {
529 SecurityPermission::ComputeUser
530 }
531 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
532 track_stub!(TODO("https://fxbug.dev/411433214"), "selinux user");
533 error!(ENOTSUP)
534 }
535 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
536 error!(ENOTSUP)
537 }
538}
539
540struct CheckReqProtApi;
541
542impl CheckReqProtApi {
543 fn new_node() -> impl FsNodeOps {
544 SeLinuxApi::new_node(|| Ok(Self {}))
545 }
546}
547
548impl SeLinuxApiOps for CheckReqProtApi {
549 fn api_write_permission() -> SecurityPermission {
550 SecurityPermission::SetCheckReqProt
551 }
552
553 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
554 error!(ENOTSUP)
557 }
558
559 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
560 Ok(b"0".into())
561 }
562}
563
564struct ContextApi {
568 security_server: Arc<SecurityServer>,
569 context_sid: LockDepMutex<Option<SecurityId>, SeLinuxFsContextSidLock>,
571}
572
573impl ContextApi {
574 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
575 SeLinuxApi::new_node(move || {
576 Ok(Self { security_server: security_server.clone(), context_sid: Default::default() })
577 })
578 }
579}
580
581impl SeLinuxApiOps for ContextApi {
582 fn api_write_permission() -> SecurityPermission {
583 SecurityPermission::CheckContext
584 }
585
586 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
587 let mut context_sid = self.context_sid.lock();
589 if context_sid.is_some() {
590 return error!(EBUSY);
591 }
592
593 *context_sid = Some(
596 self.security_server
597 .security_context_to_sid(data.as_slice().into())
598 .map_err(|_| errno!(EINVAL))?,
599 );
600
601 Ok(())
602 }
603
604 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
605 let maybe_sid = *self.context_sid.lock();
611 let result = maybe_sid
612 .and_then(|sid| self.security_server.sid_to_security_context_with_nul(sid))
613 .unwrap_or_default();
614 Ok(result.into())
615 }
616}
617
618struct InitialContextFile {
620 security_server: Arc<SecurityServer>,
621 initial_sid: InitialSid,
622}
623
624impl InitialContextFile {
625 fn new_node(security_server: Arc<SecurityServer>, initial_sid: InitialSid) -> impl FsNodeOps {
626 BytesFile::new_node(Self { security_server, initial_sid })
627 }
628}
629
630impl BytesFileOps for InitialContextFile {
631 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
632 let sid = self.initial_sid.into();
633 if let Some(context) = self.security_server.sid_to_security_context_with_nul(sid) {
634 Ok(context.into())
635 } else {
636 Ok(self.initial_sid.name().as_bytes().into())
640 }
641 }
642}
643
644struct PolicyCapFile {
648 security_server: Arc<SecurityServer>,
649 policy_cap: PolicyCap,
650}
651
652impl PolicyCapFile {
653 fn new_node(security_server: Arc<SecurityServer>, initial_sid: PolicyCap) -> impl FsNodeOps {
654 BytesFile::new_node(Self { security_server, policy_cap: initial_sid })
655 }
656}
657
658impl BytesFileOps for PolicyCapFile {
659 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
660 if self.security_server.is_policycap_enabled(self.policy_cap) {
661 Ok(b"1".into())
662 } else {
663 Ok(b"0".into())
664 }
665 }
666}
667
668struct AccessDecisionAndDecided {
673 decision: AccessDecision,
674 decided: AccessVector,
675}
676
677struct AccessApi {
678 security_server: Arc<SecurityServer>,
679 result: OnceLock<AccessDecisionAndDecided>,
680
681 kernel: Weak<Kernel>,
683}
684
685impl AccessApi {
686 fn new_node(security_server: Arc<SecurityServer>, kernel: &Arc<Kernel>) -> impl FsNodeOps {
687 let kernel = Arc::downgrade(kernel);
688 SeLinuxApi::new_node(move || {
689 Ok(Self {
690 security_server: security_server.clone(),
691 result: OnceLock::default(),
692 kernel: kernel.clone(),
693 })
694 })
695 }
696}
697
698impl SeLinuxApiOps for AccessApi {
699 fn api_write_permission() -> SecurityPermission {
700 SecurityPermission::ComputeAv
701 }
702
703 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
704 if self.result.get().is_some() {
705 return error!(EBUSY);
707 }
708
709 let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
710
711 let mut parts = data.split_whitespace();
713
714 let scontext_str = parts.next().ok_or_else(|| errno!(EINVAL))?;
716 let scontext = self
717 .security_server
718 .security_context_to_sid(scontext_str.into())
719 .map_err(|_| errno!(EINVAL))?;
720
721 let tcontext_str = parts.next().ok_or_else(|| errno!(EINVAL))?;
723 let tcontext = self
724 .security_server
725 .security_context_to_sid(tcontext_str.into())
726 .map_err(|_| errno!(EINVAL))?;
727
728 let tclass = parts.next().ok_or_else(|| errno!(EINVAL))?;
731 let tclass_id = u32::from_str(tclass).map_err(|_| errno!(EINVAL))?;
732 let tclass = ClassId::from_u32(tclass_id).ok_or_else(|| errno!(EINVAL))?.into();
733
734 let requested = if let Some(requested) = parts.next() {
736 AccessVector::from_str(requested).map_err(|_| errno!(EINVAL))?
737 } else {
738 AccessVector::ALL
739 };
740
741 let mut decision =
745 self.security_server.compute_access_decision_raw(scontext, tcontext, tclass);
746
747 let mut decided = AccessVector::ALL;
751
752 let Some(kernel) = self.kernel.upgrade() else {
755 return error!(EINVAL);
756 };
757 if let Some(todo_bug) = decision.todo_bug {
758 let denied = AccessVector::ALL - decision.allow;
759 let audited_denied = denied & decision.auditdeny;
760
761 let requested_has_audited_denial = audited_denied & requested != AccessVector::NONE;
762
763 if requested_has_audited_denial {
764 __track_stub_inner(
768 BugRef::from(NonZeroU64::new(todo_bug.get() as u64).unwrap()),
769 "Enforce SELinuxFS access API",
770 None,
771 std::panic::Location::caller(),
772 );
773 let audit_message = format!(
774 "avc: todo_deny {{ ACCESS_API }} bug={todo_bug} scontext={scontext_str:?} tcontext={tcontext_str:?} tclass={tclass_id} requested={requested:?}",
775 );
776 kernel.audit_logger().audit_log(AUDIT_AVC as u16, || audit_message);
777 } else {
778 decided -= audited_denied;
783 }
784
785 decision.allow = AccessVector::ALL;
788 }
789
790 self.result
791 .set(AccessDecisionAndDecided { decision, decided })
792 .map_err(|_| errno!(EINVAL))?;
793
794 Ok(())
795 }
796
797 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
798 let Some(AccessDecisionAndDecided { decision, decided }) = self.result.get() else {
799 return Ok(Vec::new().into());
800 };
801
802 let allowed = decision.allow;
803 let auditallow = decision.auditallow;
804 let auditdeny = decision.auditdeny;
805 let flags = decision.flags;
806
807 const SEQNO: u32 = 1;
810
811 let result =
814 format!("{allowed:x} {decided:x} {auditallow:x} {auditdeny:x} {SEQNO} {flags:x}");
815 Ok(result.into_bytes().into())
816 }
817}
818
819struct NullFileNode;
820
821impl FsNodeOps for NullFileNode {
822 fs_node_impl_not_dir!();
823
824 fn create_file_ops(
825 &self,
826 _node: &FsNode,
827 _current_task: &CurrentTask,
828 _flags: OpenFlags,
829 ) -> Result<Box<dyn FileOps>, Errno> {
830 Ok(Box::new(DevNull))
831 }
832}
833
834#[derive(Clone)]
835struct BooleansDirectory {
836 security_server: Arc<SecurityServer>,
837}
838
839impl BooleansDirectory {
840 fn new(security_server: Arc<SecurityServer>) -> Self {
841 Self { security_server }
842 }
843}
844
845impl FsNodeOps for BooleansDirectory {
846 fs_node_impl_dir_readonly!();
847
848 fn create_file_ops(
849 &self,
850 _node: &FsNode,
851 _current_task: &CurrentTask,
852 _flags: OpenFlags,
853 ) -> Result<Box<dyn FileOps>, Errno> {
854 Ok(Box::new(self.clone()))
855 }
856
857 fn lookup(
858 &self,
859 node: &FsNode,
860 current_task: &CurrentTask,
861 name: &FsStr,
862 ) -> Result<FsNodeHandle, Errno> {
863 let utf8_name = String::from_utf8(name.to_vec()).map_err(|_| errno!(ENOENT))?;
864 if self.security_server.conditional_booleans().contains(&utf8_name) {
865 Ok(node.fs().create_node_and_allocate_node_id(
866 BooleanFile::new_node(self.security_server.clone(), utf8_name),
867 FsNodeInfo::new(mode!(IFREG, 0o644), current_task.current_fscred()),
868 ))
869 } else {
870 error!(ENOENT)
871 }
872 }
873}
874
875impl FileOps for BooleansDirectory {
876 fileops_impl_directory!();
877 fileops_impl_noop_sync!();
878 fileops_impl_unbounded_seek!();
879
880 fn readdir(
881 &self,
882 file: &FileObject,
883 _current_task: &CurrentTask,
884 sink: &mut dyn DirentSink,
885 ) -> Result<(), Errno> {
886 emit_dotdot(file, sink)?;
887
888 let iter_offset = sink.offset() - 2;
891 for name in self.security_server.conditional_booleans().iter().skip(iter_offset as usize) {
892 sink.add(
893 file.fs.allocate_ino(),
894 sink.offset() + 1,
895 DirectoryEntryType::REG,
896 FsString::from(name.as_bytes()).as_ref(),
897 )?;
898 }
899
900 Ok(())
901 }
902}
903
904struct BooleanFile {
905 security_server: Arc<SecurityServer>,
906 name: String,
907}
908
909impl BooleanFile {
910 fn new_node(security_server: Arc<SecurityServer>, name: String) -> impl FsNodeOps {
911 BytesFile::new_node(BooleanFile { security_server, name })
912 }
913}
914
915impl BytesFileOps for BooleanFile {
916 fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
917 let value = parse_unsigned_file::<u32>(&data)? != 0;
918 self.security_server.set_pending_boolean(&self.name, value).map_err(|_| errno!(EIO))
919 }
920
921 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
922 let (active, pending) =
926 self.security_server.get_boolean(&self.name).map_err(|_| errno!(EIO))?;
927 Ok(format!("{} {}", active as u32, pending as u32).into_bytes().into())
928 }
929}
930
931struct CommitBooleansApi {
932 security_server: Arc<SecurityServer>,
933}
934
935impl CommitBooleansApi {
936 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
937 SeLinuxApi::new_node(move || {
938 Ok(CommitBooleansApi { security_server: security_server.clone() })
939 })
940 }
941}
942
943impl SeLinuxApiOps for CommitBooleansApi {
944 fn api_write_permission() -> SecurityPermission {
945 SecurityPermission::SetBool
946 }
947
948 fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
949 let commit = parse_unsigned_file::<u32>(&data)? != 0;
953
954 if commit {
955 self.security_server.commit_pending_booleans();
956 }
957 Ok(())
958 }
959}
960
961struct ClassDirectory {
962 security_server: Arc<SecurityServer>,
963}
964
965impl ClassDirectory {
966 fn new(security_server: Arc<SecurityServer>) -> Self {
967 Self { security_server }
968 }
969}
970
971impl FsNodeOps for ClassDirectory {
972 fs_node_impl_dir_readonly!();
973
974 fn create_file_ops(
976 &self,
977 _node: &FsNode,
978 _current_task: &CurrentTask,
979 _flags: OpenFlags,
980 ) -> Result<Box<dyn FileOps>, Errno> {
981 Ok(VecDirectory::new_file(
982 self.security_server
983 .class_names()
984 .map_err(|_| errno!(ENOENT))?
985 .iter()
986 .map(|class_name| VecDirectoryEntry {
987 entry_type: DirectoryEntryType::DIR,
988 name: class_name.clone().into(),
989 inode: None,
990 })
991 .collect(),
992 ))
993 }
994
995 fn lookup(
996 &self,
997 node: &FsNode,
998 _current_task: &CurrentTask,
999 name: &FsStr,
1000 ) -> Result<FsNodeHandle, Errno> {
1001 let id: u32 = self
1002 .security_server
1003 .class_id_by_name(&name.to_string())
1004 .map_err(|_| errno!(EINVAL))?
1005 .into();
1006
1007 let fs = node.fs();
1008 let dir = SimpleDirectory::new();
1009 dir.edit(&fs, |dir| {
1010 let index_bytes = format!("{}", id).into_bytes();
1011 dir.entry("index", BytesFile::new_node(index_bytes), mode!(IFREG, 0o444));
1012 dir.entry(
1013 "perms",
1014 PermsDirectory::new(self.security_server.clone(), name.to_string()),
1015 mode!(IFDIR, 0o555),
1016 );
1017 });
1018 Ok(dir.into_node(&fs, 0o555))
1019 }
1020}
1021
1022struct PermsDirectory {
1024 security_server: Arc<SecurityServer>,
1025 class_name: String,
1026}
1027
1028impl PermsDirectory {
1029 fn new(security_server: Arc<SecurityServer>, class_name: String) -> Self {
1030 Self { security_server, class_name }
1031 }
1032}
1033
1034impl FsNodeOps for PermsDirectory {
1035 fs_node_impl_dir_readonly!();
1036
1037 fn create_file_ops(
1039 &self,
1040 _node: &FsNode,
1041 _current_task: &CurrentTask,
1042 _flags: OpenFlags,
1043 ) -> Result<Box<dyn FileOps>, Errno> {
1044 Ok(VecDirectory::new_file(
1045 self.security_server
1046 .class_permissions_by_name(&self.class_name)
1047 .map_err(|_| errno!(ENOENT))?
1048 .iter()
1049 .map(|(_permission_id, permission_name)| VecDirectoryEntry {
1050 entry_type: DirectoryEntryType::DIR,
1051 name: permission_name.clone().into(),
1052 inode: None,
1053 })
1054 .collect(),
1055 ))
1056 }
1057
1058 fn lookup(
1059 &self,
1060 node: &FsNode,
1061 current_task: &CurrentTask,
1062 name: &FsStr,
1063 ) -> Result<FsNodeHandle, Errno> {
1064 let found_permission_id = self
1065 .security_server
1066 .class_permissions_by_name(&(self.class_name))
1067 .map_err(|_| errno!(ENOENT))?
1068 .iter()
1069 .find(|(_permission_id, permission_name)| permission_name == name)
1070 .ok_or_else(|| errno!(ENOENT))?
1071 .0;
1072
1073 Ok(node.fs().create_node_and_allocate_node_id(
1074 BytesFile::new_node(format!("{}", found_permission_id).into_bytes()),
1075 FsNodeInfo::new(mode!(IFREG, 0o444), current_task.current_fscred()),
1076 ))
1077 }
1078}
1079
1080struct AvcCacheStatsFile {
1082 security_server: Arc<SecurityServer>,
1083}
1084
1085impl AvcCacheStatsFile {
1086 fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
1087 BytesFile::new_node(Self { security_server })
1088 }
1089}
1090
1091impl BytesFileOps for AvcCacheStatsFile {
1092 fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1093 let stats = self.security_server.avc_cache_stats();
1094 Ok(format!(
1095 "lookups hits misses allocations reclaims frees\n{} {} {} {} {} {}\n",
1096 stats.lookups, stats.hits, stats.misses, stats.allocs, stats.reclaims, stats.frees
1097 )
1098 .into_bytes()
1099 .into())
1100 }
1101}
1102
1103struct SeLinuxApi<T: SeLinuxApiOps + Sync + Send + 'static> {
1130 ops: T,
1131}
1132
1133impl<T: SeLinuxApiOps + Sync + Send + 'static> SeLinuxApi<T> {
1134 fn new_node<F>(create_ops: F) -> impl FsNodeOps
1137 where
1138 F: Fn() -> Result<T, Errno> + Send + Sync + 'static,
1139 {
1140 SimpleFileNode::new(move |_| create_ops().map(|ops| SeLinuxApi { ops }))
1141 }
1142}
1143
1144trait SeLinuxApiOps {
1146 fn api_write_permission() -> SecurityPermission;
1148
1149 fn api_write_ignores_offset() -> bool {
1151 false
1152 }
1153
1154 fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
1156 error!(EINVAL)
1157 }
1158
1159 fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
1161 error!(EINVAL)
1162 }
1163
1164 fn api_write_with_task(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
1166 self.api_write(data)
1167 }
1168}
1169
1170impl<T: SeLinuxApiOps + Sync + Send + 'static> FileOps for SeLinuxApi<T> {
1171 fileops_impl_seekable!();
1172 fileops_impl_noop_sync!();
1173
1174 fn writes_update_seek_offset(&self) -> bool {
1175 false
1176 }
1177
1178 fn read(
1179 &self,
1180 _file: &FileObject,
1181 _current_task: &CurrentTask,
1182 offset: usize,
1183 data: &mut dyn OutputBuffer,
1184 ) -> Result<usize, Errno> {
1185 let response = self.ops.api_read()?;
1186 data.write(&response[offset..])
1187 }
1188
1189 fn write(
1190 &self,
1191 _file: &FileObject,
1192 current_task: &CurrentTask,
1193 offset: usize,
1194 data: &mut dyn InputBuffer,
1195 ) -> Result<usize, Errno> {
1196 if offset != 0 && !T::api_write_ignores_offset() {
1197 return error!(EINVAL);
1198 }
1199 security::selinuxfs_check_access(current_task, T::api_write_permission())?;
1200 let data = data.read_all()?;
1201 let data_len = data.len();
1202 self.ops.api_write_with_task(current_task, data)?;
1203 Ok(data_len)
1204 }
1205}
1206
1207pub fn selinux_fs(
1209 current_task: &CurrentTask,
1210 options: FileSystemOptions,
1211) -> Result<FileSystemHandle, Errno> {
1212 struct SeLinuxFsHandle(FileSystemHandle);
1213
1214 Ok(current_task
1215 .kernel()
1216 .expando
1217 .get_or_try_init(|| Ok(SeLinuxFsHandle(SeLinuxFs::new_fs(current_task, options)?)))?
1218 .0
1219 .clone())
1220}
1221
1222#[cfg(test)]
1223mod tests {
1224 use super::*;
1225 use fuchsia_runtime;
1226 use selinux::SecurityServer;
1227 use zerocopy::{FromBytes, KnownLayout};
1228
1229 #[fuchsia::test]
1230 fn status_vmo_has_correct_size_and_rights() {
1231 const STATUS_T_SIZE: usize = size_of::<u32>() * 5;
1234
1235 let status_holder = StatusPublisher::new_default().unwrap();
1236 let status_vmo = status_holder.0.get_readonly_vmo();
1237
1238 let content_size = status_vmo.get_content_size().unwrap() as usize;
1240 assert_eq!(content_size, STATUS_T_SIZE);
1241 let actual_size = status_vmo.get_size().unwrap() as usize;
1242 assert!(actual_size >= STATUS_T_SIZE);
1243
1244 let rights = status_vmo.basic_info().unwrap().rights;
1246 assert_eq!((rights & zx::Rights::MAP), zx::Rights::MAP);
1247 assert_eq!((rights & zx::Rights::READ), zx::Rights::READ);
1248 assert_eq!((rights & zx::Rights::GET_PROPERTY), zx::Rights::GET_PROPERTY);
1249 assert_eq!((rights & zx::Rights::WRITE), zx::Rights::NONE);
1250 assert_eq!((rights & zx::Rights::RESIZE), zx::Rights::NONE);
1251 }
1252
1253 #[derive(KnownLayout, FromBytes)]
1254 #[repr(C, align(4))]
1255 struct TestSeLinuxStatusT {
1256 version: u32,
1257 sequence: u32,
1258 enforcing: u32,
1259 policyload: u32,
1260 deny_unknown: u32,
1261 }
1262
1263 fn with_status_t<R>(
1264 status_vmo: &Arc<zx::Vmo>,
1265 do_test: impl FnOnce(&TestSeLinuxStatusT) -> R,
1266 ) -> R {
1267 let flags = zx::VmarFlags::PERM_READ
1268 | zx::VmarFlags::ALLOW_FAULTS
1269 | zx::VmarFlags::REQUIRE_NON_RESIZABLE;
1270 let map_addr = fuchsia_runtime::vmar_root_self()
1271 .map(0, status_vmo, 0, size_of::<TestSeLinuxStatusT>(), flags)
1272 .unwrap();
1273 #[allow(
1274 clippy::undocumented_unsafe_blocks,
1275 reason = "Force documented unsafe blocks in Starnix"
1276 )]
1277 let mapped_status = unsafe { &mut *(map_addr as *mut TestSeLinuxStatusT) };
1278 let result = do_test(mapped_status);
1279 #[allow(
1280 clippy::undocumented_unsafe_blocks,
1281 reason = "Force documented unsafe blocks in Starnix"
1282 )]
1283 unsafe {
1284 fuchsia_runtime::vmar_root_self()
1285 .unmap(map_addr, size_of::<TestSeLinuxStatusT>())
1286 .unwrap()
1287 };
1288 result
1289 }
1290
1291 #[fuchsia::test]
1292 fn status_file_layout() {
1293 let security_server = SecurityServer::new_default();
1294 let status_holder = StatusPublisher::new_default().unwrap();
1295 let status_vmo = status_holder.0.get_readonly_vmo();
1296 security_server.set_status_publisher(Box::new(status_holder));
1297 security_server.set_enforcing(false);
1298 let mut seq_no: u32 = 0;
1299 with_status_t(&status_vmo, |status| {
1300 assert_eq!(status.version, SELINUX_STATUS_VERSION);
1301 assert_eq!(status.enforcing, 0);
1302 seq_no = status.sequence;
1303 assert_eq!(seq_no % 2, 0);
1304 });
1305 security_server.set_enforcing(true);
1306 with_status_t(&status_vmo, |status| {
1307 assert_eq!(status.version, SELINUX_STATUS_VERSION);
1308 assert_eq!(status.enforcing, 1);
1309 assert_ne!(status.sequence, seq_no);
1310 seq_no = status.sequence;
1311 assert_eq!(seq_no % 2, 0);
1312 });
1313 }
1314
1315 #[fuchsia::test]
1316 fn status_accurate_directly_following_set_status_publisher() {
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
1321 assert_eq!(false, security_server.is_enforcing());
1324 security_server.set_enforcing(true);
1325
1326 security_server.set_status_publisher(Box::new(status_holder));
1327 with_status_t(&status_vmo, |status| {
1328 assert_eq!(status.enforcing, 1);
1331 });
1332 }
1333}