blob: a7e7d20483a937f4a223102a686f456921a43282 [file] [log] [blame] [edit]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{
fs::{
buffers::{InputBuffer, OutputBuffer},
*,
},
logging::not_implemented,
task::*,
types::*,
};
use derivative::Derivative;
use starnix_lock::Mutex;
use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
use zerocopy::AsBytes;
/// The version of selinux_status_t this kernel implements.
const SELINUX_STATUS_VERSION: u32 = 1;
const SELINUX_PERMS: &[&[u8]] = &[b"add", b"find", b"read", b"set"];
struct SeLinuxFs;
impl FileSystemOps for SeLinuxFs {
fn statfs(&self, _fs: &FileSystem, _current_task: &CurrentTask) -> Result<statfs, Errno> {
Ok(statfs::default(SELINUX_MAGIC))
}
fn name(&self) -> &'static FsStr {
b"selinuxfs"
}
}
impl SeLinuxFs {
fn new_fs(kernel: &Arc<Kernel>, options: FileSystemOptions) -> Result<FileSystemHandle, Errno> {
let fs = FileSystem::new(kernel, CacheMode::Permanent, SeLinuxFs, options);
let mut dir = StaticDirectoryBuilder::new(&fs);
// Read-only files & directories, exposing SELinux internal state.
dir.entry(b"checkreqprot", BytesFile::new_node(SeCheckReqProt), mode!(IFREG, 0o644));
dir.entry(b"class", SeLinuxClassDirectory::new(), mode!(IFDIR, 0o777));
dir.entry(
b"deny_unknown",
// Allow all unknown object classes/permissions.
BytesFile::new_node(b"0:0\n".to_vec()),
mode!(IFREG, 0o444),
);
dir.subdir(b"initial_contexts", 0o555, |dir| {
dir.entry(
b"kernel",
BytesFile::new_node(b"system_u:system_r:kernel_t:s0".to_vec()),
mode!(IFREG, 0o444),
);
});
dir.entry(b"mls", BytesFile::new_node(b"1".to_vec()), mode!(IFREG, 0o444));
dir.entry(b"policyvers", BytesFile::new_node(b"33".to_vec()), mode!(IFREG, 0o444));
dir.entry(
b"status",
// The status file needs to be mmap-able, so use a VMO-backed file.
// When the selinux state changes in the future, the way to update this data (and
// communicate updates with userspace) is to use the
// ["seqlock"](https://en.wikipedia.org/wiki/Seqlock) technique.
VmoFileNode::from_bytes(
selinux_status_t { version: SELINUX_STATUS_VERSION, ..Default::default() }
.as_bytes(),
)?,
mode!(IFREG, 0o444),
);
// Write-only files used to configure and query SELinux.
dir.entry(b"access", AccessFileNode::new(), mode!(IFREG, 0o666));
dir.entry(b"context", BytesFile::new_node(SeContext), mode!(IFREG, 0o666));
dir.entry(b"create", SeCreate::new_node(), mode!(IFREG, 0o644));
dir.entry(b"load", BytesFile::new_node(SeLoad), mode!(IFREG, 0o600));
// Allows the SELinux enforcing mode to be queried, or changed.
dir.entry(
b"enforce",
BytesFile::new_node(SeEnforce { enforce: Mutex::new(false) }),
// TODO(b/297313229): Get mode from the container.
mode!(IFREG, 0o644),
);
// "/dev/null" equivalent used for file descriptors redirected by SELinux.
dir.entry_dev(b"null", DeviceFileNode, mode!(IFCHR, 0o666), DeviceType::NULL);
dir.build_root();
Ok(fs)
}
}
/// The C-style struct exposed to userspace by the /sys/fs/selinux/status file.
/// Defined here (instead of imported through bindgen) as selinux headers are not exposed through
/// kernel uapi headers.
#[derive(Debug, Copy, Clone, AsBytes, Default)]
#[repr(C, packed)]
struct selinux_status_t {
/// Version number of this structure (1).
version: u32,
/// Sequence number. See [seqlock](https://en.wikipedia.org/wiki/Seqlock).
sequence: u32,
/// `0` means permissive mode, `1` means enforcing mode.
enforcing: u32,
/// The number of times the selinux policy has been reloaded.
policyload: u32,
/// `0` means allow and `1` means deny unknown object classes/permissions.
deny_unknown: u32,
}
struct SeLoad;
impl BytesFileOps for SeLoad {
fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
not_implemented!("got selinux policy, length {}, ignoring", data.len());
Ok(())
}
}
struct SeEnforce {
enforce: Mutex<bool>,
}
impl BytesFileOps for SeEnforce {
fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
let enforce_data = parse_unsigned_file::<u32>(&data)?;
let enforce_opt = match enforce_data {
0 => Some(false),
1 => Some(true),
_ => None,
};
if let Some(enforce) = enforce_opt {
*self.enforce.lock() = enforce;
}
Ok(())
}
fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
Ok(serialize_u32_file(*self.enforce.lock() as u32).into())
}
}
struct SeCreate {
data: Mutex<Vec<u8>>,
}
impl SeCreate {
fn new_node() -> impl FsNodeOps {
BytesFile::new_node(Self { data: Mutex::default() })
}
}
impl BytesFileOps for SeCreate {
fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
*self.data.lock() = data;
Ok(())
}
fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
Ok(self.data.lock().clone().into())
}
}
struct SeCheckReqProt;
impl BytesFileOps for SeCheckReqProt {
fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
let checkreqprot = parse_unsigned_file::<u32>(&data)?;
not_implemented!("selinux checkreqprot: {}", checkreqprot);
Ok(())
}
}
struct SeContext;
impl BytesFileOps for SeContext {
fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
not_implemented!("selinux validate context: {}", String::from_utf8_lossy(&data));
Ok(())
}
}
struct AccessFile {
seqno: u64,
}
impl FileOps for AccessFile {
fileops_impl_nonseekable!();
fn read(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
debug_assert!(offset == 0);
// Format is allowed decided autitallow auditdeny seqno flags
// Everything but seqno must be in hexadecimal format and represents a bits field.
let content = format!("ffffffff ffffffff 0 ffffffff {} 0\n", self.seqno);
data.write(content.as_bytes())
}
fn write(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
debug_assert!(offset == 0);
Ok(data.drain())
}
}
struct DeviceFileNode;
impl FsNodeOps for DeviceFileNode {
fs_node_impl_not_dir!();
fn create_file_ops(
&self,
_node: &FsNode,
_current_task: &CurrentTask,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
unreachable!("Special nodes cannot be opened.");
}
}
struct AccessFileNode {
seqno: Mutex<u64>,
}
impl AccessFileNode {
fn new() -> Self {
Self { seqno: Mutex::new(0) }
}
}
impl FsNodeOps for AccessFileNode {
fs_node_impl_not_dir!();
fn create_file_ops(
&self,
_node: &FsNode,
_current_task: &CurrentTask,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
let seqno = {
let mut writer = self.seqno.lock();
*writer += 1;
*writer
};
Ok(Box::new(AccessFile { seqno }))
}
}
struct SeLinuxClassDirectory {
entries: Mutex<BTreeMap<FsString, FsNodeHandle>>,
}
impl SeLinuxClassDirectory {
fn new() -> Arc<Self> {
Arc::new(Self { entries: Mutex::new(BTreeMap::new()) })
}
}
impl FsNodeOps for Arc<SeLinuxClassDirectory> {
fs_node_impl_dir_readonly!();
fn create_file_ops(
&self,
_node: &FsNode,
_current_task: &CurrentTask,
_flags: OpenFlags,
) -> Result<Box<dyn FileOps>, Errno> {
Ok(VecDirectory::new_file(
self.entries
.lock()
.iter()
.map(|(name, node)| VecDirectoryEntry {
entry_type: DirectoryEntryType::DIR,
name: name.clone(),
inode: Some(node.node_id),
})
.collect(),
))
}
fn lookup(
&self,
node: &FsNode,
_current_task: &CurrentTask,
name: &FsStr,
) -> Result<FsNodeHandle, Errno> {
let mut entries = self.entries.lock();
let next_index = entries.len() + 1;
Ok(entries
.entry(name.to_vec())
.or_insert_with(|| {
let index = format!("{next_index}\n").into_bytes();
let fs = node.fs();
let mut dir = StaticDirectoryBuilder::new(&fs);
dir.entry(b"index", BytesFile::new_node(index), mode!(IFREG, 0o444));
dir.subdir(b"perms", 0o555, |perms| {
for (i, perm) in SELINUX_PERMS.iter().enumerate() {
let node = BytesFile::new_node(format!("{}\n", i + 1).as_bytes().to_vec());
perms.entry(perm, node, mode!(IFREG, 0o444));
}
});
dir.set_mode(mode!(IFDIR, 0o555));
dir.build()
})
.clone())
}
}
#[derive(Derivative)]
#[derivative(Default)]
pub struct SeLinuxThreadGroupState {
#[derivative(Default(
value = "b\"unconfined_u:unconfined_r:unconfined_t:s0-s0:c0-c1023\".to_vec()"
))]
pub current_context: FsString,
pub fscreate_context: FsString,
pub exec_context: FsString,
}
pub fn selinux_proc_attrs(task: &TempRef<'_, Task>, dir: &mut StaticDirectoryBuilder<'_>) {
use SeLinuxContextAttr::*;
dir.entry(b"current", Current.new_node(task), mode!(IFREG, 0o666));
dir.entry(b"fscreate", FsCreate.new_node(task), mode!(IFREG, 0o666));
dir.entry(b"exec", Exec.new_node(task), mode!(IFREG, 0o666));
}
enum SeLinuxContextAttr {
Current,
Exec,
FsCreate,
}
impl SeLinuxContextAttr {
fn new_node(self, task: &TempRef<'_, Task>) -> impl FsNodeOps {
BytesFile::new_node(AttrNode { attr: self, task: WeakRef::from(task) })
}
fn access_on_task<R, F: FnOnce(&mut FsString) -> R>(&self, task: &Task, f: F) -> R {
let mut tg = task.thread_group.write();
match self {
Self::Current => f(&mut tg.selinux.current_context),
Self::FsCreate => f(&mut tg.selinux.fscreate_context),
Self::Exec => f(&mut tg.selinux.exec_context),
}
}
}
struct AttrNode {
attr: SeLinuxContextAttr,
task: WeakRef<Task>,
}
impl BytesFileOps for AttrNode {
fn write(&self, _current_task: &CurrentTask, data: Vec<u8>) -> Result<(), Errno> {
self.attr.access_on_task(Task::from_weak(&self.task)?.as_ref(), |attr| *attr = data);
Ok(())
}
fn read(&self, _current_task: &CurrentTask) -> Result<Cow<'_, [u8]>, Errno> {
Ok(self
.attr
.access_on_task(Task::from_weak(&self.task)?.as_ref(), |attr| attr.clone())
.into())
}
}
pub fn selinux_fs(kern: &Arc<Kernel>, options: FileSystemOptions) -> &FileSystemHandle {
kern.selinux_fs
.get_or_init(|| SeLinuxFs::new_fs(kern, options).expect("failed to construct selinuxfs"))
}