blob: f0997b96b89a1cfef93954b89a1ed1aac3c48fc3 [file] [log] [blame]
// Copyright 2024 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.
// TODO(https://github.com/rust-lang/rust/issues/39371): remove
#![allow(non_upper_case_globals)]
use super::selinux_hooks::audit::Auditable;
use super::{
BinderConnectionState, BpfMapState, BpfProgState, FileObjectState, FileSystemState,
KernelState, PerfEventState, ResolvedElfState, TaskState, common_cap, selinux_hooks, yama,
};
use crate::bpf::BpfMap;
use crate::bpf::program::Program;
use crate::mm::{Mapping, MappingOptions, ProtectionFlags};
use crate::perf::PerfEventFile;
use crate::security::selinux_hooks::current_task_state;
use crate::task::{CurrentTask, FullCredentials, Kernel, Task};
use crate::vfs::fs_args::MountParams;
use crate::vfs::socket::{
Socket, SocketAddress, SocketDomain, SocketFile, SocketPeer, SocketProtocol,
SocketShutdownFlags, SocketType,
};
use crate::vfs::{
Anon, DirEntryHandle, DowncastedFile, FileHandle, FileObject, FileSystem, FileSystemHandle,
FileSystemOps, FsNode, FsStr, FsString, Mount, NamespaceNode, OutputBuffer, ValueOrSize,
XattrOp,
};
use ebpf::MapFlags;
use linux_uapi::{
perf_event_attr, perf_type_id, perf_type_id_PERF_TYPE_BREAKPOINT,
perf_type_id_PERF_TYPE_HARDWARE, perf_type_id_PERF_TYPE_HW_CACHE, perf_type_id_PERF_TYPE_RAW,
perf_type_id_PERF_TYPE_SOFTWARE, perf_type_id_PERF_TYPE_TRACEPOINT,
};
use selinux::{FileSystemMountOptions, SecurityPermission, SecurityServer};
use starnix_logging::{CATEGORY_STARNIX_SECURITY, log_debug, trace_duration};
use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Unlocked};
use starnix_types::ownership::TempRef;
use starnix_uapi::arc_key::WeakKey;
use starnix_uapi::auth::PtraceAccessMode;
use starnix_uapi::device_type::DeviceType;
use starnix_uapi::errors::Errno;
use starnix_uapi::file_mode::{Access, FileMode};
use starnix_uapi::mount_flags::MountFlags;
use starnix_uapi::open_flags::OpenFlags;
use starnix_uapi::signals::Signal;
use starnix_uapi::syslog::SyslogAction;
use starnix_uapi::unmount_flags::UnmountFlags;
use starnix_uapi::user_address::UserAddress;
use starnix_uapi::{bpf_cmd, error, rlimit};
use std::ops::Range;
use std::sync::Arc;
use syncio::zxio_node_attr_has_t;
use zerocopy::FromBytes;
macro_rules! track_hook_duration {
($cname:literal) => {
trace_duration!(CATEGORY_STARNIX_SECURITY, $cname);
};
}
bitflags::bitflags! {
/// The flags about which permissions should be checked when opening an FsNode. Used in the
/// `fs_node_permission()` hook.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct PermissionFlags: u32 {
const EXEC = 1 as u32;
const WRITE = 2 as u32;
const READ = 4 as u32;
const APPEND = 8 as u32;
// Internal flag used to indicate that the check is being made on behalf of userspace e.g.
// via the `access()` syscall.
const ACCESS = 16 as u32;
// TODO: https://fxbug.dev/455782510 - Remove this once all fs_node_permission() calls are
// enforced.
const FOR_OPEN = 32 as u32;
}
}
impl PermissionFlags {
pub fn as_access(&self) -> Access {
let mut access = Access::empty();
if self.contains(PermissionFlags::READ) {
access |= Access::READ;
}
if self.contains(PermissionFlags::WRITE) {
// `APPEND` only modifies the behaviour of `WRITE` if set, so it is sufficient to only
// consider whether `WRITE` is set, to calculate the `Access` flags.
access |= Access::WRITE;
}
if self.contains(PermissionFlags::EXEC) {
access |= Access::EXEC;
}
access
}
}
impl From<Access> for PermissionFlags {
fn from(access: Access) -> Self {
// Note that `Access` doesn't have an `append` bit.
let mut permissions = PermissionFlags::empty();
if access.contains(Access::READ) {
permissions |= PermissionFlags::READ;
}
if access.contains(Access::WRITE) {
permissions |= PermissionFlags::WRITE;
}
if access.contains(Access::EXEC) {
permissions |= PermissionFlags::EXEC;
}
permissions
}
}
impl From<ProtectionFlags> for PermissionFlags {
fn from(protection_flags: ProtectionFlags) -> Self {
let mut flags = PermissionFlags::empty();
if protection_flags.contains(ProtectionFlags::READ) {
flags |= PermissionFlags::READ;
}
if protection_flags.contains(ProtectionFlags::WRITE) {
flags |= PermissionFlags::WRITE;
}
if protection_flags.contains(ProtectionFlags::EXEC) {
flags |= PermissionFlags::EXEC;
}
flags
}
}
impl From<OpenFlags> for PermissionFlags {
fn from(flags: OpenFlags) -> Self {
let mut permissions = PermissionFlags::empty();
if flags.can_read() {
permissions |= PermissionFlags::READ;
}
if flags.can_write() {
permissions |= PermissionFlags::WRITE;
if flags.contains(OpenFlags::APPEND) {
permissions |= PermissionFlags::APPEND;
}
}
permissions
}
}
impl From<MapFlags> for PermissionFlags {
fn from(bpf_flags: MapFlags) -> Self {
if bpf_flags.contains(MapFlags::SyscallReadOnly) {
PermissionFlags::READ
} else if bpf_flags.contains(MapFlags::SyscallWriteOnly) {
PermissionFlags::WRITE
} else {
PermissionFlags::READ | PermissionFlags::WRITE
}
}
}
/// The flags about the PerfEvent types.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PerfEventType {
Hardware,
Software,
Tracepoint,
Raw,
HwCache,
Breakpoint,
}
// TODO(https://github.com/rust-lang/rust/issues/39371): remove
#[allow(non_upper_case_globals)]
impl TryFrom<perf_type_id> for PerfEventType {
type Error = Errno;
fn try_from(type_id: perf_type_id) -> Result<Self, Errno> {
match type_id {
perf_type_id_PERF_TYPE_HARDWARE => Ok(Self::Hardware),
perf_type_id_PERF_TYPE_SOFTWARE => Ok(Self::Software),
perf_type_id_PERF_TYPE_TRACEPOINT => Ok(Self::Tracepoint),
perf_type_id_PERF_TYPE_RAW => Ok(Self::Raw),
perf_type_id_PERF_TYPE_HW_CACHE => Ok(Self::HwCache),
perf_type_id_PERF_TYPE_BREAKPOINT => Ok(Self::Breakpoint),
_ => {
return error!(ENOTSUP);
}
}
}
}
/// The target task type. Used in the `check_perf_event_open_access` LSM hook.
pub enum TargetTaskType<'a> {
/// Monitor all tasks/activities.
AllTasks,
/// Only monitor the current task.
CurrentTask,
/// Only monitor a specific task.
Task(&'a Task),
}
/// Executes the `hook` closure if SELinux is enabled, and has a policy loaded.
/// If SELinux is not enabled, or has no policy loaded, then the `default` closure is executed,
/// and its result returned.
fn if_selinux_else_with_context<F, R, D, C>(context: C, task: &Task, hook: F, default: D) -> R
where
F: FnOnce(C, &Arc<SecurityServer>) -> R,
D: Fn(C) -> R,
{
if let Some(state) = task.kernel().security_state.state.as_ref() {
if state.has_policy() { hook(context, &state.server) } else { default(context) }
} else {
default(context)
}
}
/// Executes the `hook` closure if SELinux is enabled, and has a policy loaded.
/// If SELinux is not enabled, or has no policy loaded, then the `default` closure is executed,
/// and its result returned.
fn if_selinux_else<F, R, D>(task: &Task, hook: F, default: D) -> R
where
F: FnOnce(&Arc<SecurityServer>) -> R,
D: Fn() -> R,
{
if_selinux_else_with_context(
(),
task,
|_, security_server| hook(security_server),
|_| default(),
)
}
/// Specialization of `if_selinux_else(...)` for hooks which return a `Result<..., Errno>`, that
/// arranges to return a default `Ok(...)` result value if SELinux is not enabled, or not yet
/// configured with a policy.
fn if_selinux_else_default_ok_with_context<R, F, C>(
context: C,
task: &Task,
hook: F,
) -> Result<R, Errno>
where
F: FnOnce(C, &Arc<SecurityServer>) -> Result<R, Errno>,
R: Default,
{
if_selinux_else_with_context(context, task, hook, |_| Ok(R::default()))
}
/// Specialization of `if_selinux_else(...)` for hooks which return a `Result<..., Errno>`, that
/// arranges to return a default `Ok(...)` result value if SELinux is not enabled, or not yet
/// configured with a policy.
fn if_selinux_else_default_ok<R, F>(task: &Task, hook: F) -> Result<R, Errno>
where
F: FnOnce(&Arc<SecurityServer>) -> Result<R, Errno>,
R: Default,
{
if_selinux_else(task, hook, || Ok(R::default()))
}
/// Returns the security state structure for the kernel, based on the supplied "selinux" argument
/// contents.
pub fn kernel_init_security(
enabled: bool,
options: String,
exceptions: Vec<String>,
) -> KernelState {
track_hook_duration!("security.hooks.kernel_init_security");
KernelState { state: enabled.then(|| selinux_hooks::kernel_init_security(options, exceptions)) }
}
/// Checks whether the given `current_task` can become the binder context manager.
/// Corresponds to the `binder_set_context_mgr` hook.
pub fn binder_set_context_mgr(current_task: &CurrentTask) -> Result<(), Errno> {
track_hook_duration!("security.hooks.binder_set_context_mgr");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::binder::binder_set_context_mgr(security_server, current_task)
})
}
/// Checks whether the given `current_task` can perform a transaction to `target_task`.
/// Corresponds to the `binder_transaction` hook.
pub fn binder_transaction(
current_task: &CurrentTask,
target_task: &Task,
connection_state: &BinderConnectionState,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.binder_transaction");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::binder::binder_transaction(
security_server,
&connection_state.state,
current_task,
target_task,
)
})
}
/// Checks whether the given `current_task` can transfer Binder objects to `target_task`.
/// Corresponds to the `binder_transfer_binder` hook.
pub fn binder_transfer_binder(current_task: &CurrentTask, target_task: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.binder_transfer_binder");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::binder::binder_transfer_binder(security_server, current_task, target_task)
})
}
/// Checks whether the given `receiving_task` can receive `file` in a Binder transaction.
/// Corresponds to the `binder_transfer_file` hook.
pub fn binder_transfer_file(
current_task: &CurrentTask,
receiving_task: &Task,
file: &FileObject,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.binder_transfer_file");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::binder::binder_transfer_file(
security_server,
current_task,
receiving_task,
file,
)
})
}
/// Returns the serialized Security Context associated with the specified state.
/// If the state's SID cannot be resolved then None is returned.
pub fn binder_get_context(
current_task: &CurrentTask,
connection_state: &BinderConnectionState,
) -> Option<Vec<u8>> {
track_hook_duration!("security.hooks.binder_get_context");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::binder::binder_get_context(&security_server, &connection_state.state)
},
|| None,
)
}
/// Consumes the mount options from the supplied `MountParams` and returns the security mount
/// options for the given `MountParams`.
/// Corresponds to the `sb_eat_lsm_opts` hook.
pub fn sb_eat_lsm_opts(
kernel: &Kernel,
mount_params: &mut MountParams,
) -> Result<FileSystemMountOptions, Errno> {
track_hook_duration!("security.hooks.sb_eat_lsm_opts");
if kernel.security_state.state.is_some() {
return selinux_hooks::superblock::sb_eat_lsm_opts(mount_params);
}
Ok(FileSystemMountOptions::default())
}
/// Returns security state to associate with a filesystem based on the supplied mount options.
/// This sits somewhere between `fs_context_parse_param()` and `sb_set_mnt_opts()` in function.
pub fn file_system_init_security(
mount_options: &FileSystemMountOptions,
ops: &dyn FileSystemOps,
) -> Result<FileSystemState, Errno> {
track_hook_duration!("security.hooks.file_system_init_security");
Ok(FileSystemState {
state: selinux_hooks::superblock::file_system_init_security(mount_options, ops)?,
})
}
/// Gives the hooks subsystem an opportunity to note that the new `file_system` needs labeling, if
/// SELinux is enabled, but no policy has yet been loaded.
// TODO: https://fxbug.dev/366405587 - Merge this logic into `file_system_resolve_security()` and
// remove this extra hook.
pub fn file_system_post_init_security(kernel: &Kernel, file_system: &FileSystemHandle) {
track_hook_duration!("security.hooks.file_system_post_init_security");
if let Some(state) = &kernel.security_state.state {
if !state.has_policy() {
// TODO: https://fxbug.dev/367585803 - Revise locking to guard against a policy load
// sneaking in, in-between `has_policy()` and this `insert()`.
log_debug!("Queuing {} FileSystem for labeling", file_system.name());
state.pending_file_systems.lock().insert(WeakKey::from(&file_system));
}
}
}
/// Resolves the labeling scheme and arguments for the `file_system`, based on the loaded policy.
/// If no policy has yet been loaded then no work is done, and the `file_system` will instead be
/// labeled when a policy is first loaded.
/// If the `file_system` was already labeled then no further work is done.
pub fn file_system_resolve_security<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
file_system: &FileSystemHandle,
) -> Result<(), Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
track_hook_duration!("security.hooks.file_system_resolve_security");
if_selinux_else_default_ok_with_context(locked, current_task, |locked, security_server| {
selinux_hooks::superblock::file_system_resolve_security(
locked,
security_server,
current_task,
file_system,
)
})
}
/// Used to return an extended attribute name and value to apply to a [`crate::vfs::FsNode`].
pub struct FsNodeSecurityXattr {
pub name: &'static FsStr,
pub value: FsString,
}
/// Checks whether the `current_task` is allowed to mmap `file` or memory using the given
/// [`ProtectionFlags`] and [`MappingOptions`].
/// Corresponds to the `mmap_file()` LSM hook.
pub fn mmap_file(
current_task: &CurrentTask,
file: Option<&FileHandle>,
protection_flags: ProtectionFlags,
options: MappingOptions,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.mmap_file");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::file::mmap_file(
security_server,
current_task,
file,
protection_flags,
options,
)
})
}
/// Checks whether `current_task` is allowed to request setting the memory protection of
/// `mapping` to `prot`.
/// Corresponds to the `file_mprotect` LSM hook.
pub fn file_mprotect(
current_task: &CurrentTask,
range: &Range<UserAddress>,
mapping: &Mapping,
prot: ProtectionFlags,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.file_mprotect");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::file::file_mprotect(security_server, current_task, range, mapping, prot)
})
}
/// Checks whether the `current_task` has the specified `permission_flags` to the `file`.
/// Corresponds to the `file_permission()` LSM hook.
pub fn file_permission(
current_task: &CurrentTask,
file: &FileObject,
permission_flags: PermissionFlags,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.file_permission");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::file::file_permission(security_server, current_task, file, permission_flags)
})
}
/// Called by the VFS to initialize the security state for an `FsNode` that is being linked at
/// `dir_entry`.
/// If the `FsNode` security state had already been initialized, or no policy is yet loaded, then
/// this is a no-op.
/// Corresponds to the `d_instantiate()` LSM hook.
pub fn fs_node_init_with_dentry<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
dir_entry: &DirEntryHandle,
) -> Result<(), Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
track_hook_duration!("security.hooks.fs_node_init_with_dentry");
// TODO: https://fxbug.dev/367585803 - Don't use `if_selinux_else()` here, because the `has_policy()`
// check is racey, so doing non-trivial work in the "else" path is unsafe. Instead, call the SELinux
// hook implementation, and let it label, or queue, the `FsNode` based on the `FileSystem` label
// state, thereby ensuring safe ordering.
if let Some(state) = &current_task.kernel().security_state.state {
selinux_hooks::fs_node::fs_node_init_with_dentry(
Some(locked.cast_locked()),
&state.server,
current_task,
dir_entry,
)
} else {
Ok(())
}
}
pub fn fs_node_init_with_dentry_no_xattr(
current_task: &CurrentTask,
dir_entry: &DirEntryHandle,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.fs_node_init_with_dentry_no_xattr");
// TODO: https://fxbug.dev/367585803 - Don't use `if_selinux_else()` here, because the `has_policy()`
// check is racey, so doing non-trivial work in the "else" path is unsafe. Instead, call the SELinux
// hook implementation, and let it label, or queue, the `FsNode` based on the `FileSystem` label
// state, thereby ensuring safe ordering.
if let Some(state) = &current_task.kernel().security_state.state {
// Sockets are currently implemented using `Anon` nodes, and may be kernel-private, in
// which case delegate to the anonymous node initializer to apply a placeholder label.
if Anon::is_private(&dir_entry.node) {
return selinux_hooks::fs_node::fs_node_init_anon(
&state.server,
current_task,
&dir_entry.node,
"",
);
}
selinux_hooks::fs_node::fs_node_init_with_dentry(
None,
&state.server,
current_task,
dir_entry,
)
} else {
Ok(())
}
}
// Temporary work-around for lack of a `CurrentTask` during creation of `DirEntry`s for some initial
// file-systems.
// TODO: https://fxbug.dev/455771186 - Clean up with-DirEntry initialization and remove this.
pub fn fs_node_init_with_dentry_deferred(kernel: &Kernel, dir_entry: &DirEntryHandle) {
track_hook_duration!("security.hooks.fs_node_init_with_dentry_no_xattr");
if kernel.security_state.state.is_some() {
selinux_hooks::fs_node::fs_node_init_with_dentry_deferred(dir_entry);
}
}
/// Applies the given label to the given node without checking any permissions.
/// Used by file-system implementations to set the label for a node, for example when it has
/// prefetched the label in the xattr rather than letting it get fetched by
/// `fs_node_init_with_dentry` later. Calling this doesn't need to exclude the use of
/// `fs_node_init_with_dentry`, it will just turn that call into a fast no-op.
/// Corresponds to the `inode_notifysecctx` LSM hook.
pub fn fs_node_notify_security_context(
current_task: &CurrentTask,
fs_node: &FsNode,
context: &FsStr,
) -> Result<(), Errno> {
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::fs_node::fs_node_notify_security_context(
security_server,
fs_node,
context,
)
},
|| error!(ENOTSUP),
)
}
/// Called by file-system implementations when creating the `FsNode` for a new file, to determine the
/// correct label based on the `CurrentTask` and `parent` node, and the policy-defined transition
/// rules, and to initialize the `FsNode`'s security state accordingly.
/// If no policy has yet been loaded then this is a no-op; if the `FsNode` corresponds to an xattr-
/// labeled file then it will receive the file-system's "default" label once a policy is loaded.
/// Returns an extended attribute value to set on the newly-created file if the labeling scheme is
/// `fs_use_xattr`. For other labeling schemes (e.g. `fs_use_trans`, mountpoint-labeling) a label
/// is set on the `FsNode` security state, but no extended attribute is set nor returned.
/// The `name` with which the new node is being created allows name-conditional `type_transition`
/// rules to be applied when determining the label for the `new_node`.
/// Corresponds to the `inode_init_security()` LSM hook.
pub fn fs_node_init_on_create(
current_task: &CurrentTask,
new_node: &FsNode,
parent: &FsNode,
name: &FsStr,
) -> Result<Option<FsNodeSecurityXattr>, Errno> {
track_hook_duration!("security.hooks.fs_node_init_on_create");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::fs_node_init_on_create(
security_server,
current_task,
new_node,
Some(parent),
name,
)
})
}
/// Called on creation of anonymous [`crate::vfs::FsNode`]s. APIs that create file-descriptors that
/// are not linked into any filesystem directory structure create anonymous nodes, labeled by this
/// hook rather than `fs_node_init_on_create()` above.
/// Corresponds to the `inode_init_security_anon()` LSM hook.
pub fn fs_node_init_anon(
current_task: &CurrentTask,
new_node: &FsNode,
node_type: &str,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.fs_node_init_anon");
if let Some(state) = current_task.kernel().security_state.state.as_ref() {
selinux_hooks::fs_node::fs_node_init_anon(&state.server, current_task, new_node, node_type)
} else {
Ok(())
}
}
/// Validate that `current_task` has permission to create a regular file in the `parent` directory,
/// with the specified file `mode`.
/// Corresponds to the `inode_create()` LSM hook.
pub fn check_fs_node_create_access(
current_task: &CurrentTask,
parent: &FsNode,
mode: FileMode,
name: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_create_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_create_access(
security_server,
current_task,
parent,
mode,
name,
)
})
}
/// Validate that `current_task` has permission to create a symlink to `old_path` in the `parent`
/// directory.
/// Corresponds to the `inode_symlink()` LSM hook.
pub fn check_fs_node_symlink_access(
current_task: &CurrentTask,
parent: &FsNode,
name: &FsStr,
old_path: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_symlink_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_symlink_access(
security_server,
current_task,
parent,
name,
old_path,
)
})
}
/// Validate that `current_task` has permission to create a new directory in the `parent` directory,
/// with the specified file `mode`.
/// Corresponds to the `inode_mkdir()` LSM hook.
pub fn check_fs_node_mkdir_access(
current_task: &CurrentTask,
parent: &FsNode,
mode: FileMode,
name: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_mkdir_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_mkdir_access(
security_server,
current_task,
parent,
mode,
name,
)
})
}
/// Validate that `current_task` has permission to create a new special file, socket or pipe, in the
/// `parent` directory, and with the specified file `mode` and `device_id`.
/// For consistency any calls to `mknod()` with a file `mode` specifying a regular file will be
/// validated by `check_fs_node_create_access()` rather than by this hook.
/// Corresponds to the `inode_mknod()` LSM hook.
pub fn check_fs_node_mknod_access(
current_task: &CurrentTask,
parent: &FsNode,
mode: FileMode,
name: &FsStr,
device_id: DeviceType,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_mknod_access");
assert!(!mode.is_reg());
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_mknod_access(
security_server,
current_task,
parent,
mode,
name,
device_id,
)
})
}
/// Validate that `current_task` has the permission to create a new hard link to a file.
/// Corresponds to the `inode_link()` LSM hook.
pub fn check_fs_node_link_access(
current_task: &CurrentTask,
parent: &FsNode,
child: &FsNode,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_link_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_link_access(
security_server,
current_task,
parent,
child,
)
})
}
/// Validate that `current_task` has the permission to remove a hard link to a file.
/// Corresponds to the `inode_unlink()` LSM hook.
pub fn check_fs_node_unlink_access(
current_task: &CurrentTask,
parent: &FsNode,
child: &FsNode,
name: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_unlink_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_unlink_access(
security_server,
current_task,
parent,
child,
name,
)
})
}
/// Validate that `current_task` has the permission to remove a directory.
/// Corresponds to the `inode_rmdir()` LSM hook.
pub fn check_fs_node_rmdir_access(
current_task: &CurrentTask,
parent: &FsNode,
child: &FsNode,
name: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_rmdir_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_rmdir_access(
security_server,
current_task,
parent,
child,
name,
)
})
}
/// Checks whether the `current_task` can rename the file or directory `moving_node`.
/// If the rename replaces an existing node, `replaced_node` must contain a reference to the
/// existing node.
/// Corresponds to the `inode_rename()` LSM hook.
pub fn check_fs_node_rename_access(
current_task: &CurrentTask,
old_parent: &FsNode,
moving_node: &FsNode,
new_parent: &FsNode,
replaced_node: Option<&FsNode>,
old_basename: &FsStr,
new_basename: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_rename_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_rename_access(
security_server,
current_task,
old_parent,
moving_node,
new_parent,
replaced_node,
old_basename,
new_basename,
)
})
}
/// Checks whether the `current_task` can read the symbolic link in `fs_node`.
/// Corresponds to the `inode_readlink()` LSM hook.
pub fn check_fs_node_read_link_access(
current_task: &CurrentTask,
fs_node: &FsNode,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_read_link_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_read_link_access(
security_server,
current_task,
fs_node,
)
})
}
/// Checks whether the `current_task` can access an inode.
/// Corresponds to the `inode_permission()` LSM hook.
pub fn fs_node_permission(
current_task: &CurrentTask,
fs_node: &FsNode,
permission_flags: PermissionFlags,
audit_context: Auditable<'_>,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.fs_node_permission");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::fs_node_permission(
security_server,
current_task,
fs_node,
permission_flags,
audit_context,
)
})
}
/// Returns whether the `current_task` can receive `file` via a socket IPC.
/// Corresponds to the `file_receive()` LSM hook.
pub fn file_receive(current_task: &CurrentTask, file: &FileObject) -> Result<(), Errno> {
track_hook_duration!("security.hooks.file_receive");
if_selinux_else_default_ok(current_task, |security_server| {
let receiving_sid = current_task_state(current_task).lock().current_sid;
selinux_hooks::file::file_receive(security_server, current_task, receiving_sid, file)
})
}
/// Returns the security state for a new file object created by `current_task`.
/// Corresponds to the `file_alloc_security()` LSM hook.
pub fn file_alloc_security(current_task: &CurrentTask) -> FileObjectState {
track_hook_duration!("security.hooks.file_alloc_security");
FileObjectState { state: selinux_hooks::file::file_alloc_security(current_task) }
}
/// Returns the security context to be assigned to a BinderConnection, based on the task that
/// creates it.
pub fn binder_connection_alloc(current_task: &CurrentTask) -> BinderConnectionState {
track_hook_duration!("security.hooks.binder_connection_alloc");
BinderConnectionState { state: selinux_hooks::binder::binder_connection_alloc(current_task) }
}
/// Returns the security context to be assigned to a BPM map object, based on the task that
/// creates it.
/// Corresponds to the `bpf_map_alloc_security()` LSM hook.
pub fn bpf_map_alloc(current_task: &CurrentTask) -> BpfMapState {
track_hook_duration!("security.hooks.bpf_map_alloc");
BpfMapState { state: selinux_hooks::bpf::bpf_map_alloc(current_task) }
}
/// Returns the security context to be assigned to a BPM program object, based on the task
/// that creates it.
/// Corresponds to the `bpf_prog_alloc_security()` LSM hook.
pub fn bpf_prog_alloc(current_task: &CurrentTask) -> BpfProgState {
track_hook_duration!("security.hooks.bpf_prog_alloc");
BpfProgState { state: selinux_hooks::bpf::bpf_prog_alloc(current_task) }
}
/// Returns whether `current_task` can issue an ioctl to `file`.
/// Corresponds to the `file_ioctl()` LSM hook.
pub fn check_file_ioctl_access(
current_task: &CurrentTask,
file: &FileObject,
request: u32,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_file_ioctl_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::file::check_file_ioctl_access(security_server, current_task, file, request)
})
}
/// Sets the security context of `CurrentTask` to be appropriate for a copy up operation
/// on `fs_node`, then call `do_copy_up`.
/// The task's security context will be reset before returning.
///
/// Corresponds to the `security_inode_copy_up()` LSM hook.
pub fn fs_node_copy_up<R>(
current_task: &CurrentTask,
fs_node: &FsNode,
do_copy_up: impl FnOnce() -> R,
) -> R {
if_selinux_else_with_context(
do_copy_up,
current_task,
|do_copy_up, _security_server| {
selinux_hooks::fs_node::fs_node_copy_up(current_task, fs_node, do_copy_up)
},
|do_copy_up| do_copy_up(),
)
}
/// This hook is called by the `flock` syscall. Returns whether `current_task` can perform
/// a lock operation on the given file.
///
/// See also `check_file_fcntl_access()` for `lock` permission checks performed after an
/// fcntl lock request.
///
/// Corresponds to the `file_lock()` LSM hook.
pub fn check_file_lock_access(current_task: &CurrentTask, file: &FileObject) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_file_lock_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::file::check_file_lock_access(security_server, current_task, file)
})
}
/// Returns whether `current_task` has the permissions to execute this fcntl syscall.
/// Corresponds to the `file_fcntl()` LSM hook.
pub fn check_file_fcntl_access(
current_task: &CurrentTask,
file: &FileObject,
fcntl_cmd: u32,
fcntl_arg: u64,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_file_fcntl_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::file::check_file_fcntl_access(
security_server,
current_task,
file,
fcntl_cmd,
fcntl_arg,
)
})
}
/// Checks whether `current_task` can set attributes on `node`.
/// Corresponds to the `inode_setattr()` LSM hook.
pub fn check_fs_node_setattr_access(
current_task: &CurrentTask,
node: &FsNode,
attributes: &zxio_node_attr_has_t,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_setattr_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_setattr_access(
security_server,
current_task,
node,
attributes,
)
})
}
/// Return the default initial `TaskState` for kernel tasks.
/// Corresponds to the `task_alloc()` LSM hook, in the special case when current_task is null.
pub fn task_alloc_for_kernel() -> TaskState {
track_hook_duration!("security.hooks.task_alloc_for_kernel");
TaskState(Arc::new(selinux_hooks::TaskAttrs::for_kernel().into()))
}
/// Returns `TaskState` for a new `Task`, based on that of `current_task`, and the specified clone
/// flags.
/// Corresponds to the `task_alloc()` LSM hook.
pub fn task_alloc(current_task: &CurrentTask, clone_flags: u64) -> TaskState {
track_hook_duration!("security.hooks.task_alloc");
TaskState(Arc::new(selinux_hooks::task::task_alloc(current_task, clone_flags).into()))
}
/// Labels an [`crate::vfs::FsNode`], by attaching a pseudo-label to the `fs_node`, which allows
/// indirect resolution of the effective label. Makes the security attributes of `fs_node` track the
/// `task`'s security attributes, even if the task's security attributes change. Called for the
/// /proc/<pid> `FsNode`s when they are created.
/// Corresponds to the `task_to_inode` LSM hook.
pub fn task_to_fs_node(current_task: &CurrentTask, task: &TempRef<'_, Task>, fs_node: &FsNode) {
track_hook_duration!("security.hooks.task_to_fs_node");
// The fs_node_init_with_task hook doesn't require any policy-specific information. Only check
// if SElinux is enabled before running it.
if current_task.kernel().security_state.state.is_some() {
selinux_hooks::task::fs_node_init_with_task(task, &fs_node);
}
}
/// Returns `TaskState` for a new `Task`, based on that of the provided `context`.
/// The effect is similar to combining the `task_alloc()` and `setprocattr()` LSM hooks, with the
/// difference that no access-checks are performed, and the "#<name>" syntax may be used to
/// have the `Task` assigned one of the "initial" Security Contexts, to allow components to be run
/// prior to a policy being loaded.
pub fn task_for_context(task: &Task, context: &FsStr) -> Result<TaskState, Errno> {
track_hook_duration!("security.hooks.task_for_context");
Ok(TaskState(Arc::new(
if let Some(kernel_state) = task.kernel().security_state.state.as_ref() {
selinux_hooks::task::task_alloc_from_context(&kernel_state.server, context)
} else {
Ok(selinux_hooks::TaskAttrs::for_selinux_disabled())
}?
.into(),
)))
}
/// Returns true if there exits a `dontaudit` rule for `current_task` access to `fs_node`, which
/// includes the `audit_access` pseudo-permission.
/// This appears to be handled via additional options & flags in other hooks, by LSM.
pub fn has_dontaudit_access(current_task: &CurrentTask, fs_node: &FsNode) -> bool {
track_hook_duration!("security.hooks.has_dontaudit_access");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::fs_node::has_dontaudit_access(security_server, current_task, fs_node)
},
|| false,
)
}
/// Returns true if a task has the specified `capability`.
/// Corresponds to the `capable()` LSM hook invoked with a no-audit flag set.
pub fn is_task_capable_noaudit(
current_task: &CurrentTask,
capability: starnix_uapi::auth::Capabilities,
) -> bool {
track_hook_duration!("security.hooks.is_task_capable_noaudit");
return common_cap::capable(current_task, capability).is_ok()
&& if_selinux_else(
current_task,
|security_server| {
selinux_hooks::task::is_task_capable_noaudit(
&security_server.as_permission_check(),
&current_task,
capability,
)
},
|| true,
);
}
/// Checks if a task has the specified `capability`.
/// Corresponds to the `capable()` LSM hook.
pub fn check_task_capable(
current_task: &CurrentTask,
capability: starnix_uapi::auth::Capabilities,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_task_capable");
common_cap::capable(current_task, capability)?;
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::task::check_task_capable(
&security_server.as_permission_check(),
&current_task,
capability,
)
})
}
/// Checks if creating a task is allowed.
/// Directly maps to the `selinux_task_create` LSM hook from the original NSA white paper.
/// Partially corresponds to the `task_alloc()` LSM hook. Compared to `task_alloc()`,
/// this hook doesn't actually modify the task's label, but instead verifies whether the task has
/// the "fork" permission on itself.
pub fn check_task_create_access(current_task: &CurrentTask) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_task_create_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::task::check_task_create_access(
&security_server.as_permission_check(),
current_task,
)
})
}
/// Checks if exec is allowed and if so, checks permissions related to the transition
/// (if any) from the pre-exec security context to the post-exec context.
///
/// Corresponds to the `bprm_creds_for_exec()` LSM hook.
pub fn bprm_creds_for_exec(
current_task: &CurrentTask,
executable: &NamespaceNode,
) -> Result<ResolvedElfState, Errno> {
track_hook_duration!("security.hooks.bprm_creds_for_exec");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::task::bprm_creds_for_exec(&security_server, current_task, executable)
},
|| Ok(ResolvedElfState { sid: None, require_secure_exec: false }),
)
}
/// Checks if creating a socket is allowed.
/// Corresponds to the `socket_create()` LSM hook.
pub fn check_socket_create_access<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
domain: SocketDomain,
socket_type: SocketType,
protocol: SocketProtocol,
kernel_private: bool,
) -> Result<(), Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
track_hook_duration!("security.hooks.socket_create");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_create_access(
locked,
&security_server,
current_task,
domain,
socket_type,
protocol,
kernel_private,
)
})
}
/// Sets the peer security context for each socket in the pair.
/// Corresponds to the `socket_socketpair()` LSM hook.
pub fn socket_socketpair(
current_task: &CurrentTask,
left: DowncastedFile<'_, SocketFile>,
right: DowncastedFile<'_, SocketFile>,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.socket_socketpair");
if_selinux_else_default_ok(current_task, |_| {
selinux_hooks::socket::socket_socketpair(left, right)
})
}
/// Computes and updates the socket security class associated with a new socket.
/// Corresponds to the `socket_post_create()` LSM hook.
pub fn socket_post_create(socket: &Socket) {
track_hook_duration!("security.hooks.socket_post_create");
selinux_hooks::socket::socket_post_create(socket);
}
/// Checks if the `current_task` is allowed to perform a bind operation for this `socket`.
/// Corresponds to the `socket_bind()` LSM hook.
pub fn check_socket_bind_access(
current_task: &CurrentTask,
socket: &Socket,
socket_address: &SocketAddress,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_bind_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_bind_access(
&security_server,
current_task,
socket,
socket_address,
)
})
}
/// Checks if the `current_task` is allowed to initiate a connection with `socket`.
/// Corresponds to the `socket_connect()` LSM hook.
pub fn check_socket_connect_access(
current_task: &CurrentTask,
socket: DowncastedFile<'_, SocketFile>,
socket_peer: &SocketPeer,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_connect_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_connect_access(
&security_server,
current_task,
socket,
socket_peer,
)
})
}
/// Checks if the `current_task` is allowed to listen on `socket_node`.
/// Corresponds to the `socket_listen()` LSM hook.
pub fn check_socket_listen_access(
current_task: &CurrentTask,
socket: &Socket,
backlog: i32,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_listen_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_listen_access(
&security_server,
current_task,
socket,
backlog,
)
})
}
/// Checks if the `current_task` is allowed to accept connections on `listening_socket`. Sets
/// the security label and SID for the accepted socket to match those of the listening socket.
/// Corresponds to the `socket_accept()` LSM hook.
pub fn socket_accept(
current_task: &CurrentTask,
listening_socket: DowncastedFile<'_, SocketFile>,
accepted_socket: DowncastedFile<'_, SocketFile>,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_getname_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::socket_accept(
&security_server,
current_task,
listening_socket,
accepted_socket,
)
})
}
/// Checks if the `current_task` is allowed to get socket options on `socket`.
/// Corresponds to the `socket_getsockopt()` LSM hook.
pub fn check_socket_getsockopt_access(
current_task: &CurrentTask,
socket: &Socket,
level: u32,
optname: u32,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_getsockopt_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_getsockopt_access(
&security_server,
current_task,
socket,
level,
optname,
)
})
}
/// Checks if the `current_task` is allowed to set socket options on `socket`.
/// Corresponds to the `socket_getsockopt()` LSM hook.
pub fn check_socket_setsockopt_access(
current_task: &CurrentTask,
socket: &Socket,
level: u32,
optname: u32,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_setsockopt_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_setsockopt_access(
&security_server,
current_task,
socket,
level,
optname,
)
})
}
/// Checks if the `current_task` is allowed to send a message on `socket`.
/// Corresponds to the `socket_sendmsg()` LSM hook.
pub fn check_socket_sendmsg_access(
current_task: &CurrentTask,
socket: &Socket,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_sendmsg_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_sendmsg_access(&security_server, current_task, socket)
})
}
/// Checks if the `current_task` is allowed to receive a message on `socket`.
/// Corresponds to the `socket_recvmsg()` LSM hook.
pub fn check_socket_recvmsg_access(
current_task: &CurrentTask,
socket: &Socket,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_recvmsg_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_recvmsg_access(&security_server, current_task, socket)
})
}
/// Checks if the `current_task` is allowed to get the local name of `socket`.
/// Corresponds to the `socket_getsockname()` LSM hook.
pub fn check_socket_getsockname_access(
current_task: &CurrentTask,
socket: &Socket,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_getname_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_getname_access(&security_server, current_task, socket)
})
}
/// Checks if the `current_task` is allowed to get the remote name of `socket`.
/// Corresponds to the `socket_getpeername()` LSM hook.
pub fn check_socket_getpeername_access(
current_task: &CurrentTask,
socket: &Socket,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_getname_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_getname_access(&security_server, current_task, socket)
})
}
/// Checks if the `current_task` is allowed to shutdown `socket`.
/// Corresponds to the `socket_shutdown()` LSM hook.
pub fn check_socket_shutdown_access(
current_task: &CurrentTask,
socket: &Socket,
how: SocketShutdownFlags,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_socket_shutdown_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_socket_shutdown_access(
&security_server,
current_task,
socket,
how,
)
})
}
/// Returns the Security Context with which the [`crate::vfs::Socket`]'s peer is labeled.
/// Corresponds to the `socket_getpeersec_stream()` LSM hook.
pub fn socket_getpeersec_stream(
current_task: &CurrentTask,
socket: &Socket,
) -> Result<Vec<u8>, Errno> {
track_hook_duration!("security.hooks.socket_getpeersec_stream");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::socket_getpeersec_stream(&security_server, current_task, socket)
})
}
/// Returns the Security Context with which the [`crate::vfs::Socket`]'s is labeled, to return to
/// the recipient via `SCM_SECURITY` auxiliary data, if `SO_PASSSEC` is set.
/// Corresponds to the `socket_getpeersec_dgram()` LSM hook.
pub fn socket_getpeersec_dgram(current_task: &CurrentTask, socket: &Socket) -> Vec<u8> {
track_hook_duration!("security.hooks.socket_getpeersec_dgram");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::socket::socket_getpeersec_dgram(&security_server, current_task, socket)
},
Vec::default,
)
}
/// Checks if the Unix domain `sending_socket` is allowed to send a message to the
/// `receiving_socket`.
/// Corresponds to the `unix_may_send()` LSM hook.
pub fn unix_may_send(
current_task: &CurrentTask,
sending_socket: &Socket,
receiving_socket: &Socket,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.unix_may_send");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::unix_may_send(
&security_server,
current_task,
sending_socket,
receiving_socket,
)
})
}
/// Checks if the Unix domain `client_socket` is allowed to connect to `listening_socket`, and
/// initialises the peer information in the client and server sockets.
/// Corresponds to the `unix_stream_connect()` LSM hook.
pub fn unix_stream_connect(
current_task: &CurrentTask,
client_socket: &Socket,
listening_socket: &Socket,
server_socket: &Socket,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.unix_stream_connect");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::unix_stream_connect(
&security_server,
current_task,
client_socket,
listening_socket,
server_socket,
)
})
}
/// Checks if the `current_task` is allowed to send a message of `message_type` on the Netlink
/// `socket`.
/// Corresponds to the `netlink_send()` LSM hook.
pub fn check_netlink_send_access(
current_task: &CurrentTask,
socket: &Socket,
message_type: u16,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_netlink_send_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::netlink_socket::check_netlink_send_access(
&security_server,
current_task,
socket,
message_type,
)
})
}
/// Checks if the `current_task` has permission to create a new TUN device.
/// Corresponds to the `tun_dev_create()` LSM hook.
pub fn check_tun_dev_create_access(current_task: &CurrentTask) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_tun_dev_create_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::socket::check_tun_dev_create_access(&security_server, current_task)
})
}
/// Updates the SELinux thread group state on exec.
/// Corresponds to the `exec_binprm` function described in the SELinux Notebook.
///
/// Resets state that should not be inherited during an `exec` domain transition. Then updates the
/// current task's SID based on the security state of the resolved executable.
pub fn exec_binprm(
locked: &mut Locked<Unlocked>,
current_task: &CurrentTask,
elf_security_state: &ResolvedElfState,
) {
track_hook_duration!("security.hooks.exec_binprm");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::task::exec_binprm(
locked,
security_server,
current_task,
elf_security_state,
)
},
|| (),
);
}
/// Checks if `source` may exercise the "getsched" permission on `target`.
/// Corresponds to the `task_getscheduler()` LSM hook.
pub fn check_getsched_access(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_getsched_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_getsched_access(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Checks if setsched is allowed.
/// Corresponds to the `task_setscheduler()` LSM hook.
pub fn check_setsched_access(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_setsched_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_setsched_access(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Checks if getpgid is allowed.
/// Corresponds to the `task_getpgid()` LSM hook.
pub fn check_getpgid_access(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_getpgid_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_getpgid_access(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Checks if setpgid is allowed.
/// Corresponds to the `task_setpgid()` LSM hook.
pub fn check_setpgid_access(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_setpgid_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_setpgid_access(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Called when the current task queries the session Id of the `target` task.
/// Corresponds to the `task_getsid()` LSM hook.
pub fn check_task_getsid(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_task_getsid");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_task_getsid(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Called when the current task queries the Linux capabilities of the `target` task.
/// Corresponds to the `capget()` LSM hook.
pub fn check_getcap_access(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_getcap_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_getcap_access(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Called when the current task attempts to set the Linux capabilities of the `target`
/// task.
/// Corresponds to the `capset()` LSM hook.
pub fn check_setcap_access(source: &CurrentTask, target: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_setcap_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_setcap_access(
&security_server.as_permission_check(),
&source,
&target,
)
})
}
/// Checks if sending a signal is allowed.
/// Corresponds to the `task_kill()` LSM hook.
pub fn check_signal_access(
source: &CurrentTask,
target: &Task,
signal: Signal,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_signal_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_signal_access(
&security_server.as_permission_check(),
&source,
&target,
signal,
)
})
}
/// Checks if a particular syslog action is allowed.
/// Corresponds to the `task_syslog()` LSM hook.
pub fn check_syslog_access(source: &CurrentTask, action: SyslogAction) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_syslog_access");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::check_syslog_access(
&security_server.as_permission_check(),
&source,
action,
)
})
}
/// Checks whether the `parent_tracer_task` is allowed to trace the `current_task`.
/// Corresponds to the `ptrace_traceme()` LSM hook.
pub fn ptrace_traceme(current_task: &CurrentTask, parent_tracer_task: &Task) -> Result<(), Errno> {
track_hook_duration!("security.hooks.ptrace_traceme");
yama::ptrace_traceme(current_task, parent_tracer_task)?;
common_cap::ptrace_traceme(current_task, parent_tracer_task)?;
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::task::ptrace_traceme(
&security_server.as_permission_check(),
current_task,
parent_tracer_task,
)
})
}
/// Checks whether the current `current_task` is allowed to trace `tracee_task`.
/// Corresponds to the `ptrace_access_check()` LSM hook.
pub fn ptrace_access_check(
current_task: &CurrentTask,
tracee_task: &Task,
mode: PtraceAccessMode,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.ptrace_access_check");
yama::ptrace_access_check(current_task, tracee_task, mode)?;
common_cap::ptrace_access_check(current_task, tracee_task, mode)?;
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::task::ptrace_access_check(
&security_server.as_permission_check(),
current_task,
tracee_task,
mode,
)
})
}
/// Called when the current task calls prlimit on a different task.
/// Corresponds to the `task_prlimit()` LSM hook.
pub fn task_prlimit(
source: &CurrentTask,
target: &Task,
check_get_rlimit: bool,
check_set_rlimit: bool,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.task_prlimit");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::task_prlimit(
&security_server.as_permission_check(),
&source,
&target,
check_get_rlimit,
check_set_rlimit,
)
})
}
/// Called before `source` sets the resource limits of `target` from `old_limit` to `new_limit`.
/// Corresponds to the `security_task_setrlimit` hook.
pub fn task_setrlimit(
source: &CurrentTask,
target: &Task,
old_limit: rlimit,
new_limit: rlimit,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.task_setrlimit");
if_selinux_else_default_ok(source, |security_server| {
selinux_hooks::task::task_setrlimit(
&security_server.as_permission_check(),
&source,
&target,
old_limit,
new_limit,
)
})
}
/// Check permission before mounting `fs`.
/// Corresponds to the `sb_kern_mount()` LSM hook.
pub fn sb_kern_mount(current_task: &CurrentTask, fs: &FileSystem) -> Result<(), Errno> {
track_hook_duration!("security.hooks.sb_kern_mount");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::superblock::sb_kern_mount(
&security_server.as_permission_check(),
current_task,
fs,
)
})
}
/// Check permission before mounting to `path`. `flags` contains the mount flags that determine the
/// kind of mount operation done, and therefore the permissions that the caller requires.
/// Corresponds to the `sb_mount()` LSM hook.
pub fn sb_mount(
current_task: &CurrentTask,
path: &NamespaceNode,
flags: MountFlags,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.sb_mount");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::superblock::sb_mount(
&security_server.as_permission_check(),
current_task,
path,
flags,
)
})
}
/// Checks permission before remounting `mount` with `new_mount_params`.
/// Corresponds to the `sb_remount()` LSM hook.
pub fn sb_remount(
current_task: &CurrentTask,
mount: &Mount,
new_mount_options: FileSystemMountOptions,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.sb_remount");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::superblock::sb_remount(security_server, mount, new_mount_options)
})
}
/// Writes the LSM mount options of `mount` into `buf`.
/// Corresponds to the `sb_show_options` LSM hook.
pub fn sb_show_options(
kernel: &Kernel,
buf: &mut impl OutputBuffer,
mount: &Mount,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.sb_show_options");
if let Some(state) = &kernel.security_state.state {
selinux_hooks::superblock::sb_show_options(&state.server, buf, mount)?;
}
Ok(())
}
/// Checks if `current_task` has the permission to get the filesystem statistics of `fs`.
/// Corresponds to the `sb_statfs()` LSM hook.
pub fn sb_statfs(current_task: &CurrentTask, fs: &FileSystem) -> Result<(), Errno> {
track_hook_duration!("security.hooks.sb_statfs");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::superblock::sb_statfs(
&security_server.as_permission_check(),
current_task,
fs,
)
})
}
/// Checks if `current_task` has the permission to unmount the filesystem mounted on
/// `node` using the unmount flags `flags`.
/// Corresponds to the `sb_umount()` LSM hook.
pub fn sb_umount(
current_task: &CurrentTask,
node: &NamespaceNode,
flags: UnmountFlags,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.sb_umount");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::superblock::sb_umount(
&security_server.as_permission_check(),
current_task,
node,
flags,
)
})
}
/// Checks if `current_task` has the permission to read file attributes for `fs_node`.
/// Corresponds to the `inode_getattr()` hook.
pub fn check_fs_node_getattr_access(
current_task: &CurrentTask,
fs_node: &FsNode,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_getattr_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_getattr_access(security_server, current_task, fs_node)
})
}
/// Returns true if the security subsystem should skip capability checks on access to the named
/// attribute, false otherwise.
pub fn fs_node_xattr_skipcap(_current_task: &CurrentTask, name: &FsStr) -> bool {
selinux_hooks::fs_node::fs_node_xattr_skipcap(name)
}
/// This is called by Starnix even for filesystems which support extended attributes, unlike Linux
/// LSM.
/// Partially corresponds to the `inode_setxattr()` LSM hook: It is equivalent to
/// `inode_setxattr()` for non-security xattrs, while `fs_node_setsecurity()` is always called for
/// security xattrs. See also [`fs_node_setsecurity()`].
pub fn check_fs_node_setxattr_access(
current_task: &CurrentTask,
fs_node: &FsNode,
name: &FsStr,
value: &FsStr,
op: XattrOp,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_setxattr_access");
common_cap::fs_node_setxattr(current_task, fs_node, name, value, op)?;
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_setxattr_access(
security_server,
current_task,
fs_node,
name,
value,
op,
)
})
}
/// Corresponds to the `inode_getxattr()` LSM hook.
pub fn check_fs_node_getxattr_access(
current_task: &CurrentTask,
fs_node: &FsNode,
name: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_getxattr_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_getxattr_access(
security_server,
current_task,
fs_node,
name,
)
})
}
/// Corresponds to the `inode_listxattr()` LSM hook.
pub fn check_fs_node_listxattr_access(
current_task: &CurrentTask,
fs_node: &FsNode,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_listxattr_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_listxattr_access(
security_server,
current_task,
fs_node,
)
})
}
/// Corresponds to the `inode_removexattr()` LSM hook.
pub fn check_fs_node_removexattr_access(
current_task: &CurrentTask,
fs_node: &FsNode,
name: &FsStr,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_fs_node_removexattr_access");
common_cap::fs_node_removexattr(current_task, fs_node, name)?;
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::fs_node::check_fs_node_removexattr_access(
security_server,
current_task,
fs_node,
name,
)
})
}
/// If SELinux is enabled and `fs_node` is in a filesystem without xattr support, returns the xattr
/// name for the security label associated with inode. Otherwise returns None.
///
/// This hook is called from the `listxattr` syscall.
///
/// Corresponds to the `inode_listsecurity()` LSM hook.
pub fn fs_node_listsecurity(current_task: &CurrentTask, fs_node: &FsNode) -> Option<FsString> {
track_hook_duration!("security.hooks.fs_node_listsecurity");
if_selinux_else(
current_task,
|_| selinux_hooks::fs_node::fs_node_listsecurity(fs_node),
|| None,
)
}
/// Returns the value of the specified "security.*" attribute for `fs_node`.
/// If SELinux is enabled then requests for the "security.selinux" attribute will return the
/// Security Context corresponding to the SID with which `fs_node` has been labeled, even if the
/// node's file system does not generally support extended attributes.
/// If SELinux is not enabled, or the node is not labeled with a SID, then the call is delegated to
/// the [`crate::vfs::FsNodeOps`], so the returned value may not be a valid Security Context.
/// Corresponds to the `inode_getsecurity()` LSM hook.
pub fn fs_node_getsecurity<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
fs_node: &FsNode,
name: &FsStr,
max_size: usize,
) -> Result<ValueOrSize<FsString>, Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
track_hook_duration!("security.hooks.fs_node_getsecurity");
if_selinux_else_with_context(
locked,
current_task,
|locked, security_server| {
selinux_hooks::fs_node::fs_node_getsecurity(
locked,
security_server,
current_task,
fs_node,
name,
max_size,
)
},
|locked| {
fs_node.ops().get_xattr(
locked.cast_locked::<FileOpsCore>(),
fs_node,
current_task,
name,
max_size,
)
},
)
}
/// Called when an extended attribute with "security."-prefixed `name` is being set, after having
/// passed the discretionary and `check_fs_node_setxattr_access()` permission-checks.
/// This allows the LSM (e.g. SELinux) to update internal state as necessary for xattr changes.
///
/// Partially corresponds to the `inode_setsecurity()` and `inode_post_setxattr()` LSM hooks.
pub fn fs_node_setsecurity<L>(
locked: &mut Locked<L>,
current_task: &CurrentTask,
fs_node: &FsNode,
name: &FsStr,
value: &FsStr,
op: XattrOp,
) -> Result<(), Errno>
where
L: LockEqualOrBefore<FileOpsCore>,
{
track_hook_duration!("security.hooks.fs_node_setsecurity");
if_selinux_else_with_context(
locked,
current_task,
|locked, security_server| {
selinux_hooks::fs_node::fs_node_setsecurity(
locked,
security_server,
current_task,
fs_node,
name,
value,
op,
)
},
|locked| {
fs_node.ops().set_xattr(
locked.cast_locked::<FileOpsCore>(),
fs_node,
current_task,
name,
value,
op,
)
},
)
}
/// Checks whether `current_task` can perform the given bpf `cmd`. This hook is called from the
/// `sys_bpf()` syscall after the attribute is copied into the kernel.
/// Corresponds to the `bpf()` LSM hook.
pub fn check_bpf_access<Attr: FromBytes>(
current_task: &CurrentTask,
cmd: bpf_cmd,
attr: &Attr,
attr_size: u32,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_bpf_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::bpf::check_bpf_access(security_server, current_task, cmd, attr, attr_size)
})
}
/// Checks whether `current_task` can create a bpf_map. This hook is called from the
/// `sys_bpf()` syscall when the kernel tries to generate and return a file descriptor for maps.
/// Corresponds to the `bpf_map()` LSM hook.
pub fn check_bpf_map_access(
current_task: &CurrentTask,
bpf_map: &BpfMap,
flags: PermissionFlags,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_bpf_map_access");
if_selinux_else_default_ok(current_task, |security_server| {
let subject_sid = current_task_state(current_task).lock().current_sid;
selinux_hooks::bpf::check_bpf_map_access(
security_server,
current_task,
subject_sid,
bpf_map,
flags,
)
})
}
/// Checks whether `current_task` can create a bpf_program. This hook is called from the
/// `sys_bpf()` syscall when the kernel tries to generate and return a file descriptor for
/// programs.
/// Corresponds to the `bpf_prog()` LSM hook.
pub fn check_bpf_prog_access(
current_task: &CurrentTask,
bpf_program: &Program,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_bpf_prog_access");
if_selinux_else_default_ok(current_task, |security_server| {
let subject_sid = current_task_state(current_task).lock().current_sid;
selinux_hooks::bpf::check_bpf_prog_access(
security_server,
current_task,
subject_sid,
bpf_program,
)
})
}
/// Checks whether `current_task` has the correct permissions to monitor the given target task or
/// tasks.
/// Corresponds to the `perf_event_open` LSM hook.
pub fn check_perf_event_open_access(
current_task: &CurrentTask,
target_task_type: TargetTaskType<'_>,
attr: &perf_event_attr,
event_type: PerfEventType,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_perf_event_open_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::perf_event::check_perf_event_open_access(
security_server,
current_task,
target_task_type,
attr,
event_type,
)
})
}
/// Returns the security context to be assigned to a PerfEventFileState, based on the task that
/// creates it.
/// Corresponds to the `perf_event_alloc` LSM hook.
pub fn perf_event_alloc(current_task: &CurrentTask) -> PerfEventState {
track_hook_duration!("security.hooks.perf_event_alloc");
PerfEventState { state: selinux_hooks::perf_event::perf_event_alloc(current_task) }
}
/// Checks whether `current_task` has the correct permissions to read the given `perf_event_file`
/// Corresponds to the `perf_event_read` LSM hook.
pub fn check_perf_event_read_access(
current_task: &CurrentTask,
perf_event_file: &PerfEventFile,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_perf_event_read_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::perf_event::check_perf_event_read_access(
security_server,
current_task,
perf_event_file,
)
})
}
/// Checks whether `current_task` has the correct permissions to write to the given `perf_event_file`.
/// Corresponds to the `perf_event_write` LSM hook.
pub fn check_perf_event_write_access(
current_task: &CurrentTask,
perf_event_file: &PerfEventFile,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.check_perf_event_write_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::perf_event::check_perf_event_write_access(
security_server,
current_task,
perf_event_file,
)
})
}
/// Identifies one of the Security Context attributes associated with a task.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProcAttr {
Current,
Exec,
FsCreate,
KeyCreate,
Previous,
SockCreate,
}
/// Returns the Security Context associated with the `name`ed entry for the specified `target` task.
/// Corresponds to the `getprocattr()` LSM hook.
pub fn get_procattr(
current_task: &CurrentTask,
target: &Task,
attr: ProcAttr,
) -> Result<Vec<u8>, Errno> {
track_hook_duration!("security.hooks.get_procattr");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::task::get_procattr(security_server, current_task, target, attr)
},
// If SELinux is disabled then there are no values to return.
|| error!(EINVAL),
)
}
/// Sets the Security Context associated with the `name`ed entry for the current task.
/// Corresponds to the `setprocattr()` LSM hook.
pub fn set_procattr(
current_task: &CurrentTask,
attr: ProcAttr,
context: &[u8],
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.set_procattr");
if_selinux_else(
current_task,
|security_server| {
selinux_hooks::task::set_procattr(security_server, current_task, attr, context)
},
// If SELinux is disabled then no writes are accepted.
|| error!(EINVAL),
)
}
/// Returns true if SELinux is enabled on the kernel for this task.
pub fn fs_is_xattr_labeled(fs: FileSystemHandle) -> bool {
fs.security_state.state.supports_xattr()
}
/// Stashes a reference to the selinuxfs null file for later use by hooks that remap
/// inaccessible file descriptors to null.
pub fn selinuxfs_init_null(current_task: &CurrentTask, null_fs_node: &FileHandle) {
// Note: No `if_selinux_...` guard because hook is invoked inside selinuxfs initialization code;
// i.e., hook is only invoked when selinux is enabled.
selinux_hooks::selinuxfs::selinuxfs_init_null(current_task, null_fs_node)
}
/// Called by the "selinuxfs" when a policy has been successfully loaded, to allow policy-dependent
/// initialization to be completed. This includes resolving labeling schemes and state for
/// file-systems mounted prior to policy load (e.g. the "selinuxfs" itself), and initializing
/// security state for any file nodes they may already contain.
// TODO: https://fxbug.dev/362917997 - Remove this when SELinux LSM is modularized.
pub fn selinuxfs_policy_loaded<L>(locked: &mut Locked<L>, current_task: &CurrentTask)
where
L: LockEqualOrBefore<FileOpsCore>,
{
track_hook_duration!("security.hooks.selinuxfs_policy_loaded");
selinux_hooks::selinuxfs::selinuxfs_policy_loaded(locked, current_task)
}
/// Used by the "selinuxfs" module to access the SELinux administration API, if enabled.
// TODO: https://fxbug.dev/335397745 - Return a more restricted API, or ...
// TODO: https://fxbug.dev/362917997 - Remove this when SELinux LSM is modularized.
pub fn selinuxfs_get_admin_api(current_task: &CurrentTask) -> Option<Arc<SecurityServer>> {
current_task.kernel().security_state.state.as_ref().map(|state| state.server.clone())
}
/// Used by the "selinuxfs" module to perform checks on SELinux API file accesses.
// TODO: https://fxbug.dev/362917997 - Remove this when SELinux LSM is modularized.
pub fn selinuxfs_check_access(
current_task: &CurrentTask,
permission: SecurityPermission,
) -> Result<(), Errno> {
track_hook_duration!("security.hooks.selinuxfs_check_access");
if_selinux_else_default_ok(current_task, |security_server| {
selinux_hooks::selinuxfs::selinuxfs_check_access(security_server, current_task, permission)
})
}
/// Marks the credentials as being used for an internal operation. All SELinux permission checks
/// will be skipped on this task.
pub fn creds_start_internal_operation(creds: &mut FullCredentials) {
track_hook_duration!("security.hooks.creds_start_internal_operation");
creds.security_state.lock().internal_operation = true;
}
pub mod testing {
use super::{Arc, KernelState, SecurityServer, selinux_hooks};
use starnix_sync::Mutex;
use std::sync::OnceLock;
use std::sync::atomic::AtomicU64;
/// Used by Starnix' `testing.rs` to create `KernelState` wrapping a test-
/// supplied `SecurityServer`.
pub fn kernel_state(security_server: Option<Arc<SecurityServer>>) -> KernelState {
KernelState {
state: security_server.map(|server| selinux_hooks::KernelState {
server,
pending_file_systems: Mutex::default(),
selinuxfs_null: OnceLock::default(),
access_denial_count: AtomicU64::new(0u64),
has_policy: false.into(),
}),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::security::selinux_hooks::get_cached_sid;
use crate::security::selinux_hooks::testing::{
self, spawn_kernel_with_selinux_hooks_test_policy_and_run,
};
use crate::testing::{create_task, spawn_kernel_and_run, spawn_kernel_with_selinux_and_run};
use linux_uapi::XATTR_NAME_SELINUX;
use selinux::InitialSid;
use starnix_uapi::auth::PTRACE_MODE_ATTACH;
use starnix_uapi::signals::SIGTERM;
const VALID_SECURITY_CONTEXT: &[u8] = b"u:object_r:test_valid_t:s0";
const VALID_SECURITY_CONTEXT_WITH_NUL: &[u8] = b"u:object_r:test_valid_t:s0\0";
const DIFFERENT_VALID_SECURITY_CONTEXT: &[u8] = b"u:object_r:test_different_valid_t:s0";
const DIFFERENT_VALID_SECURITY_CONTEXT_WITH_NUL: &[u8] =
b"u:object_r:test_different_valid_t:s0\0";
const INVALID_SECURITY_CONTEXT_INTERNAL_NUL: &[u8] = b"u:object_r:test_valid_\0t:s0";
const INVALID_SECURITY_CONTEXT: &[u8] = b"not_a_u:object_r:test_valid_t:s0";
#[derive(Default, Debug, PartialEq)]
enum TestHookResult {
WasRun,
WasNotRun,
#[default]
WasNotRunDefault,
}
#[fuchsia::test]
async fn if_selinux_else_disabled() {
spawn_kernel_and_run(async |_, current_task| {
assert!(current_task.kernel().security_state.state.is_none());
let check_result =
if_selinux_else_default_ok(current_task, |_| Ok(TestHookResult::WasRun));
assert_eq!(check_result, Ok(TestHookResult::WasNotRunDefault));
let run_else_result = if_selinux_else(
current_task,
|_| TestHookResult::WasRun,
|| TestHookResult::WasNotRun,
);
assert_eq!(run_else_result, TestHookResult::WasNotRun);
})
.await;
}
#[fuchsia::test]
async fn if_selinux_else_without_policy() {
spawn_kernel_with_selinux_and_run(async |_locked, current_task, _security_server| {
let check_result =
if_selinux_else_default_ok(current_task, |_| Ok(TestHookResult::WasRun));
assert_eq!(check_result, Ok(TestHookResult::WasNotRunDefault));
let run_else_result = if_selinux_else(
current_task,
|_| TestHookResult::WasRun,
|| TestHookResult::WasNotRun,
);
assert_eq!(run_else_result, TestHookResult::WasNotRun);
})
.await;
}
#[fuchsia::test]
async fn if_selinux_else_with_policy() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, _security_server| {
let check_result =
if_selinux_else_default_ok(current_task, |_| Ok(TestHookResult::WasRun));
assert_eq!(check_result, Ok(TestHookResult::WasRun));
let run_else_result = if_selinux_else(
current_task,
|_| TestHookResult::WasRun,
|| TestHookResult::WasNotRun,
);
assert_eq!(run_else_result, TestHookResult::WasRun);
},
)
.await;
}
#[fuchsia::test]
async fn task_alloc_selinux_disabled() {
spawn_kernel_and_run(async |_, current_task| {
task_alloc(current_task, 0);
})
.await;
}
#[fuchsia::test]
async fn task_create_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |_, current_task| {
assert!(current_task.kernel().security_state.state.is_none());
assert_eq!(check_task_create_access(current_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn task_create_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, security_server| {
security_server.set_enforcing(false);
assert_eq!(check_task_create_access(current_task), Ok(()));
},
)
.await;
}
#[fuchsia::test]
async fn exec_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
assert!(current_task.kernel().security_state.state.is_none());
let executable = &testing::create_test_file(locked, current_task);
assert_eq!(
bprm_creds_for_exec(current_task, executable),
Ok(ResolvedElfState { sid: None, require_secure_exec: false })
);
})
.await;
}
#[fuchsia::test]
async fn exec_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
security_server.set_enforcing(false);
let executable = &testing::create_test_file(locked, current_task);
// Expect that access is granted, and a `SecurityId` is returned in the `ResolvedElfState`.
let result = bprm_creds_for_exec(current_task, executable);
assert!(result.expect("Exec check should succeed").sid.is_some());
},
)
.await;
}
#[fuchsia::test]
async fn no_state_update_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
// Without SELinux enabled and a policy loaded, only `InitialSid` values exist
// in the system.
let target_sid = InitialSid::Unlabeled.into();
let elf_state = ResolvedElfState { sid: Some(target_sid), require_secure_exec: false };
assert!(
selinux_hooks::current_task_state(current_task).lock().current_sid != target_sid
);
let before_hook_sid =
selinux_hooks::current_task_state(current_task).lock().current_sid;
exec_binprm(locked, current_task, &elf_state);
assert_eq!(
selinux_hooks::current_task_state(current_task).lock().current_sid,
before_hook_sid
);
assert_eq!(current_task.security_state.lock().current_sid, before_hook_sid)
})
.await;
}
#[fuchsia::test]
async fn no_state_update_for_selinux_without_policy() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
// Without SELinux enabled and a policy loaded, only `InitialSid` values exist
// in the system.
let initial_state = current_task.security_state.lock().clone();
let elf_sid = InitialSid::Unlabeled.into();
let elf_state = ResolvedElfState { sid: Some(elf_sid), require_secure_exec: false };
assert_ne!(elf_sid, selinux_hooks::current_task_state(current_task).lock().current_sid);
exec_binprm(locked, current_task, &elf_state);
assert_eq!(*current_task.security_state.lock(), initial_state);
})
.await;
}
#[fuchsia::test]
async fn state_update_for_permissive_mode() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
security_server.set_enforcing(false);
let initial_state = selinux_hooks::TaskAttrs::for_kernel();
*current_task.security_state.lock() = initial_state.clone();
let elf_sid = security_server
.security_context_to_sid(b"u:object_r:fork_no_t:s0".into())
.expect("invalid security context");
let elf_state = ResolvedElfState { sid: Some(elf_sid), require_secure_exec: false };
assert_ne!(
elf_sid,
selinux_hooks::current_task_state(current_task).lock().current_sid
);
exec_binprm(locked, current_task, &elf_state);
assert_eq!(
selinux_hooks::current_task_state(current_task).lock().current_sid,
elf_sid
);
assert_eq!(current_task.security_state.lock().current_sid, elf_sid);
},
)
.await;
}
#[fuchsia::test]
async fn getsched_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_getsched_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn getsched_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_getsched_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn setsched_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_setsched_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn setsched_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_setsched_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn getpgid_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_getpgid_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn getpgid_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_getpgid_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn setpgid_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_setpgid_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn setpgid_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_setpgid_access(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn task_getsid_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_task_getsid(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn task_getsid_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_task_getsid(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn signal_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_signal_access(current_task, &another_task, SIGTERM), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn signal_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(check_signal_access(current_task, &another_task, SIGTERM), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn ptrace_traceme_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(ptrace_traceme(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn ptrace_traceme_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(ptrace_traceme(current_task, &another_task), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn ptrace_attach_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(
ptrace_access_check(current_task, &another_task, PTRACE_MODE_ATTACH),
Ok(())
);
})
.await;
}
#[fuchsia::test]
async fn ptrace_attach_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(
ptrace_access_check(current_task, &another_task, PTRACE_MODE_ATTACH),
Ok(())
);
})
.await;
}
#[fuchsia::test]
async fn task_prlimit_access_allowed_for_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(task_prlimit(current_task, &another_task, true, true), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn task_prlimit_access_allowed_for_permissive_mode() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let another_task = create_task(locked, &current_task.kernel(), "another-task");
assert_eq!(task_prlimit(current_task, &another_task, true, true), Ok(()));
})
.await;
}
#[fuchsia::test]
async fn fs_node_task_to_fs_node_noop_selinux_disabled() {
spawn_kernel_and_run(async |locked, current_task| {
let node = &testing::create_test_file(locked, current_task).entry.node;
task_to_fs_node(current_task, &current_task.temp_task(), &node);
assert_eq!(None, selinux_hooks::get_cached_sid(node));
})
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_selinux_disabled_only_sets_xattr() {
spawn_kernel_and_run(async |locked, current_task| {
let node = &testing::create_test_file(locked, current_task).entry.node;
fs_node_setsecurity(
locked,
current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
assert_eq!(None, selinux_hooks::get_cached_sid(node));
})
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_selinux_without_policy_only_sets_xattr() {
spawn_kernel_with_selinux_and_run(async |locked, current_task, _security_server| {
let node = &testing::create_test_file(locked, current_task).entry.node;
fs_node_setsecurity(
locked,
current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
assert_eq!(None, selinux_hooks::get_cached_sid(node));
})
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_selinux_permissive_sets_xattr_and_label() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
security_server.set_enforcing(false);
let expected_sid = security_server
.security_context_to_sid(VALID_SECURITY_CONTEXT.into())
.expect("no SID for VALID_SECURITY_CONTEXT");
let node = &testing::create_test_file(locked, &current_task).entry.node;
// Safeguard against a false positive by ensuring `expected_sid` is not already the file's label.
assert_ne!(Some(expected_sid), selinux_hooks::get_cached_sid(node));
fs_node_setsecurity(
locked,
current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
// Verify that the SID now cached on the node is that SID
// corresponding to VALID_SECURITY_CONTEXT.
assert_eq!(Some(expected_sid), selinux_hooks::get_cached_sid(node));
},
)
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_not_selinux_only_sets_xattr() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
let valid_security_context_sid = security_server
.security_context_to_sid(VALID_SECURITY_CONTEXT.into())
.expect("no SID for VALID_SECURITY_CONTEXT");
let node = &testing::create_test_file(locked, current_task).entry.node;
// The label assigned to the test file on creation must differ from
// VALID_SECURITY_CONTEXT, otherwise this test may return a false
// positive.
let whatever_sid = selinux_hooks::get_cached_sid(node);
assert_ne!(Some(valid_security_context_sid), whatever_sid);
fs_node_setsecurity(
locked,
current_task,
&node,
"security.selinu!".into(), // Note: name != "security.selinux".
VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
// Verify that the node's SID (whatever it was) has not changed.
assert_eq!(whatever_sid, selinux_hooks::get_cached_sid(node));
},
)
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_selinux_enforcing_invalid_context_fails() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, _security_server| {
let node = &testing::create_test_file(locked, current_task).entry.node;
let before_sid = selinux_hooks::get_cached_sid(node);
assert_ne!(Some(InitialSid::Unlabeled.into()), before_sid);
assert!(
check_fs_node_setxattr_access(
&current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
"!".into(), // Note: Not a valid security context.
XattrOp::Set,
)
.is_err()
);
assert_eq!(before_sid, selinux_hooks::get_cached_sid(node));
},
)
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_selinux_permissive_invalid_context_sets_xattr_and_label() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
security_server.set_enforcing(false);
let node = &testing::create_test_file(locked, current_task).entry.node;
assert_ne!(Some(InitialSid::Unlabeled.into()), selinux_hooks::get_cached_sid(node));
fs_node_setsecurity(
locked,
current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
"!".into(), // Note: Not a valid security context.
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
assert_eq!(Some(InitialSid::Unlabeled.into()), selinux_hooks::get_cached_sid(node));
},
)
.await;
}
#[fuchsia::test]
async fn fs_node_setsecurity_different_sid_for_different_context() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, _security_server| {
let node = &testing::create_test_file(locked, current_task).entry.node;
fs_node_setsecurity(
locked,
current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
assert!(selinux_hooks::get_cached_sid(node).is_some());
let first_sid = selinux_hooks::get_cached_sid(node).unwrap();
fs_node_setsecurity(
locked,
current_task,
&node,
XATTR_NAME_SELINUX.to_bytes().into(),
DIFFERENT_VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
assert!(selinux_hooks::get_cached_sid(node).is_some());
let second_sid = selinux_hooks::get_cached_sid(node).unwrap();
assert_ne!(first_sid, second_sid);
},
)
.await;
}
#[fuchsia::test]
async fn fs_node_getsecurity_returns_cached_context() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
let node = &testing::create_test_file(locked, current_task).entry.node;
// Set a mismatched value in `node`'s "security.seliux" attribute.
const TEST_VALUE: &str = "Something Random";
node.ops()
.set_xattr(
locked.cast_locked::<FileOpsCore>(),
node,
current_task,
XATTR_NAME_SELINUX.to_bytes().into(),
TEST_VALUE.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
// Attach a valid SID to the `node`.
let sid = security_server
.security_context_to_sid(VALID_SECURITY_CONTEXT.into())
.expect("security context to SID");
selinux_hooks::set_cached_sid(&node, sid);
// Reading the security attribute should return the Security Context for the SID, rather than delegating.
let result = fs_node_getsecurity(
locked,
current_task,
node,
XATTR_NAME_SELINUX.to_bytes().into(),
4096,
);
assert_eq!(
result,
Ok(ValueOrSize::Value(FsString::new(VALID_SECURITY_CONTEXT_WITH_NUL.into())))
);
},
)
.await;
}
#[fuchsia::test]
async fn fs_node_getsecurity_delegates_to_get_xattr() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
let node = &testing::create_test_file(locked, current_task).entry.node;
// Set an invalid value in `node`'s "security.selinux" attribute.
// This requires SELinux to be in permissive mode, otherwise the "relabelto" permission check will fail.
security_server.set_enforcing(false);
const TEST_VALUE: &str = "Something Random";
fs_node_setsecurity(
locked,
current_task,
node,
XATTR_NAME_SELINUX.to_bytes().into(),
TEST_VALUE.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
security_server.set_enforcing(true);
// Reading the security attribute should pass-through to read the value from the file system.
let result = fs_node_getsecurity(
locked,
current_task,
node,
XATTR_NAME_SELINUX.to_bytes().into(),
4096,
);
assert_eq!(result, Ok(ValueOrSize::Value(FsString::new(TEST_VALUE.into()))));
},
)
.await;
}
#[fuchsia::test]
async fn set_get_procattr() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, _security_server| {
assert_eq!(
get_procattr(current_task, current_task, ProcAttr::Exec),
Ok(Vec::new())
);
assert_eq!(
// Test policy allows "kernel_t" tasks to set the "exec" context.
set_procattr(current_task, ProcAttr::Exec, VALID_SECURITY_CONTEXT.into()),
Ok(())
);
assert_eq!(
// Test policy does not allow "kernel_t" tasks to set the "sockcreate" context.
set_procattr(
current_task,
ProcAttr::SockCreate,
DIFFERENT_VALID_SECURITY_CONTEXT.into()
),
error!(EACCES)
);
assert_eq!(
// It is never permitted to set the "previous" context.
set_procattr(
current_task,
ProcAttr::Previous,
DIFFERENT_VALID_SECURITY_CONTEXT.into()
),
error!(EINVAL)
);
assert_eq!(
// Cannot set an invalid context.
set_procattr(current_task, ProcAttr::Exec, INVALID_SECURITY_CONTEXT.into()),
error!(EINVAL)
);
assert_eq!(
get_procattr(current_task, current_task, ProcAttr::Exec),
Ok(VALID_SECURITY_CONTEXT_WITH_NUL.into())
);
assert!(get_procattr(current_task, current_task, ProcAttr::Current).is_ok());
},
)
.await;
}
#[fuchsia::test]
async fn set_get_procattr_with_nulls() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, _security_server| {
assert_eq!(
get_procattr(current_task, current_task, ProcAttr::Exec),
Ok(Vec::new())
);
assert_eq!(
// Setting a Context with a string with trailing null(s) should work, if the Context is valid.
set_procattr(
current_task,
ProcAttr::Exec,
VALID_SECURITY_CONTEXT_WITH_NUL.into()
),
Ok(())
);
assert_eq!(
// Nulls in the middle of an otherwise valid Context truncate it, rendering it invalid.
set_procattr(
current_task,
ProcAttr::FsCreate,
INVALID_SECURITY_CONTEXT_INTERNAL_NUL.into()
),
error!(EINVAL)
);
assert_eq!(
get_procattr(current_task, current_task, ProcAttr::Exec),
Ok(VALID_SECURITY_CONTEXT_WITH_NUL.into())
);
assert_eq!(
get_procattr(current_task, current_task, ProcAttr::FsCreate),
Ok(Vec::new())
);
},
)
.await;
}
#[fuchsia::test]
async fn set_get_procattr_clear_context() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, _security_server| {
// Set up the "exec" and "fscreate" Contexts with valid values.
assert_eq!(
set_procattr(current_task, ProcAttr::Exec, VALID_SECURITY_CONTEXT.into()),
Ok(())
);
assert_eq!(
set_procattr(
current_task,
ProcAttr::FsCreate,
DIFFERENT_VALID_SECURITY_CONTEXT.into()
),
Ok(())
);
// Clear the "exec" context with a write containing a single null octet.
assert_eq!(set_procattr(current_task, ProcAttr::Exec, b"\0"), Ok(()));
assert_eq!(current_task.security_state.lock().exec_sid, None);
// Clear the "fscreate" context with a write containing a single newline.
assert_eq!(set_procattr(current_task, ProcAttr::FsCreate, b"\x0a"), Ok(()));
assert_eq!(current_task.security_state.lock().fscreate_sid, None);
},
)
.await;
}
#[fuchsia::test]
async fn set_get_procattr_setcurrent() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, _security_server| {
// Stash the initial "previous" context.
let initial_previous =
get_procattr(current_task, current_task, ProcAttr::Previous).unwrap();
assert_eq!(
// Dynamically transition to a valid new context.
set_procattr(current_task, ProcAttr::Current, VALID_SECURITY_CONTEXT.into()),
Ok(())
);
assert_eq!(
// "current" should report the new context.
get_procattr(current_task, current_task, ProcAttr::Current),
Ok(VALID_SECURITY_CONTEXT_WITH_NUL.into())
);
assert_eq!(
// "prev" should continue to report the original context.
get_procattr(current_task, current_task, ProcAttr::Previous),
Ok(initial_previous.clone())
);
assert_eq!(
// Dynamically transition to a different valid context.
set_procattr(
current_task,
ProcAttr::Current,
DIFFERENT_VALID_SECURITY_CONTEXT.into()
),
Ok(())
);
assert_eq!(
// "current" should report the different new context.
get_procattr(current_task, current_task, ProcAttr::Current),
Ok(DIFFERENT_VALID_SECURITY_CONTEXT_WITH_NUL.into())
);
assert_eq!(
// "prev" should continue to report the original context.
get_procattr(current_task, current_task, ProcAttr::Previous),
Ok(initial_previous.clone())
);
},
)
.await;
}
#[fuchsia::test]
async fn set_get_procattr_selinux_permissive() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|_locked, current_task, security_server| {
security_server.set_enforcing(false);
assert_eq!(
get_procattr(current_task, &current_task.temp_task(), ProcAttr::Exec),
Ok(Vec::new())
);
assert_eq!(
// Test policy allows "kernel_t" tasks to set the "exec" context.
set_procattr(current_task, ProcAttr::Exec, VALID_SECURITY_CONTEXT.into()),
Ok(())
);
assert_eq!(
// Test policy does not allow "kernel_t" tasks to set the "fscreate" context, but
// in permissive mode the setting will be allowed.
set_procattr(
current_task,
ProcAttr::FsCreate,
DIFFERENT_VALID_SECURITY_CONTEXT.into()
),
Ok(())
);
assert_eq!(
// Setting an invalid context should fail, even in permissive mode.
set_procattr(current_task, ProcAttr::Exec, INVALID_SECURITY_CONTEXT.into()),
error!(EINVAL)
);
assert_eq!(
get_procattr(current_task, &current_task.temp_task(), ProcAttr::Exec),
Ok(VALID_SECURITY_CONTEXT_WITH_NUL.into())
);
assert!(
get_procattr(current_task, &current_task.temp_task(), ProcAttr::Current)
.is_ok()
);
},
)
.await;
}
#[fuchsia::test]
async fn set_get_procattr_selinux_disabled() {
spawn_kernel_and_run(async |_, current_task| {
assert_eq!(
set_procattr(&current_task, ProcAttr::Exec, VALID_SECURITY_CONTEXT.into()),
error!(EINVAL)
);
assert_eq!(
// Test policy allows "kernel_t" tasks to set the "exec" context.
set_procattr(&current_task, ProcAttr::Exec, VALID_SECURITY_CONTEXT.into()),
error!(EINVAL)
);
assert_eq!(
// Test policy does not allow "kernel_t" tasks to set the "fscreate" context.
set_procattr(&current_task, ProcAttr::FsCreate, VALID_SECURITY_CONTEXT.into()),
error!(EINVAL)
);
assert_eq!(
// Cannot set an invalid context.
set_procattr(&current_task, ProcAttr::Exec, INVALID_SECURITY_CONTEXT.into()),
error!(EINVAL)
);
assert_eq!(
get_procattr(&current_task, &current_task.temp_task(), ProcAttr::Current),
error!(EINVAL)
);
})
.await;
}
#[fuchsia::test]
async fn create_file_with_fscreate_sid() {
spawn_kernel_with_selinux_hooks_test_policy_and_run(
|locked, current_task, security_server| {
let sid =
security_server.security_context_to_sid(VALID_SECURITY_CONTEXT.into()).unwrap();
let source_node = &testing::create_test_file(locked, current_task).entry.node;
fs_node_setsecurity(
locked,
current_task,
&source_node,
XATTR_NAME_SELINUX.to_bytes().into(),
VALID_SECURITY_CONTEXT.into(),
XattrOp::Set,
)
.expect("set_xattr(security.selinux) failed");
let dir_entry = fs_node_copy_up(current_task, source_node, || {
current_task
.fs()
.root()
.create_node(
locked,
&current_task,
"test_file2".into(),
FileMode::IFREG,
DeviceType::NONE,
)
.unwrap()
})
.entry;
assert_eq!(get_cached_sid(&dir_entry.node), Some(sid));
},
)
.await;
}
}