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 starnix_sync::LockEqualOrBefore;
8
9use seq_lock::{SeqLock, SeqLockable, WriteSize};
10
11use selinux::policy::metadata::POLICYDB_VERSION_MAX;
12use selinux::policy::{AccessDecision, AccessVector};
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, Locked, Mutex, 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::{NonZeroU32, NonZeroU64};
47use std::ops::Deref;
48use std::str::FromStr;
49use std::sync::{Arc, OnceLock, Weak};
50use strum::VariantArray as _;
51use zerocopy::{Immutable, IntoBytes};
52use zx::{self as zx, HandleBased as _};
53
54/// The version of the SELinux "status" file this implementation implements.
55const SELINUX_STATUS_VERSION: u32 = 1;
56
57/// Header of the C-style struct exposed via the /sys/fs/selinux/status file,
58/// to userspace. Defined here (instead of imported through bindgen) as selinux
59/// headers are not exposed through  kernel uapi headers.
60#[derive(IntoBytes, Copy, Clone, Immutable)]
61#[repr(C, align(4))]
62struct SeLinuxStatusHeader {
63    /// Version number of this structure (1).
64    version: u32,
65}
66
67impl Default for SeLinuxStatusHeader {
68    fn default() -> Self {
69        Self { version: SELINUX_STATUS_VERSION }
70    }
71}
72
73/// Value part of the C-style struct exposed via the /sys/fs/selinux/status file,
74/// to userspace. Defined here (instead of imported through bindgen) as selinux
75/// headers are not exposed through  kernel uapi headers.
76#[derive(IntoBytes, Copy, Clone, Default, Immutable)]
77#[repr(C, align(4))]
78struct SeLinuxStatusValue {
79    /// `0` means permissive mode, `1` means enforcing mode.
80    enforcing: u32,
81    /// The number of times the selinux policy has been reloaded.
82    policyload: u32,
83    /// `0` means allow and `1` means deny unknown object classes/permissions.
84    deny_unknown: u32,
85}
86
87// SAFETY: `SeLinuxStatusValue` can be safely written to shared memory in 4-byte chunks
88// because it is composed solely of u32s. It does not include an inline sequence lock.
89unsafe impl SeqLockable for SeLinuxStatusValue {
90    const WRITE_SIZE: WriteSize = WriteSize::Four;
91    const HAS_INLINE_SEQUENCE: bool = false;
92    const VMO_NAME: &'static [u8] = b"starnix:selinux";
93}
94
95type StatusSeqLock = SeqLock<SeLinuxStatusHeader, SeLinuxStatusValue>;
96
97struct StatusPublisher(StatusSeqLock);
98
99impl StatusPublisher {
100    pub fn new_default() -> Result<Self, zx::Status> {
101        let seq_lock = StatusSeqLock::new_default()?;
102        Ok(StatusPublisher(seq_lock))
103    }
104}
105
106impl SeLinuxStatusPublisher for StatusPublisher {
107    fn set_status(&mut self, policy_status: SeLinuxStatus) {
108        self.0.set_value(SeLinuxStatusValue {
109            enforcing: policy_status.is_enforcing as u32,
110            policyload: policy_status.change_count,
111            deny_unknown: policy_status.deny_unknown as u32,
112        })
113    }
114}
115
116struct SeLinuxFs;
117impl FileSystemOps for SeLinuxFs {
118    fn statfs(
119        &self,
120        _locked: &mut Locked<FileOpsCore>,
121        _fs: &FileSystem,
122        _current_task: &CurrentTask,
123    ) -> Result<statfs, Errno> {
124        Ok(default_statfs(SELINUX_MAGIC))
125    }
126    fn name(&self) -> &'static FsStr {
127        "selinuxfs".into()
128    }
129}
130
131/// Implements the /sys/fs/selinux filesystem, as documented in the SELinux
132/// Notebook at
133/// https://github.com/SELinuxProject/selinux-notebook/blob/main/src/lsm_selinux.md#selinux-filesystem
134impl SeLinuxFs {
135    fn new_fs<L>(
136        locked: &mut Locked<L>,
137        current_task: &CurrentTask,
138        options: FileSystemOptions,
139    ) -> Result<FileSystemHandle, Errno>
140    where
141        L: LockEqualOrBefore<FileOpsCore>,
142    {
143        // If SELinux is not enabled then the "selinuxfs" file system does not exist.
144        let security_server = security::selinuxfs_get_admin_api(current_task)
145            .ok_or_else(|| errno!(ENODEV, "selinuxfs"))?;
146
147        let kernel = current_task.kernel();
148        let fs = FileSystem::new(locked, kernel, CacheMode::Permanent, SeLinuxFs, options)?;
149        let root = SimpleDirectory::new();
150        fs.create_root(fs.allocate_ino(), root.clone());
151        let dir = SimpleDirectoryMutator::new(fs.clone(), root);
152
153        // Read-only files & directories, exposing SELinux internal state.
154        dir.subdir("avc", 0o555, |dir| {
155            dir.entry(
156                "cache_stats",
157                AvcCacheStatsFile::new_node(security_server.clone()),
158                mode!(IFREG, 0o444),
159            );
160        });
161        dir.entry("checkreqprot", CheckReqProtApi::new_node(), mode!(IFREG, 0o644));
162        dir.entry("class", ClassDirectory::new(security_server.clone()), mode!(IFDIR, 0o555));
163        dir.entry(
164            "deny_unknown",
165            DenyUnknownFile::new_node(security_server.clone()),
166            mode!(IFREG, 0o444),
167        );
168        dir.entry(
169            "reject_unknown",
170            RejectUnknownFile::new_node(security_server.clone()),
171            mode!(IFREG, 0o444),
172        );
173        dir.subdir("initial_contexts", 0o555, |dir| {
174            for initial_sid in InitialSid::all_variants() {
175                dir.entry(
176                    initial_sid.name(),
177                    InitialContextFile::new_node(security_server.clone(), *initial_sid),
178                    mode!(IFREG, 0o444),
179                );
180            }
181        });
182        dir.entry("mls", BytesFile::new_node(b"1".to_vec()), mode!(IFREG, 0o444));
183        dir.entry("policy", PolicyFile::new_node(security_server.clone()), mode!(IFREG, 0o600));
184        dir.subdir("policy_capabilities", 0o555, |dir| {
185            for capability in PolicyCap::VARIANTS {
186                dir.entry(
187                    capability.name(),
188                    PolicyCapFile::new_node(security_server.clone(), *capability),
189                    mode!(IFREG, 0o444),
190                );
191            }
192        });
193        dir.entry(
194            "policyvers",
195            BytesFile::new_node(format!("{}", POLICYDB_VERSION_MAX).into_bytes()),
196            mode!(IFREG, 0o444),
197        );
198
199        // The status file needs to be mmap-able, so use a VMO-backed file. When the selinux state
200        // changes in the future, the way to update this data (and communicate updates with
201        // userspace) is to use the ["seqlock"](https://en.wikipedia.org/wiki/Seqlock) technique.
202        let status_holder = StatusPublisher::new_default().expect("selinuxfs status seqlock");
203        let status_file = status_holder
204            .0
205            .get_readonly_vmo()
206            .duplicate_handle(zx::Rights::SAME_RIGHTS)
207            .map_err(impossible_error)?;
208        dir.entry(
209            "status",
210            MemoryRegularNode::from_memory(Arc::new(MemoryObject::from(status_file))),
211            mode!(IFREG, 0o444),
212        );
213        security_server.set_status_publisher(Box::new(status_holder));
214
215        // Write-only files used to configure and query SELinux.
216        dir.entry(
217            "access",
218            AccessApi::new_node(security_server.clone(), current_task.kernel()),
219            mode!(IFREG, 0o666),
220        );
221        dir.entry("context", ContextApi::new_node(security_server.clone()), mode!(IFREG, 0o666));
222        dir.entry("create", CreateApi::new_node(security_server.clone()), mode!(IFREG, 0o666));
223        dir.entry("member", MemberApi::new_node(), mode!(IFREG, 0o666));
224        dir.entry("relabel", RelabelApi::new_node(), mode!(IFREG, 0o666));
225        dir.entry("user", UserApi::new_node(), mode!(IFREG, 0o666));
226        dir.entry("load", LoadApi::new_node(security_server.clone()), mode!(IFREG, 0o600));
227        dir.entry(
228            "commit_pending_bools",
229            CommitBooleansApi::new_node(security_server.clone()),
230            mode!(IFREG, 0o200),
231        );
232
233        // Read/write files allowing values to be queried or changed.
234        dir.entry("booleans", BooleansDirectory::new(security_server.clone()), mode!(IFDIR, 0o555));
235        // TODO(b/297313229): Get mode from the container.
236        dir.entry("enforce", EnforceApi::new_node(security_server), mode!(IFREG, 0o644));
237
238        // "/dev/null" equivalent used for file descriptors redirected by SELinux.
239        let null_ops: Box<dyn FsNodeOps> = (NullFileNode).into();
240        let mut info = FsNodeInfo::new(mode!(IFCHR, 0o666), FsCred::root());
241        info.rdev = DeviceId::NULL;
242        let null_fs_node = fs.create_node_and_allocate_node_id(null_ops, info);
243        dir.node("null".into(), null_fs_node.clone());
244
245        // Initialize selinux kernel state to store a copy of "/sys/fs/selinux/null" for use in
246        // hooks that redirect file descriptors to null. This has the side-effect of applying the
247        // policy-defined "devnull" SID to the `null_fs_node`.
248        let null_ops: Box<dyn FileOps> = Box::new(DevNull);
249        let null_flags = OpenFlags::empty();
250        let null_name =
251            NamespaceNode::new_anonymous(DirEntry::new(null_fs_node, None, "null".into()));
252        let null_file_object =
253            FileObject::new(locked, current_task, null_ops, null_name, null_flags)
254                .expect("create file object for just-created selinuxfs/null");
255        security::selinuxfs_init_null(current_task, &null_file_object);
256
257        Ok(fs)
258    }
259}
260
261/// "load" API, accepting a binary policy in a single `write()` operation, which must be at seek
262/// position zero.
263struct LoadApi {
264    security_server: Arc<SecurityServer>,
265}
266
267impl LoadApi {
268    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
269        SeLinuxApi::new_node(move || Ok(Self { security_server: security_server.clone() }))
270    }
271}
272
273impl SeLinuxApiOps for LoadApi {
274    fn api_write_permission() -> SecurityPermission {
275        SecurityPermission::LoadPolicy
276    }
277    fn api_write_with_task(
278        &self,
279        locked: &mut Locked<FileOpsCore>,
280        current_task: &CurrentTask,
281        data: Vec<u8>,
282    ) -> Result<(), Errno> {
283        log_info!("Loading {} byte policy", data.len());
284        self.security_server.load_policy(data).map_err(|error| {
285            log_error!("Policy load error: {}", error);
286            errno!(EINVAL)
287        })?;
288
289        // Allow one-time initialization of state that requires a loaded policy.
290        security::selinuxfs_policy_loaded(locked, current_task);
291
292        Ok(())
293    }
294}
295
296/// "policy" file, which allows the currently-loaded binary policy, to be read as a normal file,
297/// including supporting seek-aware reads.
298struct PolicyFile {
299    security_server: Arc<SecurityServer>,
300}
301
302impl PolicyFile {
303    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
304        SimpleFileNode::new(move |_, _| Ok(Self { security_server: security_server.clone() }))
305    }
306}
307
308impl FileOps for PolicyFile {
309    fileops_impl_seekable!();
310    fileops_impl_noop_sync!();
311
312    fn read(
313        &self,
314        _locked: &mut Locked<FileOpsCore>,
315        _file: &FileObject,
316        _current_task: &CurrentTask,
317        offset: usize,
318        data: &mut dyn OutputBuffer,
319    ) -> Result<usize, Errno> {
320        let policy = self.security_server.get_binary_policy().ok_or_else(|| errno!(EINVAL))?;
321        let policy_bytes: &[u8] = policy.deref();
322
323        if offset >= policy_bytes.len() {
324            return Ok(0);
325        }
326
327        data.write(&policy_bytes[offset..])
328    }
329
330    fn write(
331        &self,
332        _locked: &mut Locked<FileOpsCore>,
333        _file: &FileObject,
334        _current_task: &CurrentTask,
335        _offset: usize,
336        _data: &mut dyn InputBuffer,
337    ) -> Result<usize, Errno> {
338        error!(EACCES)
339    }
340}
341
342/// "enforce" API used to control whether SELinux is globally permissive, versus enforcing.
343struct EnforceApi {
344    security_server: Arc<SecurityServer>,
345}
346
347impl EnforceApi {
348    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
349        SeLinuxApi::new_node(move || Ok(Self { security_server: security_server.clone() }))
350    }
351}
352
353impl SeLinuxApiOps for EnforceApi {
354    fn api_write_permission() -> SecurityPermission {
355        SecurityPermission::SetEnforce
356    }
357
358    fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
359        // Callers may write any number of times to this API, so long as the `data` is valid.
360        let enforce = parse_unsigned_file::<u32>(&data)? != 0;
361        self.security_server.set_enforcing(enforce);
362        Ok(())
363    }
364
365    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
366        Ok(self.security_server.is_enforcing().then_some(b"1").unwrap_or(b"0").into())
367    }
368}
369
370/// "deny_unknown" file which exposes how classes & permissions not defined by the policy should
371/// be allowed or denied.
372struct DenyUnknownFile {
373    security_server: Arc<SecurityServer>,
374}
375
376impl DenyUnknownFile {
377    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
378        BytesFile::new_node(Self { security_server })
379    }
380}
381
382impl BytesFileOps for DenyUnknownFile {
383    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
384        Ok(format!("{}", self.security_server.deny_unknown() as u32).into_bytes().into())
385    }
386}
387
388/// "reject_unknown" file which exposes whether kernel classes & permissions not defined by the
389/// policy would have prevented the policy being loaded.
390struct RejectUnknownFile {
391    security_server: Arc<SecurityServer>,
392}
393
394impl RejectUnknownFile {
395    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
396        BytesFile::new_node(Self { security_server })
397    }
398}
399
400impl BytesFileOps for RejectUnknownFile {
401    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
402        Ok(format!("{}", self.security_server.reject_unknown() as u32).into_bytes().into())
403    }
404}
405
406/// "create" API used to determine the Security Context to associate with a new resource instance
407/// based on source, target, and target class.
408struct CreateApi {
409    security_server: Arc<SecurityServer>,
410    result: OnceLock<SecurityId>,
411}
412
413impl CreateApi {
414    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
415        SeLinuxApi::new_node(move || {
416            Ok(Self { security_server: security_server.clone(), result: OnceLock::new() })
417        })
418    }
419}
420
421impl SeLinuxApiOps for CreateApi {
422    fn api_write_permission() -> SecurityPermission {
423        SecurityPermission::ComputeCreate
424    }
425
426    fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
427        if self.result.get().is_some() {
428            // The "create" API can be written-to at most once.
429            return error!(EBUSY);
430        }
431
432        let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
433
434        // Requests consist of three mandatory space-separated elements.
435        let mut parts = data.split_whitespace();
436
437        // <scontext>: describes the subject that is creating the new object.
438        let scontext = parts.next().ok_or_else(|| errno!(EINVAL))?;
439        let scontext = self
440            .security_server
441            .security_context_to_sid(scontext.into())
442            .map_err(|_| errno!(EINVAL))?;
443
444        // <tcontext>: describes the target (e.g. parent directory) of the create operation.
445        let tcontext = parts.next().ok_or_else(|| errno!(EINVAL))?;
446        let tcontext = self
447            .security_server
448            .security_context_to_sid(tcontext.into())
449            .map_err(|_| errno!(EINVAL))?;
450
451        // <tclass>: the policy-specific Id of the created object's class, as a decimal integer.
452        // Class Ids are obtained via lookups in the SELinuxFS "class" directory.
453        let tclass = parts.next().ok_or_else(|| errno!(EINVAL))?;
454        let tclass = u32::from_str(tclass).map_err(|_| errno!(EINVAL))?;
455        let tclass = ClassId::new(NonZeroU32::new(tclass).ok_or_else(|| errno!(EINVAL))?);
456
457        // Optional <name>: the final element of the path of the newly-created object. This allows
458        // filename-dependent transition rules to be applied to the computation.
459        let tname = parts.next();
460        if tname.is_some() {
461            track_stub!(TODO("https://fxbug.dev/361552580"), "selinux create with name");
462            return error!(ENOTSUP);
463        }
464
465        // There must be no further trailing arguments.
466        if parts.next().is_some() {
467            return error!(EINVAL);
468        }
469
470        let result = self
471            .security_server
472            .compute_create_sid_raw(scontext, tcontext, tclass)
473            .map_err(|_| errno!(EINVAL))?;
474        self.result.set(result).map_err(|_| errno!(EINVAL))?;
475
476        Ok(())
477    }
478
479    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
480        let maybe_context = self
481            .result
482            .get()
483            .map(|sid| self.security_server.sid_to_security_context_with_nul(*sid).unwrap());
484        let context = maybe_context.unwrap_or_else(|| Vec::new());
485        Ok(context.into())
486    }
487}
488
489/// "member" API used to determine the Security Context to associate with a new resource instance
490/// based on source, target, and target class and `type_member` rules.
491struct MemberApi;
492
493impl MemberApi {
494    fn new_node() -> impl FsNodeOps {
495        SeLinuxApi::new_node(|| Ok(Self {}))
496    }
497}
498
499impl SeLinuxApiOps for MemberApi {
500    fn api_write_permission() -> SecurityPermission {
501        SecurityPermission::ComputeMember
502    }
503    fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
504        track_stub!(TODO("https://fxbug.dev/399069170"), "selinux member");
505        error!(ENOTSUP)
506    }
507    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
508        error!(ENOTSUP)
509    }
510}
511
512/// "relabel" API used to determine the Security Context to associate with a new resource instance
513/// based on source, target, and target class and `type_change` rules.
514struct RelabelApi;
515
516impl RelabelApi {
517    fn new_node() -> impl FsNodeOps {
518        SeLinuxApi::new_node(|| Ok(Self {}))
519    }
520}
521
522impl SeLinuxApiOps for RelabelApi {
523    fn api_write_permission() -> SecurityPermission {
524        SecurityPermission::ComputeRelabel
525    }
526    fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
527        track_stub!(TODO("https://fxbug.dev/399069766"), "selinux relabel");
528        error!(ENOTSUP)
529    }
530    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
531        error!(ENOTSUP)
532    }
533}
534
535/// "user" API used to perform a user decision.
536struct UserApi;
537
538impl UserApi {
539    fn new_node() -> impl FsNodeOps {
540        SeLinuxApi::new_node(|| Ok(Self {}))
541    }
542}
543
544impl SeLinuxApiOps for UserApi {
545    fn api_write_permission() -> SecurityPermission {
546        SecurityPermission::ComputeUser
547    }
548    fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
549        track_stub!(TODO("https://fxbug.dev/411433214"), "selinux user");
550        error!(ENOTSUP)
551    }
552    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
553        error!(ENOTSUP)
554    }
555}
556
557struct CheckReqProtApi;
558
559impl CheckReqProtApi {
560    fn new_node() -> impl FsNodeOps {
561        SeLinuxApi::new_node(|| Ok(Self {}))
562    }
563}
564
565impl SeLinuxApiOps for CheckReqProtApi {
566    fn api_write_permission() -> SecurityPermission {
567        SecurityPermission::SetCheckReqProt
568    }
569
570    fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
571        // Linux v6.4 removed support for enabling "checkreqprot", rendering writes to the node a
572        // no-op.
573        error!(ENOTSUP)
574    }
575
576    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
577        Ok(b"0".into())
578    }
579}
580
581/// "context" API which accepts a Security Context in a single `write()` operation, and validates
582/// it against the loaded policy. If the Context is invalid then the `write()` returns `EINVAL`,
583/// otherwise the Context may be read back from the file.
584struct ContextApi {
585    security_server: Arc<SecurityServer>,
586    // Holds the SID representing the Security Context that the caller wrote to the file.
587    context_sid: Mutex<Option<SecurityId>>,
588}
589
590impl ContextApi {
591    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
592        SeLinuxApi::new_node(move || {
593            Ok(Self { security_server: security_server.clone(), context_sid: Mutex::default() })
594        })
595    }
596}
597
598impl SeLinuxApiOps for ContextApi {
599    fn api_write_permission() -> SecurityPermission {
600        SecurityPermission::CheckContext
601    }
602
603    fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
604        // If this instance was already written-to then fail the operation.
605        let mut context_sid = self.context_sid.lock();
606        if context_sid.is_some() {
607            return error!(EBUSY);
608        }
609
610        // Validate that the `data` describe valid user, role, type, etc by attempting to create
611        // a SID from it.
612        *context_sid = Some(
613            self.security_server
614                .security_context_to_sid(data.as_slice().into())
615                .map_err(|_| errno!(EINVAL))?,
616        );
617
618        Ok(())
619    }
620
621    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
622        // Read returns the Security Context the caller previously wrote to the file, normalized
623        // as a consequence of the Context->SID->Context round-trip. If no Context had been written
624        // by the caller, then this API file behaves as though empty.
625        // TODO: https://fxbug.dev/319629153 - If `write()` failed due to an invalid Context then
626        // should `read()` also fail, or return an empty result?
627        let maybe_sid = *self.context_sid.lock();
628        let result = maybe_sid
629            .and_then(|sid| self.security_server.sid_to_security_context_with_nul(sid))
630            .unwrap_or_default();
631        Ok(result.into())
632    }
633}
634
635/// Implements an entry within the "initial_contexts" directory.
636struct InitialContextFile {
637    security_server: Arc<SecurityServer>,
638    initial_sid: InitialSid,
639}
640
641impl InitialContextFile {
642    fn new_node(security_server: Arc<SecurityServer>, initial_sid: InitialSid) -> impl FsNodeOps {
643        BytesFile::new_node(Self { security_server, initial_sid })
644    }
645}
646
647impl BytesFileOps for InitialContextFile {
648    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
649        let sid = self.initial_sid.into();
650        if let Some(context) = self.security_server.sid_to_security_context_with_nul(sid) {
651            Ok(context.into())
652        } else {
653            // Looking up an initial SID can only fail if no policy is loaded, in
654            // which case the file contains the name of the initial SID, rather
655            // than a Security Context value.
656            Ok(self.initial_sid.name().as_bytes().into())
657        }
658    }
659}
660
661/// An entry in the "policy_capabilities" directory. There is one entry for each policy capability
662/// supported by the kernel implementation, with the content indicating whether the capability is
663/// enabled or disabled by the loaded policy.
664struct PolicyCapFile {
665    security_server: Arc<SecurityServer>,
666    policy_cap: PolicyCap,
667}
668
669impl PolicyCapFile {
670    fn new_node(security_server: Arc<SecurityServer>, initial_sid: PolicyCap) -> impl FsNodeOps {
671        BytesFile::new_node(Self { security_server, policy_cap: initial_sid })
672    }
673}
674
675impl BytesFileOps for PolicyCapFile {
676    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
677        if self.security_server.is_policycap_enabled(self.policy_cap) {
678            Ok(b"1".into())
679        } else {
680            Ok(b"0".into())
681        }
682    }
683}
684
685/// Extends a calculated `AccessDecision` with an additional permission set describing which
686/// permissions were actually `decided` - all other permissions in the `AccessDecision` structure
687/// should be assumed to be un-`decided`. This allows the "access" API to return partial results, to
688/// force userspace to re-query the API if any un-`decided` permission is later requested.
689struct AccessDecisionAndDecided {
690    decision: AccessDecision,
691    decided: AccessVector,
692}
693
694struct AccessApi {
695    security_server: Arc<SecurityServer>,
696    result: OnceLock<AccessDecisionAndDecided>,
697
698    // Required to support audit-logging of requests granted via `todo_deny` exceptions.
699    kernel: Weak<Kernel>,
700}
701
702impl AccessApi {
703    fn new_node(security_server: Arc<SecurityServer>, kernel: &Arc<Kernel>) -> impl FsNodeOps {
704        let kernel = Arc::downgrade(kernel);
705        SeLinuxApi::new_node(move || {
706            Ok(Self {
707                security_server: security_server.clone(),
708                result: OnceLock::default(),
709                kernel: kernel.clone(),
710            })
711        })
712    }
713}
714
715impl SeLinuxApiOps for AccessApi {
716    fn api_write_permission() -> SecurityPermission {
717        SecurityPermission::ComputeAv
718    }
719
720    fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
721        if self.result.get().is_some() {
722            // The "access" API can be written-to at most once.
723            return error!(EBUSY);
724        }
725
726        let data = str::from_utf8(&data).map_err(|_| errno!(EINVAL))?;
727
728        // Requests consist of three mandatory space-separated elements, and one optional element.
729        let mut parts = data.split_whitespace();
730
731        // <scontext>: describes the subject acting on the class.
732        let scontext_str = parts.next().ok_or_else(|| errno!(EINVAL))?;
733        let scontext = self
734            .security_server
735            .security_context_to_sid(scontext_str.into())
736            .map_err(|_| errno!(EINVAL))?;
737
738        // <tcontext>: describes the target (e.g. parent directory) of the operation.
739        let tcontext_str = parts.next().ok_or_else(|| errno!(EINVAL))?;
740        let tcontext = self
741            .security_server
742            .security_context_to_sid(tcontext_str.into())
743            .map_err(|_| errno!(EINVAL))?;
744
745        // <tclass>: the policy-specific Id of the target class, as a decimal integer.
746        // Class Ids are obtained via lookups in the SELinuxFS "class" directory.
747        let tclass = parts.next().ok_or_else(|| errno!(EINVAL))?;
748        let tclass_id = u32::from_str(tclass).map_err(|_| errno!(EINVAL))?;
749        let tclass = ClassId::new(NonZeroU32::new(tclass_id).ok_or_else(|| errno!(EINVAL))?).into();
750
751        // <request>: the set of permissions that the caller requests.
752        let requested = if let Some(requested) = parts.next() {
753            AccessVector::from_str(requested).map_err(|_| errno!(EINVAL))?
754        } else {
755            AccessVector::ALL
756        };
757
758        // This API does not appear to treat trailing arguments as invalid.
759
760        // Perform the access decision calculation.
761        let mut decision =
762            self.security_server.compute_access_decision_raw(scontext, tcontext, tclass);
763
764        // `compute_access_decision()` returns an `AccessDecision` with results calculated for all
765        // permissions defined by policy, so by default the "access" API reports all permissions as
766        // having been `decided`.
767        let mut decided = AccessVector::ALL;
768
769        // If there is a `todo_bug` associated with the decision then grant all permissions and
770        // make a best-effort attempt to emit a log for missing permissions.
771        let Some(kernel) = self.kernel.upgrade() else {
772            return error!(EINVAL);
773        };
774        if let Some(todo_bug) = decision.todo_bug {
775            let denied = AccessVector::ALL - decision.allow;
776            let audited_denied = denied & decision.auditdeny;
777
778            let requested_has_audited_denial = audited_denied & requested != AccessVector::NONE;
779
780            if requested_has_audited_denial {
781                // One or more requested permissions would be denied, and the denial audit-logged,
782                // so emit a track-stub report and a description of the request and result.
783                // Leave all permissions `decided`, so that only the first such failure is audited.
784                __track_stub_inner(
785                    BugRef::from(NonZeroU64::new(todo_bug.get() as u64).unwrap()),
786                    "Enforce SELinuxFS access API",
787                    None,
788                    std::panic::Location::caller(),
789                );
790                let audit_message = format!(
791                    "avc: todo_deny {{ ACCESS_API }} bug={todo_bug} scontext={scontext_str:?} tcontext={tcontext_str:?} tclass={tclass_id} requested={requested:?}",
792                );
793                kernel.audit_logger().audit_log(AUDIT_AVC as u16, || audit_message);
794            } else {
795                // All requested permissions were granted. To allow "todo_deny" logs and track-stub
796                // tracking of permissions that would otherwise be denied & audited, remove those
797                // permissions from the `decided` set, to signal that the userspace AVC should re-
798                // query rather than using these cached results.
799                decided -= audited_denied;
800            }
801
802            // Grant all permissions in the returned result, so that clients that ignore the
803            // `decided` set will still have the allowance applied to all permissions.
804            decision.allow = AccessVector::ALL;
805        }
806
807        self.result
808            .set(AccessDecisionAndDecided { decision, decided })
809            .map_err(|_| errno!(EINVAL))?;
810
811        Ok(())
812    }
813
814    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
815        let Some(AccessDecisionAndDecided { decision, decided }) = self.result.get() else {
816            return Ok(Vec::new().into());
817        };
818
819        let allowed = decision.allow;
820        let auditallow = decision.auditallow;
821        let auditdeny = decision.auditdeny;
822        let flags = decision.flags;
823
824        // TODO: https://fxbug.dev/361551536 - `seqno` should reflect the policy revision from
825        // which the result was calculated, to allow the client to re-try if the policy changed.
826        const SEQNO: u32 = 1;
827
828        // Result format is: allowed decided auditallow auditdeny seqno flags
829        // Everything but seqno must be in hexadecimal format and represents a bits field.
830        let result =
831            format!("{allowed:x} {decided:x} {auditallow:x} {auditdeny:x} {SEQNO} {flags:x}");
832        Ok(result.into_bytes().into())
833    }
834}
835
836struct NullFileNode;
837
838impl FsNodeOps for NullFileNode {
839    fs_node_impl_not_dir!();
840
841    fn create_file_ops(
842        &self,
843        _locked: &mut Locked<FileOpsCore>,
844        _node: &FsNode,
845        _current_task: &CurrentTask,
846        _flags: OpenFlags,
847    ) -> Result<Box<dyn FileOps>, Errno> {
848        Ok(Box::new(DevNull))
849    }
850}
851
852#[derive(Clone)]
853struct BooleansDirectory {
854    security_server: Arc<SecurityServer>,
855}
856
857impl BooleansDirectory {
858    fn new(security_server: Arc<SecurityServer>) -> Self {
859        Self { security_server }
860    }
861}
862
863impl FsNodeOps for BooleansDirectory {
864    fs_node_impl_dir_readonly!();
865
866    fn create_file_ops(
867        &self,
868        _locked: &mut Locked<FileOpsCore>,
869        _node: &FsNode,
870        _current_task: &CurrentTask,
871        _flags: OpenFlags,
872    ) -> Result<Box<dyn FileOps>, Errno> {
873        Ok(Box::new(self.clone()))
874    }
875
876    fn lookup(
877        &self,
878        _locked: &mut Locked<FileOpsCore>,
879        node: &FsNode,
880        current_task: &CurrentTask,
881        name: &FsStr,
882    ) -> Result<FsNodeHandle, Errno> {
883        let utf8_name = String::from_utf8(name.to_vec()).map_err(|_| errno!(ENOENT))?;
884        if self.security_server.conditional_booleans().contains(&utf8_name) {
885            Ok(node.fs().create_node_and_allocate_node_id(
886                BooleanFile::new_node(self.security_server.clone(), utf8_name),
887                FsNodeInfo::new(mode!(IFREG, 0o644), current_task.current_fscred()),
888            ))
889        } else {
890            error!(ENOENT)
891        }
892    }
893}
894
895impl FileOps for BooleansDirectory {
896    fileops_impl_directory!();
897    fileops_impl_noop_sync!();
898    fileops_impl_unbounded_seek!();
899
900    fn readdir(
901        &self,
902        _locked: &mut Locked<FileOpsCore>,
903        file: &FileObject,
904        _current_task: &CurrentTask,
905        sink: &mut dyn DirentSink,
906    ) -> Result<(), Errno> {
907        emit_dotdot(file, sink)?;
908
909        // `emit_dotdot()` provides the first two directory entries, so that the entries for
910        // the conditional booleans start from offset 2.
911        let iter_offset = sink.offset() - 2;
912        for name in self.security_server.conditional_booleans().iter().skip(iter_offset as usize) {
913            sink.add(
914                file.fs.allocate_ino(),
915                /* next offset = */ sink.offset() + 1,
916                DirectoryEntryType::REG,
917                FsString::from(name.as_bytes()).as_ref(),
918            )?;
919        }
920
921        Ok(())
922    }
923}
924
925struct BooleanFile {
926    security_server: Arc<SecurityServer>,
927    name: String,
928}
929
930impl BooleanFile {
931    fn new_node(security_server: Arc<SecurityServer>, name: String) -> impl FsNodeOps {
932        BytesFile::new_node(BooleanFile { security_server, name })
933    }
934}
935
936impl BytesFileOps for BooleanFile {
937    fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
938        let value = parse_unsigned_file::<u32>(&data)? != 0;
939        self.security_server.set_pending_boolean(&self.name, value).map_err(|_| errno!(EIO))
940    }
941
942    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
943        // Each boolean has a current active value, and a pending value that
944        // will become active if "commit_pending_booleans" is written to.
945        // e.g. "1 0" will be read if a boolean is True but will become False.
946        let (active, pending) =
947            self.security_server.get_boolean(&self.name).map_err(|_| errno!(EIO))?;
948        Ok(format!("{} {}", active as u32, pending as u32).into_bytes().into())
949    }
950}
951
952struct CommitBooleansApi {
953    security_server: Arc<SecurityServer>,
954}
955
956impl CommitBooleansApi {
957    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
958        SeLinuxApi::new_node(move || {
959            Ok(CommitBooleansApi { security_server: security_server.clone() })
960        })
961    }
962}
963
964impl SeLinuxApiOps for CommitBooleansApi {
965    fn api_write_permission() -> SecurityPermission {
966        SecurityPermission::SetBool
967    }
968
969    fn api_write(&self, data: Vec<u8>) -> Result<(), Errno> {
970        // "commit_pending_booleans" expects a numeric argument, which is
971        // interpreted as a boolean, with the pending booleans committed if the
972        // value is true (i.e. non-zero).
973        let commit = parse_unsigned_file::<u32>(&data)? != 0;
974
975        if commit {
976            self.security_server.commit_pending_booleans();
977        }
978        Ok(())
979    }
980}
981
982struct ClassDirectory {
983    security_server: Arc<SecurityServer>,
984}
985
986impl ClassDirectory {
987    fn new(security_server: Arc<SecurityServer>) -> Self {
988        Self { security_server }
989    }
990}
991
992impl FsNodeOps for ClassDirectory {
993    fs_node_impl_dir_readonly!();
994
995    /// Returns the set of classes under the "class" directory.
996    fn create_file_ops(
997        &self,
998        _locked: &mut Locked<FileOpsCore>,
999        _node: &FsNode,
1000        _current_task: &CurrentTask,
1001        _flags: OpenFlags,
1002    ) -> Result<Box<dyn FileOps>, Errno> {
1003        Ok(VecDirectory::new_file(
1004            self.security_server
1005                .class_names()
1006                .map_err(|_| errno!(ENOENT))?
1007                .iter()
1008                .map(|class_name| VecDirectoryEntry {
1009                    entry_type: DirectoryEntryType::DIR,
1010                    name: class_name.clone().into(),
1011                    inode: None,
1012                })
1013                .collect(),
1014        ))
1015    }
1016
1017    fn lookup(
1018        &self,
1019        _locked: &mut Locked<FileOpsCore>,
1020        node: &FsNode,
1021        _current_task: &CurrentTask,
1022        name: &FsStr,
1023    ) -> Result<FsNodeHandle, Errno> {
1024        let id: u32 = self
1025            .security_server
1026            .class_id_by_name(&name.to_string())
1027            .map_err(|_| errno!(EINVAL))?
1028            .into();
1029
1030        let fs = node.fs();
1031        let dir = SimpleDirectory::new();
1032        dir.edit(&fs, |dir| {
1033            let index_bytes = format!("{}", id).into_bytes();
1034            dir.entry("index", BytesFile::new_node(index_bytes), mode!(IFREG, 0o444));
1035            dir.entry(
1036                "perms",
1037                PermsDirectory::new(self.security_server.clone(), name.to_string()),
1038                mode!(IFDIR, 0o555),
1039            );
1040        });
1041        Ok(dir.into_node(&fs, 0o555))
1042    }
1043}
1044
1045/// Represents the perms/ directory under each class entry of the SeLinuxClassDirectory.
1046struct PermsDirectory {
1047    security_server: Arc<SecurityServer>,
1048    class_name: String,
1049}
1050
1051impl PermsDirectory {
1052    fn new(security_server: Arc<SecurityServer>, class_name: String) -> Self {
1053        Self { security_server, class_name }
1054    }
1055}
1056
1057impl FsNodeOps for PermsDirectory {
1058    fs_node_impl_dir_readonly!();
1059
1060    /// Lists all available permissions for the corresponding class.
1061    fn create_file_ops(
1062        &self,
1063        _locked: &mut Locked<FileOpsCore>,
1064        _node: &FsNode,
1065        _current_task: &CurrentTask,
1066        _flags: OpenFlags,
1067    ) -> Result<Box<dyn FileOps>, Errno> {
1068        Ok(VecDirectory::new_file(
1069            self.security_server
1070                .class_permissions_by_name(&self.class_name)
1071                .map_err(|_| errno!(ENOENT))?
1072                .iter()
1073                .map(|(_permission_id, permission_name)| VecDirectoryEntry {
1074                    entry_type: DirectoryEntryType::DIR,
1075                    name: permission_name.clone().into(),
1076                    inode: None,
1077                })
1078                .collect(),
1079        ))
1080    }
1081
1082    fn lookup(
1083        &self,
1084        _locked: &mut Locked<FileOpsCore>,
1085        node: &FsNode,
1086        current_task: &CurrentTask,
1087        name: &FsStr,
1088    ) -> Result<FsNodeHandle, Errno> {
1089        let found_permission_id = self
1090            .security_server
1091            .class_permissions_by_name(&(self.class_name))
1092            .map_err(|_| errno!(ENOENT))?
1093            .iter()
1094            .find(|(_permission_id, permission_name)| permission_name == name)
1095            .ok_or_else(|| errno!(ENOENT))?
1096            .0;
1097
1098        Ok(node.fs().create_node_and_allocate_node_id(
1099            BytesFile::new_node(format!("{}", found_permission_id).into_bytes()),
1100            FsNodeInfo::new(mode!(IFREG, 0o444), current_task.current_fscred()),
1101        ))
1102    }
1103}
1104
1105/// Exposes AVC cache statistics from the SELinux security server to userspace.
1106struct AvcCacheStatsFile {
1107    security_server: Arc<SecurityServer>,
1108}
1109
1110impl AvcCacheStatsFile {
1111    fn new_node(security_server: Arc<SecurityServer>) -> impl FsNodeOps {
1112        BytesFile::new_node(Self { security_server })
1113    }
1114}
1115
1116impl BytesFileOps for AvcCacheStatsFile {
1117    fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
1118        let stats = self.security_server.avc_cache_stats();
1119        Ok(format!(
1120            "lookups hits misses allocations reclaims frees\n{} {} {} {} {} {}\n",
1121            stats.lookups, stats.hits, stats.misses, stats.allocs, stats.reclaims, stats.frees
1122        )
1123        .into_bytes()
1124        .into())
1125    }
1126}
1127
1128/// File node implementation tailored to the behaviour of the APIs exposed to userspace via the
1129/// SELinux filesystem. These API files share some unusual behaviours:
1130///
1131/// (1) Seek Position:
1132/// API files in the SELinux filesystem do have persistent seek offsets, but asymmetric behaviour
1133/// for read and write operations:
1134/// - Read operations respect the file offset, and increment it.
1135/// - Write operations do not increment the file offset. This is important for APIs such as
1136///   "create", which are used by `write()`ing a query and then `read()`ing the resulting value,
1137///   since otherwise the `read()` would start from the end of the `write()`.
1138///
1139/// API files do not handle non-zero offset `write()`s consistently. Some, (e.g. "context"), ignore
1140/// the offset, while others (e.g. "load") will fail with `EINVAL` if it is non-zero.
1141///
1142/// (2) Single vs Multi-Request:
1143/// Most API files may be `read()` from any number of times, but only support a single `write()`
1144/// operation. Attempting to `write()` a second time will return `EBUSY`.
1145///
1146/// (3) Error Handling:
1147/// Once an operation on an API file has failed, all subsequent operations on that file will
1148/// also fail, with the same error code.  e.g. Attempting multiple `write()` operations will
1149/// return `EBUSY` from the second and subsequent calls, but subsequent calls to `read()`,
1150/// `seek()` etc will also return `EBUSY`.
1151///
1152/// This helper currently implements asymmetric seek behaviour, and permission checks on write
1153/// operations.
1154struct SeLinuxApi<T: SeLinuxApiOps + Sync + Send + 'static> {
1155    ops: T,
1156}
1157
1158impl<T: SeLinuxApiOps + Sync + Send + 'static> SeLinuxApi<T> {
1159    /// Returns a new `SeLinuxApi` file node that will use `create_ops` to create a new `SeLinuxApiOps`
1160    /// instance every time a caller opens the file.
1161    fn new_node<F>(create_ops: F) -> impl FsNodeOps
1162    where
1163        F: Fn() -> Result<T, Errno> + Send + Sync + 'static,
1164    {
1165        SimpleFileNode::new(move |_, _| create_ops().map(|ops| SeLinuxApi { ops }))
1166    }
1167}
1168
1169/// Trait implemented for each SELinux API file (e.g. "create", "load") to define its behaviour.
1170trait SeLinuxApiOps {
1171    /// Returns the "security" class permission that is required in order to write to the API file.
1172    fn api_write_permission() -> SecurityPermission;
1173
1174    /// Returns true if writes ignore the seek offset, rather than requiring it to be zero.
1175    fn api_write_ignores_offset() -> bool {
1176        false
1177    }
1178
1179    /// Processes a request written to an API file.
1180    fn api_write(&self, _data: Vec<u8>) -> Result<(), Errno> {
1181        error!(EINVAL)
1182    }
1183
1184    /// Returns the complete contents of this API file.
1185    fn api_read(&self) -> Result<Cow<'_, [u8]>, Errno> {
1186        error!(EINVAL)
1187    }
1188
1189    /// Variant of `api_write()` that additionally receives the `current_task`.
1190    fn api_write_with_task(
1191        &self,
1192        _locked: &mut Locked<FileOpsCore>,
1193        _current_task: &CurrentTask,
1194        data: Vec<u8>,
1195    ) -> Result<(), Errno> {
1196        self.api_write(data)
1197    }
1198}
1199
1200impl<T: SeLinuxApiOps + Sync + Send + 'static> FileOps for SeLinuxApi<T> {
1201    fileops_impl_seekable!();
1202    fileops_impl_noop_sync!();
1203
1204    fn writes_update_seek_offset(&self) -> bool {
1205        false
1206    }
1207
1208    fn read(
1209        &self,
1210        _locked: &mut Locked<FileOpsCore>,
1211        _file: &FileObject,
1212        _current_task: &CurrentTask,
1213        offset: usize,
1214        data: &mut dyn OutputBuffer,
1215    ) -> Result<usize, Errno> {
1216        let response = self.ops.api_read()?;
1217        data.write(&response[offset..])
1218    }
1219
1220    fn write(
1221        &self,
1222        locked: &mut Locked<FileOpsCore>,
1223        _file: &FileObject,
1224        current_task: &CurrentTask,
1225        offset: usize,
1226        data: &mut dyn InputBuffer,
1227    ) -> Result<usize, Errno> {
1228        if offset != 0 && !T::api_write_ignores_offset() {
1229            return error!(EINVAL);
1230        }
1231        security::selinuxfs_check_access(current_task, T::api_write_permission())?;
1232        let data = data.read_all()?;
1233        let data_len = data.len();
1234        self.ops.api_write_with_task(locked, current_task, data)?;
1235        Ok(data_len)
1236    }
1237}
1238
1239/// Returns the "selinuxfs" file system, used by the system userspace to administer SELinux.
1240pub fn selinux_fs(
1241    locked: &mut Locked<Unlocked>,
1242    current_task: &CurrentTask,
1243    options: FileSystemOptions,
1244) -> Result<FileSystemHandle, Errno> {
1245    struct SeLinuxFsHandle(FileSystemHandle);
1246
1247    Ok(current_task
1248        .kernel()
1249        .expando
1250        .get_or_try_init(|| Ok(SeLinuxFsHandle(SeLinuxFs::new_fs(locked, current_task, options)?)))?
1251        .0
1252        .clone())
1253}
1254
1255#[cfg(test)]
1256mod tests {
1257    use super::*;
1258    use fuchsia_runtime;
1259    use selinux::SecurityServer;
1260    use zerocopy::{FromBytes, KnownLayout};
1261
1262    #[fuchsia::test]
1263    fn status_vmo_has_correct_size_and_rights() {
1264        // The current version of the "status" file contains five packed
1265        // u32 values.
1266        const STATUS_T_SIZE: usize = size_of::<u32>() * 5;
1267
1268        let status_holder = StatusPublisher::new_default().unwrap();
1269        let status_vmo = status_holder.0.get_readonly_vmo();
1270
1271        // Verify the content and actual size of the structure are as expected.
1272        let content_size = status_vmo.get_content_size().unwrap() as usize;
1273        assert_eq!(content_size, STATUS_T_SIZE);
1274        let actual_size = status_vmo.get_size().unwrap() as usize;
1275        assert!(actual_size >= STATUS_T_SIZE);
1276
1277        // Ensure the returned handle is read-only and non-resizable.
1278        let rights = status_vmo.basic_info().unwrap().rights;
1279        assert_eq!((rights & zx::Rights::MAP), zx::Rights::MAP);
1280        assert_eq!((rights & zx::Rights::READ), zx::Rights::READ);
1281        assert_eq!((rights & zx::Rights::GET_PROPERTY), zx::Rights::GET_PROPERTY);
1282        assert_eq!((rights & zx::Rights::WRITE), zx::Rights::NONE);
1283        assert_eq!((rights & zx::Rights::RESIZE), zx::Rights::NONE);
1284    }
1285
1286    #[derive(KnownLayout, FromBytes)]
1287    #[repr(C, align(4))]
1288    struct TestSeLinuxStatusT {
1289        version: u32,
1290        sequence: u32,
1291        enforcing: u32,
1292        policyload: u32,
1293        deny_unknown: u32,
1294    }
1295
1296    fn with_status_t<R>(
1297        status_vmo: &Arc<zx::Vmo>,
1298        do_test: impl FnOnce(&TestSeLinuxStatusT) -> R,
1299    ) -> R {
1300        let flags = zx::VmarFlags::PERM_READ
1301            | zx::VmarFlags::ALLOW_FAULTS
1302            | zx::VmarFlags::REQUIRE_NON_RESIZABLE;
1303        let map_addr = fuchsia_runtime::vmar_root_self()
1304            .map(0, status_vmo, 0, size_of::<TestSeLinuxStatusT>(), flags)
1305            .unwrap();
1306        #[allow(
1307            clippy::undocumented_unsafe_blocks,
1308            reason = "Force documented unsafe blocks in Starnix"
1309        )]
1310        let mapped_status = unsafe { &mut *(map_addr as *mut TestSeLinuxStatusT) };
1311        let result = do_test(mapped_status);
1312        #[allow(
1313            clippy::undocumented_unsafe_blocks,
1314            reason = "Force documented unsafe blocks in Starnix"
1315        )]
1316        unsafe {
1317            fuchsia_runtime::vmar_root_self()
1318                .unmap(map_addr, size_of::<TestSeLinuxStatusT>())
1319                .unwrap()
1320        };
1321        result
1322    }
1323
1324    #[fuchsia::test]
1325    fn status_file_layout() {
1326        let security_server = SecurityServer::new_default();
1327        let status_holder = StatusPublisher::new_default().unwrap();
1328        let status_vmo = status_holder.0.get_readonly_vmo();
1329        security_server.set_status_publisher(Box::new(status_holder));
1330        security_server.set_enforcing(false);
1331        let mut seq_no: u32 = 0;
1332        with_status_t(&status_vmo, |status| {
1333            assert_eq!(status.version, SELINUX_STATUS_VERSION);
1334            assert_eq!(status.enforcing, 0);
1335            seq_no = status.sequence;
1336            assert_eq!(seq_no % 2, 0);
1337        });
1338        security_server.set_enforcing(true);
1339        with_status_t(&status_vmo, |status| {
1340            assert_eq!(status.version, SELINUX_STATUS_VERSION);
1341            assert_eq!(status.enforcing, 1);
1342            assert_ne!(status.sequence, seq_no);
1343            seq_no = status.sequence;
1344            assert_eq!(seq_no % 2, 0);
1345        });
1346    }
1347
1348    #[fuchsia::test]
1349    fn status_accurate_directly_following_set_status_publisher() {
1350        let security_server = SecurityServer::new_default();
1351        let status_holder = StatusPublisher::new_default().unwrap();
1352        let status_vmo = status_holder.0.get_readonly_vmo();
1353
1354        // Ensure a change in status-visible security server state is made before invoking
1355        // `set_status_publisher()`.
1356        assert_eq!(false, security_server.is_enforcing());
1357        security_server.set_enforcing(true);
1358
1359        security_server.set_status_publisher(Box::new(status_holder));
1360        with_status_t(&status_vmo, |status| {
1361            // Ensure latest `enforcing` state is reported immediately following
1362            // `set_status_publisher()`.
1363            assert_eq!(status.enforcing, 1);
1364        });
1365    }
1366}