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