Skip to main content

starnix_modules_selinuxfs/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#![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
50/// The version of the SELinux "status" file this implementation implements.
51const SELINUX_STATUS_VERSION: u32 = 1;
52
53/// Header of the C-style struct exposed via the /sys/fs/selinux/status file,
54/// to userspace. Defined here (instead of imported through bindgen) as selinux
55/// headers are not exposed through  kernel uapi headers.
56#[derive(IntoBytes, Copy, Clone, Immutable)]
57#[repr(C, align(4))]
58struct SeLinuxStatusHeader {
59    /// Version number of this structure (1).
60    version: u32,
61}
62
63impl Default for SeLinuxStatusHeader {
64    fn default() -> Self {
65        Self { version: SELINUX_STATUS_VERSION }
66    }
67}
68
69/// Value part of the C-style struct exposed via the /sys/fs/selinux/status file,
70/// to userspace. Defined here (instead of imported through bindgen) as selinux
71/// headers are not exposed through  kernel uapi headers.
72#[derive(IntoBytes, Copy, Clone, Default, Immutable)]
73#[repr(C, align(4))]
74struct SeLinuxStatusValue {
75    /// `0` means permissive mode, `1` means enforcing mode.
76    enforcing: u32,
77    /// The number of times the selinux policy has been reloaded.
78    policyload: u32,
79    /// `0` means allow and `1` means deny unknown object classes/permissions.
80    deny_unknown: u32,
81}
82
83// SAFETY: `SeLinuxStatusValue` can be safely written to shared memory in 4-byte chunks
84// because it is composed solely of u32s. It does not include an inline sequence lock.
85unsafe 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
122/// Implements the /sys/fs/selinux filesystem, as documented in the SELinux
123/// Notebook at
124/// https://github.com/SELinuxProject/selinux-notebook/blob/main/src/lsm_selinux.md#selinux-filesystem
125impl SeLinuxFs {
126    fn new_fs(
127        current_task: &CurrentTask,
128        options: FileSystemOptions,
129    ) -> Result<FileSystemHandle, Errno> {
130        // If SELinux is not enabled then the "selinuxfs" file system does not exist.
131        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        // Read-only files & directories, exposing SELinux internal state.
141        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        // The status file needs to be mmap-able, so use a VMO-backed file. When the selinux state
187        // changes in the future, the way to update this data (and communicate updates with
188        // userspace) is to use the ["seqlock"](https://en.wikipedia.org/wiki/Seqlock) technique.
189        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        // Write-only files used to configure and query SELinux.
203        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        // Read/write files allowing values to be queried or changed.
221        dir.entry("booleans", BooleansDirectory::new(security_server.clone()), mode!(IFDIR, 0o555));
222        // TODO(b/297313229): Get mode from the container.
223        dir.entry("enforce", EnforceApi::new_node(security_server), mode!(IFREG, 0o644));
224
225        // "/dev/null" equivalent used for file descriptors redirected by SELinux.
226        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        // Initialize selinux kernel state to store a copy of "/sys/fs/selinux/null" for use in
233        // hooks that redirect file descriptors to null. This has the side-effect of applying the
234        // policy-defined "devnull" SID to the `null_fs_node`.
235        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
247/// "load" API, accepting a binary policy in a single `write()` operation, which must be at seek
248/// position zero.
249struct 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        // Allow one-time initialization of state that requires a loaded policy.
271        security::selinuxfs_policy_loaded(current_task);
272
273        Ok(())
274    }
275}
276
277/// "policy" file, which allows the currently-loaded binary policy, to be read as a normal file,
278/// including supporting seek-aware reads.
279struct 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
328/// "enforce" API used to control whether SELinux is globally permissive, versus enforcing.
329struct 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        // Callers may write any number of times to this API, so long as the `data` is valid.
346        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
356/// "deny_unknown" file which exposes how classes & permissions not defined by the policy should
357/// be allowed or denied.
358struct 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
374/// "reject_unknown" file which exposes whether kernel classes & permissions not defined by the
375/// policy would have prevented the policy being loaded.
376struct 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
392/// "create" API used to determine the Security Context to associate with a new resource instance
393/// based on source, target, and target class.
394struct 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            // The "create" API can be written-to at most once.
415            return error!(EBUSY);
416        }
417
418        let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
419
420        // Requests consist of three mandatory space-separated elements.
421        let mut parts = data.split_whitespace();
422
423        // <scontext>: describes the subject that is creating the new object.
424        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        // <tcontext>: describes the target (e.g. parent directory) of the create operation.
431        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        // <tclass>: the policy-specific Id of the created object's class, as a decimal integer.
438        // Class Ids are obtained via lookups in the SELinuxFS "class" directory.
439        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        // Optional <name>: the final element of the path of the newly-created object. This allows
444        // filename-dependent transition rules to be applied to the computation.
445        let tname = parts.next().map(str::as_bytes).unwrap_or(&[]);
446
447        // There must be no further trailing arguments.
448        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
471/// "member" API used to determine the Security Context to associate with a new resource instance
472/// based on source, target, and target class and `type_member` rules.
473struct 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
494/// "relabel" API used to determine the Security Context to associate with a new resource instance
495/// based on source, target, and target class and `type_change` rules.
496struct 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
517/// "user" API used to perform a user decision.
518struct 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        // Linux v6.4 removed support for enabling "checkreqprot", rendering writes to the node a
554        // no-op.
555        error!(ENOTSUP)
556    }
557
558    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
559        Ok(b"0".into())
560    }
561}
562
563/// "context" API which accepts a Security Context in a single `write()` operation, and validates
564/// it against the loaded policy. If the Context is invalid then the `write()` returns `EINVAL`,
565/// otherwise the Context may be read back from the file.
566struct ContextApi {
567    security_server: Arc<SecurityServer>,
568    // Holds the SID representing the Security Context that the caller wrote to the file.
569    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        // If this instance was already written-to then fail the operation.
587        let mut context_sid = self.context_sid.lock();
588        if context_sid.is_some() {
589            return error!(EBUSY);
590        }
591
592        // Validate that the `data` describe valid user, role, type, etc by attempting to create
593        // a SID from it.
594        *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        // Read returns the Security Context the caller previously wrote to the file, normalized
605        // as a consequence of the Context->SID->Context round-trip. If no valid Context had been
606        // written by the caller, then this API file behaves as though empty.
607        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
615/// Implements an entry within the "initial_contexts" directory.
616struct 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            // Looking up an initial SID can only fail if no policy is loaded, in
634            // which case the file contains the name of the initial SID, rather
635            // than a Security Context value.
636            Ok(self.initial_sid.name().as_bytes().into())
637        }
638    }
639}
640
641/// An entry in the "policy_capabilities" directory. There is one entry for each policy capability
642/// supported by the kernel implementation, with the content indicating whether the capability is
643/// enabled or disabled by the loaded policy.
644struct 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
665/// Extends a calculated `AccessDecision` with an additional permission set describing which
666/// permissions were actually `decided` - all other permissions in the `AccessDecision` structure
667/// should be assumed to be un-`decided`. This allows the "access" API to return partial results, to
668/// force userspace to re-query the API if any un-`decided` permission is later requested.
669struct AccessDecisionAndDecided {
670    decision: AccessDecision,
671    decided: AccessVector,
672}
673
674struct AccessApi {
675    security_server: Arc<SecurityServer>,
676    result: OnceLock<AccessDecisionAndDecided>,
677
678    // Required to support audit-logging of requests granted via `todo_deny` exceptions.
679    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            // The "access" API can be written-to at most once.
703            return error!(EBUSY);
704        }
705
706        let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
707
708        // Requests consist of three mandatory space-separated elements, and one optional element.
709        let mut parts = data.split_whitespace();
710
711        // <scontext>: describes the subject acting on the class.
712        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        // <tcontext>: describes the target (e.g. parent directory) of the operation.
719        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        // <tclass>: the policy-specific Id of the target class, as a decimal integer.
726        // Class Ids are obtained via lookups in the SELinuxFS "class" directory.
727        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        // <request>: the set of permissions that the caller requests.
732        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        // This API does not appear to treat trailing arguments as invalid.
739
740        // Perform the access decision calculation.
741        let mut decision =
742            self.security_server.compute_access_decision_raw(scontext, tcontext, tclass);
743
744        // `compute_access_decision()` returns an `AccessDecision` with results calculated for all
745        // permissions defined by policy, so by default the "access" API reports all permissions as
746        // having been `decided`.
747        let mut decided = AccessVector::ALL;
748
749        // If there is a `todo_bug` associated with the decision then grant all permissions and
750        // make a best-effort attempt to emit a log for missing permissions.
751        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                // One or more requested permissions would be denied, and the denial audit-logged,
762                // so emit a track-stub report and a description of the request and result.
763                // Leave all permissions `decided`, so that only the first such failure is audited.
764                __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                // All requested permissions were granted. To allow "todo_deny" logs and track-stub
776                // tracking of permissions that would otherwise be denied & audited, remove those
777                // permissions from the `decided` set, to signal that the userspace AVC should re-
778                // query rather than using these cached results.
779                decided -= audited_denied;
780            }
781
782            // Grant all permissions in the returned result, so that clients that ignore the
783            // `decided` set will still have the allowance applied to all permissions.
784            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        // TODO: https://fxbug.dev/574857992 - `seqno` should reflect the policy revision from
805        // which the result was calculated, to allow the client to re-try if the policy changed.
806        const SEQNO: u32 = 1;
807
808        // Result format is: allowed decided auditallow auditdeny seqno flags
809        // Everything but seqno must be in hexadecimal format and represents a bits field.
810        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        // `emit_dotdot()` provides the first two directory entries, so that the entries for
886        // the conditional booleans start from offset 2.
887        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                /* next offset = */ 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        // Each boolean has a current active value, and a pending value that
920        // will become active if "commit_pending_booleans" is written to.
921        // e.g. "1 0" will be read if a boolean is True but will become False.
922        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        // "commit_pending_booleans" expects a numeric argument, which is
947        // interpreted as a boolean, with the pending booleans committed if the
948        // value is true (i.e. non-zero).
949        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    /// Returns the set of classes under the "class" directory.
972    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
1016/// Represents the perms/ directory under each class entry of the SeLinuxClassDirectory.
1017struct 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    /// Lists all available permissions for the corresponding class.
1032    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
1074/// Exposes AVC cache statistics from the SELinux security server to userspace.
1075struct 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
1097/// File node implementation tailored to the behaviour of the APIs exposed to userspace via the
1098/// SELinux filesystem. These API files share some unusual behaviours:
1099///
1100/// (1) Seek Position:
1101/// API files in the SELinux filesystem do have persistent seek offsets, but asymmetric behaviour
1102/// for read and write operations:
1103/// - Read operations respect the file offset, and increment it.
1104/// - Write operations do not increment the file offset. This is important for APIs such as
1105///   "create", which are used by `write()`ing a query and then `read()`ing the resulting value,
1106///   since otherwise the `read()` would start from the end of the `write()`.
1107///
1108/// API files do not handle non-zero offset `write()`s consistently. Some, (e.g. "context"), ignore
1109/// the offset, while others (e.g. "load") will fail with `EINVAL` if it is non-zero.
1110///
1111/// (2) Single vs Multi-Request:
1112/// Most API files may be `read()` from any number of times, but only support a single `write()`
1113/// operation. Attempting to `write()` a second time will return `EBUSY`.
1114///
1115/// (3) Error Handling:
1116/// Once an operation on an API file has failed, all subsequent operations on that file will
1117/// also fail, with the same error code.  e.g. Attempting multiple `write()` operations will
1118/// return `EBUSY` from the second and subsequent calls, but subsequent calls to `read()`,
1119/// `seek()` etc will also return `EBUSY`.
1120///
1121/// This helper currently implements asymmetric seek behaviour, and permission checks on write
1122/// operations.
1123struct SeLinuxApi<T: SeLinuxApiOps + Sync + Send + 'static> {
1124    ops: T,
1125}
1126
1127impl<T: SeLinuxApiOps + Sync + Send + 'static> SeLinuxApi<T> {
1128    /// Returns a new `SeLinuxApi` file node that will use `create_ops` to create a new `SeLinuxApiOps`
1129    /// instance every time a caller opens the file.
1130    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
1138/// Trait implemented for each SELinux API file (e.g. "create", "load") to define its behaviour.
1139trait SeLinuxApiOps {
1140    /// Returns the "security" class permission that is required in order to write to the API file.
1141    fn api_write_permission() -> SecurityPermission;
1142
1143    /// Returns true if writes ignore the seek offset, rather than requiring it to be zero.
1144    fn api_write_ignores_offset() -> bool {
1145        false
1146    }
1147
1148    /// Processes a request written to an API file.
1149    fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
1150        error!(EINVAL)
1151    }
1152
1153    /// Returns the complete contents of this API file.
1154    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
1155        error!(EINVAL)
1156    }
1157
1158    /// Variant of `api_write()` that additionally receives the `current_task`.
1159    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
1201/// Returns the "selinuxfs" file system, used by the system userspace to administer SELinux.
1202pub 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        // The current version of the "status" file contains five packed
1226        // u32 values.
1227        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        // Verify the content and actual size of the structure are as expected.
1233        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        // Ensure the returned handle is read-only and non-resizable.
1239        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        // Ensure a change in status-visible security server state is made before invoking
1316        // `set_status_publisher()`.
1317        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            // Ensure latest `enforcing` state is reported immediately following
1323            // `set_status_publisher()`.
1324            assert_eq!(status.enforcing, 1);
1325        });
1326    }
1327}