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