Skip to main content

starnix_modules_selinuxfs/
lib.rs

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