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