blob: 840bf6496ae9e3124bfdce3059e676217cd0329e [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.
use selinux::{
permission_check::PermissionCheck, security_server::SecurityServer, InitialSid, SecurityId,
};
use selinux_common::{FilePermission, ObjectClass, ProcessPermission};
use starnix_logging::log_debug;
use starnix_uapi::{
errno, error,
errors::Errno,
signals::{Signal, SIGCHLD, SIGKILL, SIGSTOP},
};
use std::sync::Arc;
/// Checks if creating a task is allowed.
pub(super) fn check_task_create_access(
permission_check: &impl PermissionCheck,
task_sid: SecurityId,
) -> Result<(), Errno> {
// When creating a process there is no transition involved, the source and target SIDs
// are the current SID.
let target_sid = task_sid;
check_permission(permission_check, task_sid, target_sid, ProcessPermission::Fork)
}
/// Checks the SELinux permissions required for exec. Returns the SELinux state of a resolved
/// elf if all required permissions are allowed.
pub(super) fn check_exec_access(
security_server: &Arc<SecurityServer>,
security_state: &ThreadGroupState,
executable_sid: SecurityId,
) -> Result<Option<SecurityId>, Errno> {
let current_sid = security_state.current_sid;
let new_sid = if let Some(exec_sid) = security_state.exec_sid {
// Use the proc exec SID if set.
exec_sid
} else {
security_server
.compute_new_sid(current_sid, executable_sid, ObjectClass::Process)
.map_err(|_| errno!(EACCES))?
// TODO(http://b/319232900): validate that the new context is valid, and return EACCESS if
// it's not.
};
if current_sid == new_sid {
// To `exec()` a binary in the caller's domain, the caller must be granted
// "execute_no_trans" permission to the binary.
if !security_server.has_permission(
current_sid,
executable_sid,
FilePermission::ExecuteNoTrans,
) {
// TODO(http://b/330904217): once filesystems are labeled, deny access.
log_debug!("execute_no_trans permission is denied, ignoring.");
}
} else {
// Domain transition, check that transition is allowed.
if !security_server.has_permission(current_sid, new_sid, ProcessPermission::Transition) {
return error!(EACCES);
}
// Check that the executable file has an entry point into the new domain.
if !security_server.has_permission(new_sid, executable_sid, FilePermission::Entrypoint) {
// TODO(http://b/330904217): once filesystems are labeled, deny access.
log_debug!("entrypoint permission is denied, ignoring.");
}
// Check that ptrace permission is allowed if the process is traced.
if let Some(ptracer_sid) = security_state.ptracer_sid {
if !security_server.has_permission(ptracer_sid, new_sid, ProcessPermission::Ptrace) {
return error!(EACCES);
}
}
}
Ok(Some(new_sid))
}
/// Updates the SELinux thread group state on exec, using the security ID associated with the
/// resolved elf.
pub(super) fn update_state_on_exec(
security_state: &mut ThreadGroupState,
elf_security_state: Option<SecurityId>,
) {
// TODO(http://b/316181721): check if the previous state needs to be updated regardless.
if let Some(elf_security_state) = elf_security_state {
security_state.previous_sid = security_state.current_sid;
security_state.current_sid = elf_security_state;
}
}
/// Checks if source with `source_sid` may exercise the "getsched" permission on target with
/// `target_sid` according to SELinux server status `status` and permission checker
/// `permission`.
pub(super) fn check_getsched_access(
permission_check: &impl PermissionCheck,
source_sid: SecurityId,
target_sid: SecurityId,
) -> Result<(), Errno> {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::GetSched)
}
/// Checks if the task with `source_sid` is allowed to set scheduling parameters for the task with
/// `target_sid`.
pub(super) fn check_setsched_access(
permission_check: &impl PermissionCheck,
source_sid: SecurityId,
target_sid: SecurityId,
) -> Result<(), Errno> {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::SetSched)
}
/// Checks if the task with `source_sid` is allowed to get the process group ID of the task with
/// `target_sid`.
pub(super) fn check_getpgid_access(
permission_check: &impl PermissionCheck,
source_sid: SecurityId,
target_sid: SecurityId,
) -> Result<(), Errno> {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::GetPgid)
}
/// Checks if the task with `source_sid` is allowed to set the process group ID of the task with
/// `target_sid`.
pub(super) fn check_setpgid_access(
permission_check: &impl PermissionCheck,
source_sid: SecurityId,
target_sid: SecurityId,
) -> Result<(), Errno> {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::SetPgid)
}
/// Checks if the task with `source_sid` is allowed to send `signal` to the task with `target_sid`.
pub(super) fn check_signal_access(
permission_check: &impl PermissionCheck,
source_sid: SecurityId,
target_sid: SecurityId,
signal: Signal,
) -> Result<(), Errno> {
match signal {
// The `sigkill` permission is required for sending SIGKILL.
SIGKILL => {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::SigKill)
}
// The `sigstop` permission is required for sending SIGSTOP.
SIGSTOP => {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::SigStop)
}
// The `sigchld` permission is required for sending SIGCHLD.
SIGCHLD => {
check_permission(permission_check, source_sid, target_sid, ProcessPermission::SigChld)
}
// The `signal` permission is required for sending any signal other than SIGKILL, SIGSTOP
// or SIGCHLD.
_ => check_permission(permission_check, source_sid, target_sid, ProcessPermission::Signal),
}
}
/// Checks if the task with `source_sid` is allowed to trace the task with `target_sid`.
pub(super) fn check_ptrace_access_and_update_state(
permission_check: &impl PermissionCheck,
tracer_sid: SecurityId,
tracee_security_state: &mut ThreadGroupState,
) -> Result<(), Errno> {
check_permission(
permission_check,
tracer_sid,
tracee_security_state.current_sid,
ProcessPermission::Ptrace,
)
.and_then(|_| {
// If tracing is allowed, set the `ptracer_sid` of the tracee with the tracer's SID.
tracee_security_state.ptracer_sid = Some(tracer_sid);
Ok(())
})
}
/// Checks if `permission` is allowed from the task with `source_sid` to the task with `target_sid`.
fn check_permission(
permission_check: &impl PermissionCheck,
source_sid: SecurityId,
target_sid: SecurityId,
permission: ProcessPermission,
) -> Result<(), Errno> {
match permission_check.has_permission(source_sid, target_sid, permission) {
true => Ok(()),
false => error!(EACCES),
}
}
/// Returns a `ThreadGroupState` instance for a new task.
pub(super) fn alloc_security(parent: Option<&ThreadGroupState>) -> ThreadGroupState {
match parent {
Some(parent) => parent.clone(),
None => ThreadGroupState::for_kernel(),
}
}
/// The SELinux security structure for `ThreadGroup`.
#[derive(Clone, Debug, PartialEq)]
pub(super) struct ThreadGroupState {
/// Current SID for the task.
pub current_sid: SecurityId,
/// SID for the task upon the next execve call.
pub exec_sid: Option<SecurityId>,
/// SID for files created by the task.
pub fscreate_sid: Option<SecurityId>,
/// SID for kernel-managed keys created by the task.
pub keycreate_sid: Option<SecurityId>,
/// SID prior to the last execve.
pub previous_sid: SecurityId,
/// SID for sockets created by the task.
pub sockcreate_sid: Option<SecurityId>,
/// SID of the tracer, if the thread group is traced.
pub ptracer_sid: Option<SecurityId>,
}
impl ThreadGroupState {
/// Returns initial state for the kernel's root thread group.
pub(super) fn for_kernel() -> Self {
Self {
current_sid: SecurityId::initial(InitialSid::Kernel),
previous_sid: SecurityId::initial(InitialSid::Kernel),
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
sockcreate_sid: None,
ptracer_sid: None,
}
}
/// Returns placeholder state for use when SELinux is not enabled.
pub(super) fn for_selinux_disabled() -> Self {
Self {
current_sid: SecurityId::initial(InitialSid::Unlabeled),
previous_sid: SecurityId::initial(InitialSid::Unlabeled),
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
sockcreate_sid: None,
ptracer_sid: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use selinux::security_server::Mode;
use starnix_uapi::signals::SIGTERM;
const HOOKS_TESTS_BINARY_POLICY: &[u8] =
include_bytes!("../../../lib/selinux/testdata/micro_policies/hooks_tests_policy.pp");
fn security_server_with_policy() -> Arc<SecurityServer> {
let policy_bytes = HOOKS_TESTS_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new(Mode::Enable);
security_server.set_enforcing(true);
security_server.load_policy(policy_bytes).expect("policy load failed");
security_server
}
#[fuchsia::test]
fn task_create_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let sid = security_server
.security_context_to_sid(b"u:object_r:fork_yes_t:s0")
.expect("invalid security context");
assert_eq!(check_task_create_access(&security_server.as_permission_check(), sid), Ok(()));
}
#[fuchsia::test]
fn task_create_denied_for_denied_type() {
let security_server = security_server_with_policy();
let sid = security_server
.security_context_to_sid(b"u:object_r:fork_no_t:s0")
.expect("invalid security context");
assert_eq!(
check_task_create_access(&security_server.as_permission_check(), sid),
error!(EACCES)
);
}
#[fuchsia::test]
fn exec_transition_allowed_for_allowed_transition_type() {
let security_server = security_server_with_policy();
let current_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_source_t:s0")
.expect("invalid security context");
let exec_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_target_t:s0")
.expect("invalid security context");
let executable_sid = security_server
.security_context_to_sid(b"u:object_r:executable_file_trans_t:s0")
.expect("invalid security context");
let security_state = ThreadGroupState {
current_sid: current_sid,
exec_sid: Some(exec_sid),
fscreate_sid: None,
keycreate_sid: None,
previous_sid: current_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
assert_eq!(
check_exec_access(&security_server, &security_state, executable_sid),
Ok(Some(exec_sid))
);
}
#[fuchsia::test]
fn exec_transition_denied_for_transition_denied_type() {
let security_server = security_server_with_policy();
let current_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_source_t:s0")
.expect("invalid security context");
let exec_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_denied_target_t:s0")
.expect("invalid security context");
let executable_sid = security_server
.security_context_to_sid(b"u:object_r:executable_file_trans_t:s0")
.expect("invalid security context");
let security_state = ThreadGroupState {
current_sid: current_sid,
exec_sid: Some(exec_sid),
fscreate_sid: None,
keycreate_sid: None,
previous_sid: current_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
assert_eq!(
check_exec_access(&security_server, &security_state, executable_sid),
error!(EACCES)
);
}
// TODO(http://b/330904217): reenable test once filesystems are labeled and access is denied.
#[ignore]
#[fuchsia::test]
fn exec_transition_denied_for_executable_with_no_entrypoint_perm() {
let security_server = security_server_with_policy();
let current_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_source_t:s0")
.expect("invalid security context");
let exec_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_target_t:s0")
.expect("invalid security context");
let executable_sid = security_server
.security_context_to_sid(b"u:object_r:executable_file_trans_no_entrypoint_t:s0")
.expect("invalid security context");
let security_state = ThreadGroupState {
current_sid: current_sid,
exec_sid: Some(exec_sid),
fscreate_sid: None,
keycreate_sid: None,
previous_sid: current_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
assert_eq!(
check_exec_access(&security_server, &security_state, executable_sid),
error!(EACCES)
);
}
#[fuchsia::test]
fn exec_no_trans_allowed_for_executable() {
let security_server = security_server_with_policy();
let current_sid = security_server
.security_context_to_sid(b"u:object_r:exec_no_trans_source_t:s0")
.expect("invalid security context");
let executable_sid = security_server
.security_context_to_sid(b"u:object_r:executable_file_no_trans_t:s0")
.expect("invalid security context");
let security_state = ThreadGroupState {
current_sid: current_sid,
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
previous_sid: current_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
assert_eq!(
check_exec_access(&security_server, &security_state, executable_sid),
Ok(Some(current_sid))
);
}
// TODO(http://b/330904217): reenable test once filesystems are labeled and access is denied.
#[ignore]
#[fuchsia::test]
fn exec_no_trans_denied_for_executable() {
let security_server = security_server_with_policy();
let current_sid = security_server
.security_context_to_sid(b"u:object_r:exec_transition_target_t:s0")
.expect("invalid security context");
let executable_sid = security_server
.security_context_to_sid(b"u:object_r:executable_file_no_trans_t:s0")
.expect("invalid security context");
let security_state = ThreadGroupState {
current_sid: current_sid,
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
previous_sid: current_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
// There is no `execute_no_trans` allow statement from `current_sid` to `executable_sid`,
// expect access denied.
assert_eq!(
check_exec_access(&security_server, &security_state, executable_sid),
error!(EACCES)
);
}
#[fuchsia::test]
fn no_state_update_if_no_elf_state() {
let initial_state = ThreadGroupState::for_kernel();
let mut security_state = initial_state.clone();
update_state_on_exec(&mut security_state, None);
assert_eq!(security_state, initial_state);
}
#[fuchsia::test]
fn state_is_updated_on_exec() {
let security_server = security_server_with_policy();
let initial_state = ThreadGroupState::for_kernel();
let mut security_state = initial_state.clone();
let elf_sid = security_server
.security_context_to_sid(b"u:object_r:test_valid_t:s0")
.expect("invalid security context");
assert_ne!(elf_sid, initial_state.current_sid);
update_state_on_exec(&mut security_state, Some(elf_sid));
assert_eq!(
security_state,
ThreadGroupState {
current_sid: elf_sid,
exec_sid: initial_state.exec_sid,
fscreate_sid: initial_state.fscreate_sid,
keycreate_sid: initial_state.keycreate_sid,
previous_sid: initial_state.previous_sid,
sockcreate_sid: initial_state.sockcreate_sid,
ptracer_sid: initial_state.ptracer_sid,
}
);
}
#[fuchsia::test]
fn getsched_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_getsched_yes_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_getsched_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_getsched_access(&security_server.as_permission_check(), source_sid, target_sid),
Ok(())
);
}
#[fuchsia::test]
fn getsched_access_denied_for_denied_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_getsched_no_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_getsched_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_getsched_access(&security_server.as_permission_check(), source_sid, target_sid),
error!(EACCES)
);
}
#[fuchsia::test]
fn setsched_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_setsched_yes_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_setsched_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_setsched_access(&security_server.as_permission_check(), source_sid, target_sid),
Ok(())
);
}
#[fuchsia::test]
fn setsched_access_denied_for_denied_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_setsched_no_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_setsched_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_setsched_access(&security_server.as_permission_check(), source_sid, target_sid),
error!(EACCES)
);
}
#[fuchsia::test]
fn getpgid_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_getpgid_yes_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_getpgid_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_getpgid_access(&security_server.as_permission_check(), source_sid, target_sid),
Ok(())
);
}
#[fuchsia::test]
fn getpgid_access_denied_for_denied_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_getpgid_no_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_getpgid_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_getpgid_access(&security_server.as_permission_check(), source_sid, target_sid),
error!(EACCES)
);
}
#[fuchsia::test]
fn sigkill_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_sigkill_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_signal_access(
&security_server.as_permission_check(),
source_sid,
target_sid,
SIGKILL,
),
Ok(())
);
}
#[fuchsia::test]
fn sigchld_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_sigchld_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_signal_access(
&security_server.as_permission_check(),
source_sid,
target_sid,
SIGCHLD,
),
Ok(())
);
}
#[fuchsia::test]
fn sigstop_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_sigstop_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_target_t:s0")
.expect("invalid security context");
assert_eq!(
check_signal_access(
&security_server.as_permission_check(),
source_sid,
target_sid,
SIGSTOP,
),
Ok(())
);
}
#[fuchsia::test]
fn signal_access_allowed_for_allowed_type() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_signal_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_target_t:s0")
.expect("invalid security context");
// The `signal` permission allows signals other than SIGKILL, SIGCHLD, SIGSTOP.
assert_eq!(
check_signal_access(
&security_server.as_permission_check(),
source_sid,
target_sid,
SIGTERM,
),
Ok(())
);
}
#[fuchsia::test]
fn signal_access_denied_for_denied_signals() {
let security_server = security_server_with_policy();
let source_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_signal_t:s0")
.expect("invalid security context");
let target_sid = security_server
.security_context_to_sid(b"u:object_r:test_kill_target_t:s0")
.expect("invalid security context");
// The `signal` permission does not allow SIGKILL, SIGCHLD or SIGSTOP.
for signal in [SIGCHLD, SIGKILL, SIGSTOP] {
assert_eq!(
check_signal_access(
&security_server.as_permission_check(),
source_sid,
target_sid,
signal,
),
error!(EACCES)
);
}
}
#[fuchsia::test]
fn ptrace_access_allowed_for_allowed_type_and_state_is_updated() {
let security_server = security_server_with_policy();
let tracer_sid = security_server
.security_context_to_sid(b"u:object_r:test_ptrace_tracer_yes_t:s0")
.expect("invalid security context");
let tracee_sid = security_server
.security_context_to_sid(b"u:object_r:test_ptrace_traced_t:s0")
.expect("invalid security context");
let initial_state = ThreadGroupState {
current_sid: tracee_sid.clone(),
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
previous_sid: tracee_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
let mut tracee_state = initial_state.clone();
assert_eq!(
check_ptrace_access_and_update_state(
&security_server.as_permission_check(),
tracer_sid.clone(),
&mut tracee_state
),
Ok(())
);
assert_eq!(
tracee_state,
ThreadGroupState {
current_sid: initial_state.current_sid,
exec_sid: initial_state.exec_sid,
fscreate_sid: initial_state.fscreate_sid,
keycreate_sid: initial_state.keycreate_sid,
previous_sid: initial_state.previous_sid,
sockcreate_sid: initial_state.sockcreate_sid,
ptracer_sid: Some(tracer_sid),
}
);
}
#[fuchsia::test]
fn ptrace_access_denied_for_denied_type_and_state_is_not_updated() {
let security_server = security_server_with_policy();
let tracer_sid = security_server
.security_context_to_sid(b"u:object_r:test_ptrace_tracer_no_t:s0")
.expect("invalid security context");
let tracee_sid = security_server
.security_context_to_sid(b"u:object_r:test_ptrace_traced_t:s0")
.expect("invalid security context");
let initial_state = ThreadGroupState {
current_sid: tracee_sid.clone(),
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
previous_sid: tracee_sid,
sockcreate_sid: None,
ptracer_sid: None,
};
let mut tracee_state = initial_state.clone();
assert_eq!(
check_ptrace_access_and_update_state(
&security_server.as_permission_check(),
tracer_sid,
&mut tracee_state
),
error!(EACCES)
);
assert_eq!(initial_state, tracee_state);
}
#[fuchsia::test]
async fn alloc_security_no_parent() {
let security_state = alloc_security(None);
assert_eq!(
security_state,
ThreadGroupState {
current_sid: SecurityId::initial(InitialSid::Kernel),
previous_sid: SecurityId::initial(InitialSid::Kernel),
exec_sid: None,
fscreate_sid: None,
keycreate_sid: None,
sockcreate_sid: None,
ptracer_sid: None,
}
);
}
#[fuchsia::test]
async fn alloc_security_from_parent() {
// Create a fake parent state, with values for some fields, to check for.
let mut parent_security_state = alloc_security(None);
parent_security_state.current_sid = SecurityId::initial(InitialSid::Unlabeled);
parent_security_state.exec_sid = Some(SecurityId::initial(InitialSid::Unlabeled));
parent_security_state.fscreate_sid = Some(SecurityId::initial(InitialSid::Unlabeled));
parent_security_state.keycreate_sid = Some(SecurityId::initial(InitialSid::Unlabeled));
parent_security_state.sockcreate_sid = Some(SecurityId::initial(InitialSid::Unlabeled));
let security_state = alloc_security(Some(&parent_security_state));
assert_eq!(security_state, parent_security_state);
}
}