blob: 02779501c39485912d43f693fa6ffac9881c539c [file] [log] [blame]
// Copyright 2023 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::{
access_vector_cache::{Manager as AvcManager, Query, QueryMut},
permission_check::{PermissionCheck, PermissionCheckImpl},
seq_lock::SeqLock,
SecurityId,
};
use anyhow::Context as _;
use fuchsia_zircon::{self as zx};
use selinux_common::{
AbstractObjectClass, ClassPermission, FileClass, InitialSid, Permission, FIRST_UNUSED_SID,
};
use selinux_policy::{
metadata::HandleUnknown, parse_policy_by_value, parser::ByValue, AccessVector,
AccessVectorComputer, Policy, SecurityContext,
};
use starnix_sync::Mutex;
use std::{collections::HashMap, num::NonZeroU32, ops::DerefMut, sync::Arc};
use zerocopy::{AsBytes, NoCell};
/// The version of the SELinux "status" file this implementation implements.
const SELINUX_STATUS_VERSION: u32 = 1;
/// Specifies whether the implementation should be fully functional, or provide
/// only hard-coded fake information.
#[derive(Copy, Clone, Debug)]
pub enum Mode {
Enable,
Fake,
}
pub trait SecurityServerStatus {
/// Returns true if access decisions by the security server should be enforced by hooks.
fn is_enforcing(&self) -> bool;
/// Returns true if the security server is using a hard-coded fake policy.
fn is_fake(&self) -> bool;
}
struct LoadedPolicy {
/// Parsed policy structure.
parsed: Policy<ByValue<Vec<u8>>>,
/// The binary policy that was previously passed to `load_policy()`.
binary: Vec<u8>,
}
type SeLinuxStatus = SeqLock<SeLinuxStatusHeader, SeLinuxStatusValue>;
#[derive(Default)]
struct SeLinuxBooleans {
/// Active values for all of the booleans defined by the policy.
/// Entries are created at policy load for each policy-defined conditional.
active: HashMap<String, bool>,
/// Pending values for any booleans modified since the last commit.
pending: HashMap<String, bool>,
}
impl SeLinuxBooleans {
fn reset(&mut self, booleans: Vec<(String, bool)>) {
self.active = HashMap::from_iter(booleans);
self.pending.clear();
}
fn names(&mut self) -> Vec<String> {
self.active.keys().cloned().collect()
}
fn set_pending(&mut self, name: &str, value: bool) -> Result<(), ()> {
if !self.active.contains_key(name) {
return Err(());
}
self.pending.insert(name.into(), value);
Ok(())
}
fn get(&mut self, name: &str) -> Result<(bool, bool), ()> {
let active = self.active.get(name).ok_or(())?;
let pending = self.pending.get(name).unwrap_or(active);
Ok((*active, *pending))
}
fn commit_pending(&mut self) {
self.active.extend(self.pending.drain());
}
}
struct SecurityServerState {
/// Cache of SecurityIds (SIDs) used to refer to Security Contexts.
// TODO(http://b/308175643): reference count SIDs, so that when the last SELinux object
// referencing a SID gets destroyed, the entry is removed from the map.
sids: HashMap<SecurityId, SecurityContext>,
/// Identifier to allocate to the next new Security Context.
next_sid: NonZeroU32,
/// Describes the currently active policy.
policy: Option<Arc<LoadedPolicy>>,
/// Holds active and pending states for each boolean defined by policy.
booleans: SeLinuxBooleans,
/// Encapsulates the security server "status" fields, which are exposed to
/// userspace as a C-layout structure, and updated with the SeqLock pattern.
status: SeLinuxStatus,
/// True if hooks should enforce policy-based access decisions.
enforcing: bool,
/// Count of changes to the active policy. Changes include both loads
/// of complete new policies, and modifications to a previously loaded
/// policy, e.g. by committing new values to conditional booleans in it.
policy_change_count: u32,
}
impl SecurityServerState {
/// Looks up `security_context`, adding it if not found, and returns the SID.
fn security_context_to_sid(&mut self, security_context: SecurityContext) -> SecurityId {
match self.sids.iter().find(|(_, sc)| **sc == security_context) {
Some((sid, _)) => *sid,
None => {
// Create and insert a new SID for `security_context`.
let sid = SecurityId(self.next_sid);
self.next_sid = self.next_sid.checked_add(1).expect("exhausted SID namespace");
assert!(
self.sids.insert(sid, security_context).is_none(),
"impossible error: SID already exists."
);
sid
}
}
}
/// Returns the `SecurityContext` associated with `sid`.
/// If `sid` was invalidated by a policy reload then the "unlabeled" context is returned instead.
///
/// # Panics
///
/// This API panics if called before a policy has been loaded.
fn sid_to_security_context(&self, sid: SecurityId) -> &SecurityContext {
self.sids.get(&sid).unwrap_or_else(|| {
self.sids
.get(&SecurityId::initial(InitialSid::Unlabeled))
.expect("requires policy to be loaded")
})
}
/// Returns the `SecurityContext` associated with `sid`, unless `sid` was invalidated by a
/// policy reload. Query implementations should use `sid_to_security_context()`.
fn try_sid_to_security_context(&self, sid: SecurityId) -> Option<&SecurityContext> {
self.sids.get(&sid)
}
fn deny_unknown(&self) -> bool {
self.policy.as_ref().map_or(true, |p| *p.parsed.handle_unknown() != HandleUnknown::Allow)
}
fn reject_unknown(&self) -> bool {
self.policy.as_ref().map_or(false, |p| *p.parsed.handle_unknown() == HandleUnknown::Reject)
}
}
pub struct SecurityServer {
/// Determines whether the security server is enabled, or only provides
/// a hard-coded set of fake responses.
mode: Mode,
/// Manager for any access vector cache layers that are shared between threads subject to access
/// control by this security server. This [`AvcManager`] is also responsible for constructing
/// thread-local caches for use by individual threads that subject to access control by this
/// security server.
avc_manager: AvcManager<SecurityServer>,
/// The mutable state of the security server.
state: Mutex<SecurityServerState>,
}
impl SecurityServer {
pub fn new(mode: Mode) -> Arc<Self> {
let avc_manager = AvcManager::new();
let status = SeLinuxStatus::new_default().expect("Failed to create SeLinuxStatus");
let state = Mutex::new(SecurityServerState {
sids: HashMap::new(),
next_sid: NonZeroU32::new(FIRST_UNUSED_SID).unwrap(),
policy: None,
booleans: SeLinuxBooleans::default(),
status,
enforcing: false,
policy_change_count: 0,
});
let security_server = Arc::new(Self { mode, avc_manager, state });
// TODO(http://b/304776236): Consider constructing shared owner of `AvcManager` and
// `SecurityServer` to eliminate weak reference.
security_server.as_ref().avc_manager.set_security_server(Arc::downgrade(&security_server));
security_server
}
/// Converts a shared pointer to [`SecurityServer`] to a [`PermissionCheck`] without consuming
/// the pointer.
pub fn as_permission_check<'a>(self: &'a Arc<Self>) -> impl PermissionCheck + 'a {
PermissionCheckImpl::new(self, self.avc_manager.get_shared_cache())
}
/// Returns the security ID mapped to `security_context`, creating it if it does not exist.
///
/// All objects with the same security context will have the same SID associated.
pub fn security_context_to_sid(
&self,
security_context: &[u8],
) -> Result<SecurityId, anyhow::Error> {
let mut state = self.state.lock();
let policy = &state.policy.as_ref().ok_or(anyhow::anyhow!("no policy loaded"))?.parsed;
let context =
policy.parse_security_context(security_context).map_err(anyhow::Error::from)?;
Ok(state.security_context_to_sid(context))
}
/// Returns the Security Context string for the requested `sid`.
/// This is used only where Contexts need to be stringified to expose to userspace, as
/// is the case for e.g. the `/proc/*/attr/` filesystem.
pub fn sid_to_security_context(&self, sid: SecurityId) -> Option<Vec<u8>> {
let state = self.state.lock();
let context = state.try_sid_to_security_context(sid)?;
Some(state.policy.as_ref()?.parsed.serialize_security_context(context))
}
/// Applies the supplied policy to the security server.
pub fn load_policy(&self, binary_policy: Vec<u8>) -> Result<(), anyhow::Error> {
// Parse the supplied policy, and reject the load operation if it is
// malformed or invalid.
let (parsed, binary) = parse_policy_by_value(binary_policy)?;
let parsed = parsed.validate()?;
// Bundle the binary policy together with a parsed copy for the
// [`SecurityServer`] to use to answer queries. This will fail if the
// supplied policy cannot be parsed due to being malformed, or if the
// parsed policy is not valid.
let policy = Arc::new(LoadedPolicy { parsed, binary });
// Replace any existing policy and update the [`SeLinuxStatus`].
self.with_state_and_update_status(|state| {
// Remap any existing Security Contexts to use Ids defined by the new policy.
// TODO(b/330677360): Replace serialize/parse with an efficient implementation.
assert_eq!(state.policy.is_none(), state.sids.is_empty());
let new_sids = state.sids.iter().filter_map(|(sid, context)| {
let context_str =
state.policy.as_ref().unwrap().parsed.serialize_security_context(context);
let new_context = policy.parsed.parse_security_context(context_str.as_slice());
new_context.ok().map(|context| (*sid, context))
});
state.sids = HashMap::from_iter(new_sids);
// Replace the "initial" SID's associated Contexts.
let initial_sids = InitialSid::all_variants();
let mut initial_contexts = Vec::with_capacity(initial_sids.len());
for id in InitialSid::all_variants() {
let security_context = policy.parsed.initial_context(id);
initial_contexts.push((SecurityId::initial(id), security_context));
}
state.sids.extend(initial_contexts);
// TODO(b/324265752): Determine whether SELinux booleans need to be retained across
// policy (re)loads.
state.booleans.reset(
policy
.parsed
.conditional_booleans()
.iter()
// TODO(b/324392507): Relax the UTF8 requirement on policy strings.
.map(|(name, value)| (String::from_utf8((*name).to_vec()).unwrap(), *value))
.collect(),
);
state.policy = Some(policy);
state.policy_change_count += 1;
});
Ok(())
}
/// Returns the active policy in binary form.
pub fn get_binary_policy(&self) -> Vec<u8> {
self.state.lock().policy.as_ref().map_or(Vec::new(), |p| p.binary.clone())
}
/// Returns true if a policy has been loaded.
pub fn has_policy(&self) -> bool {
self.state.lock().policy.is_some()
}
/// Set to enforcing mode if `enforce` is true, permissive mode otherwise.
pub fn set_enforcing(&self, enforcing: bool) {
self.with_state_and_update_status(|state| state.enforcing = enforcing);
}
/// Returns true if the policy requires unknown class / permissions to be
/// denied. Defaults to true until a policy is loaded.
pub fn deny_unknown(&self) -> bool {
if self.is_fake() {
false
} else {
self.state.lock().deny_unknown()
}
}
/// Returns true if the policy requires unknown class / permissions to be
/// rejected. Defaults to false until a policy is loaded.
pub fn reject_unknown(&self) -> bool {
if self.is_fake() {
false
} else {
self.state.lock().reject_unknown()
}
}
/// Returns the list of names of boolean conditionals defined by the
/// loaded policy.
pub fn conditional_booleans(&self) -> Vec<String> {
self.state.lock().booleans.names()
}
/// Returns the active and pending values of a policy boolean, if it exists.
pub fn get_boolean(&self, name: &str) -> Result<(bool, bool), ()> {
self.state.lock().booleans.get(name)
}
/// Sets the pending value of a boolean, if it is defined in the policy.
pub fn set_pending_boolean(&self, name: &str, value: bool) -> Result<(), ()> {
self.state.lock().booleans.set_pending(name, value)
}
/// Commits all pending changes to conditional booleans.
pub fn commit_pending_booleans(&self) {
// TODO(b/324264149): Commit values into the stored policy itself.
self.with_state_and_update_status(|state| {
state.booleans.commit_pending();
state.policy_change_count += 1;
});
}
/// Computes the precise access vector for `source_sid` targeting `target_sid` as class
/// `target_class`.
///
/// TODO(http://b/305722921): Implement complete access decision logic. For now, the security
/// server abides by explicit `allow [source] [target]:[class] [permissions..];` statements.
pub fn compute_access_vector(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessVector {
let state = self.state.lock();
let policy = match &state.policy {
Some(policy) => policy,
// Policy is "allow all" when no policy is loaded, regardless of enforcing state.
None => return AccessVector::ALL,
};
// Policy is loaded, so `sid_to_security_context()` will not panic.
let source_context = state.sid_to_security_context(source_sid);
let target_context = state.sid_to_security_context(target_sid);
match target_class {
AbstractObjectClass::System(target_class) => policy
.parsed
.compute_explicitly_allowed(
source_context.type_(),
target_context.type_(),
target_class,
)
.unwrap_or(AccessVector::NONE),
AbstractObjectClass::Custom(target_class) => policy
.parsed
.compute_explicitly_allowed_custom(
source_context.type_(),
target_context.type_(),
&target_class,
)
.unwrap_or(AccessVector::NONE),
// No meaningful policy can be determined without target class.
_ => AccessVector::NONE,
}
}
/// Computes the appropriate security identifier (SID) for the security context of a file-like
/// object of class `file_class` created by `source_sid` targeting `target_sid`.
pub fn compute_new_file_sid(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
let mut state = self.state.lock();
let policy = match &state.policy {
Some(policy) => policy,
None => {
return Err(anyhow::anyhow!("no policy loaded")).context("computing new file sid")
}
};
// Policy is loaded, so `sid_to_security_context()` will not panic.
let source_context = state.sid_to_security_context(source_sid);
let target_context = state.sid_to_security_context(target_sid);
policy
.parsed
.new_file_security_context(source_context, target_context, &file_class)
.map(|sc| state.security_context_to_sid(sc))
.map_err(anyhow::Error::from)
.context("computing new file security context from policy")
}
/// Returns a read-only VMO containing the SELinux "status" structure.
pub fn get_status_vmo(&self) -> Arc<zx::Vmo> {
self.state.lock().status.get_readonly_vmo()
}
/// Returns a reference to the shared access vector cache that delebates cache misses to `self`.
pub fn get_shared_avc(&self) -> &impl Query {
self.avc_manager.get_shared_cache()
}
/// Returns a newly constructed thread-local access vector cache that delegates cache misses to
/// any shared caches owned by `self.avc_manager`, which ultimately delegate to `self`. The
/// returned cache will be reset when this security server's policy is reset.
pub fn new_thread_local_avc(&self) -> impl QueryMut {
self.avc_manager.new_thread_local_cache()
}
// Runs the supplied function with locked `self`, and then updates the [`SeLinuxStatus`].
fn with_state_and_update_status(&self, f: impl FnOnce(&mut SecurityServerState)) {
let mut state = self.state.lock();
f(state.deref_mut());
let new_value = SeLinuxStatusValue {
enforcing: state.enforcing as u32,
policyload: state.policy_change_count,
deny_unknown: if self.is_fake() { 0 } else { state.deny_unknown() as u32 },
};
state.status.set_value(new_value);
}
}
impl SecurityServerStatus for SecurityServer {
fn is_enforcing(&self) -> bool {
self.state.lock().enforcing
}
fn is_fake(&self) -> bool {
match self.mode {
Mode::Fake => true,
_ => false,
}
}
}
impl Query for SecurityServer {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessVector {
self.compute_access_vector(source_sid, target_sid, target_class)
}
}
impl AccessVectorComputer for SecurityServer {
fn access_vector_from_permission<P: ClassPermission + Into<Permission> + 'static>(
&self,
permission: P,
) -> AccessVector {
match &self.state.lock().policy {
Some(policy) => policy.parsed.access_vector_from_permission(permission),
None => AccessVector::NONE,
}
}
fn access_vector_from_permissions<
P: ClassPermission + Into<Permission> + 'static,
PI: IntoIterator<Item = P>,
>(
&self,
permissions: PI,
) -> AccessVector {
match &self.state.lock().policy {
Some(policy) => policy.parsed.access_vector_from_permissions(permissions),
None => AccessVector::NONE,
}
}
}
/// Header of the C-style struct exposed via the /sys/fs/selinux/status file,
/// to userspace. Defined here (instead of imported through bindgen) as selinux
/// headers are not exposed through kernel uapi headers.
#[derive(AsBytes, Copy, Clone, NoCell)]
#[repr(C, align(4))]
struct SeLinuxStatusHeader {
/// Version number of this structure (1).
version: u32,
}
impl Default for SeLinuxStatusHeader {
fn default() -> Self {
Self { version: SELINUX_STATUS_VERSION }
}
}
/// Value part of the C-style struct exposed via the /sys/fs/selinux/status file,
/// to userspace. Defined here (instead of imported through bindgen) as selinux
/// headers are not exposed through kernel uapi headers.
#[derive(AsBytes, Copy, Clone, Default, NoCell)]
#[repr(C, align(4))]
struct SeLinuxStatusValue {
/// `0` means permissive mode, `1` means enforcing mode.
enforcing: u32,
/// The number of times the selinux policy has been reloaded.
policyload: u32,
/// `0` means allow and `1` means deny unknown object classes/permissions.
deny_unknown: u32,
}
#[cfg(test)]
mod tests {
use super::*;
use fuchsia_zircon::AsHandleRef as _;
use selinux_common::ObjectClass;
use std::mem::size_of;
use zerocopy::{FromBytes, FromZeroes};
const TESTSUITE_BINARY_POLICY: &[u8] = include_bytes!("../testdata/policies/selinux_testsuite");
const TESTS_BINARY_POLICY: &[u8] =
include_bytes!("../testdata/micro_policies/security_server_tests_policy.pp");
fn security_server_with_tests_policy() -> Arc<SecurityServer> {
let policy_bytes = TESTS_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new(Mode::Enable);
assert_eq!(
Ok(()),
security_server.load_policy(policy_bytes).map_err(|e| format!("{:?}", e))
);
security_server
}
#[fuchsia::test]
fn sid_to_security_context() {
let security_context = b"user0:unconfined_r:unconfined_t:s0";
let security_server = security_server_with_tests_policy();
let sid = security_server
.security_context_to_sid(security_context)
.expect("creating SID from security context should succeed");
assert_eq!(
security_server.sid_to_security_context(sid).expect("sid not found"),
security_context
);
}
#[fuchsia::test]
fn sids_for_different_security_contexts_differ() {
let security_server = security_server_with_tests_policy();
let sid1 = security_server
.security_context_to_sid(b"user0:object_r:type0:s0")
.expect("creating SID from security context should succeed");
let sid2 = security_server
.security_context_to_sid(b"user0:unconfined_r:unconfined_t:s0")
.expect("creating SID from security context should succeed");
assert_ne!(sid1, sid2);
}
#[fuchsia::test]
fn sids_for_same_security_context_are_equal() {
let security_context = b"user0:unconfined_r:unconfined_t:s0";
let security_server = security_server_with_tests_policy();
let sid_count_before = security_server.state.lock().sids.len();
let sid1 = security_server
.security_context_to_sid(security_context)
.expect("creating SID from security context should succeed");
let sid2 = security_server
.security_context_to_sid(security_context)
.expect("creating SID from security context should succeed");
assert_eq!(sid1, sid2);
assert_eq!(security_server.state.lock().sids.len(), sid_count_before + 1);
}
#[fuchsia::test]
fn sids_allocated_outside_initial_range() {
let security_context = b"user0:unconfined_r:unconfined_t:s0";
let security_server = security_server_with_tests_policy();
let sid_count_before = security_server.state.lock().sids.len();
let sid = security_server
.security_context_to_sid(security_context)
.expect("creating SID from security context should succeed");
assert_eq!(security_server.state.lock().sids.len(), sid_count_before + 1);
assert!(sid.0.get() >= FIRST_UNUSED_SID);
}
#[fuchsia::test]
fn compute_access_vector_allows_all() {
let security_server = SecurityServer::new(Mode::Enable);
let sid1 = SecurityId::initial(InitialSid::Kernel);
let sid2 = SecurityId::initial(InitialSid::Unlabeled);
assert_eq!(
security_server.compute_access_vector(sid1, sid2, ObjectClass::Process.into()),
AccessVector::ALL
);
}
#[fuchsia::test]
fn fake_security_server_is_fake() {
let security_server = SecurityServer::new(Mode::Enable);
assert_eq!(security_server.is_fake(), false);
let fake_security_server = SecurityServer::new(Mode::Fake);
assert_eq!(fake_security_server.is_fake(), true);
}
#[fuchsia::test]
fn loaded_policy_can_be_retrieved() {
let security_server = security_server_with_tests_policy();
assert_eq!(TESTS_BINARY_POLICY, security_server.get_binary_policy().as_slice());
}
#[fuchsia::test]
fn loaded_policy_is_validated() {
let not_really_a_policy = "not a real policy".as_bytes().to_vec();
let security_server = SecurityServer::new(Mode::Enable);
assert!(security_server.load_policy(not_really_a_policy.clone()).is_err());
}
#[fuchsia::test]
fn enforcing_mode_is_reported() {
let security_server = SecurityServer::new(Mode::Enable);
assert!(!security_server.is_enforcing());
security_server.set_enforcing(true);
assert!(security_server.is_enforcing());
}
#[fuchsia::test]
fn without_policy_conditional_booleans_are_empty() {
let security_server = SecurityServer::new(Mode::Enable);
assert!(security_server.conditional_booleans().is_empty());
}
#[fuchsia::test]
fn conditional_booleans_can_be_queried() {
let policy_bytes = TESTSUITE_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new(Mode::Enable);
assert_eq!(
Ok(()),
security_server.load_policy(policy_bytes).map_err(|e| format!("{:?}", e))
);
let booleans = security_server.conditional_booleans();
assert!(!booleans.is_empty());
let boolean = booleans[0].as_str();
assert!(security_server.get_boolean("this_is_not_a_valid_boolean_name").is_err());
assert!(security_server.get_boolean(boolean).is_ok());
}
#[fuchsia::test]
fn conditional_booleans_can_be_changed() {
let policy_bytes = TESTSUITE_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new(Mode::Enable);
assert_eq!(
Ok(()),
security_server.load_policy(policy_bytes).map_err(|e| format!("{:?}", e))
);
let booleans = security_server.conditional_booleans();
assert!(!booleans.is_empty());
let boolean = booleans[0].as_str();
let (active, pending) = security_server.get_boolean(boolean).unwrap();
assert_eq!(active, pending, "Initially active and pending values should match");
security_server.set_pending_boolean(boolean, !active).unwrap();
let (active, pending) = security_server.get_boolean(boolean).unwrap();
assert!(active != pending, "Before commit pending should differ from active");
security_server.commit_pending_booleans();
let (final_active, final_pending) = security_server.get_boolean(boolean).unwrap();
assert_eq!(final_active, pending, "Pending value should be active after commit");
assert_eq!(final_active, final_pending, "Active and pending are the same after commit");
}
#[fuchsia::test]
fn status_vmo_has_correct_size_and_rights() {
// The current version of the "status" file contains five packed
// u32 values.
const STATUS_T_SIZE: usize = size_of::<u32>() * 5;
let security_server = SecurityServer::new(Mode::Enable);
let status_vmo = security_server.get_status_vmo();
// Verify the content and actual size of the structure are as expected.
let content_size = status_vmo.get_content_size().unwrap() as usize;
assert_eq!(content_size, STATUS_T_SIZE);
let actual_size = status_vmo.get_size().unwrap() as usize;
assert!(actual_size >= STATUS_T_SIZE);
// Ensure the returned handle is read-only and non-resizable.
let rights = status_vmo.basic_info().unwrap().rights;
assert_eq!((rights & zx::Rights::MAP), zx::Rights::MAP);
assert_eq!((rights & zx::Rights::READ), zx::Rights::READ);
assert_eq!((rights & zx::Rights::GET_PROPERTY), zx::Rights::GET_PROPERTY);
assert_eq!((rights & zx::Rights::WRITE), zx::Rights::NONE);
assert_eq!((rights & zx::Rights::RESIZE), zx::Rights::NONE);
}
#[derive(FromBytes, FromZeroes)]
#[repr(C, align(4))]
struct TestSeLinuxStatusT {
version: u32,
sequence: u32,
enforcing: u32,
policyload: u32,
deny_unknown: u32,
}
fn with_status_t<R>(
security_server: &SecurityServer,
do_test: impl FnOnce(&TestSeLinuxStatusT) -> R,
) -> R {
let flags = zx::VmarFlags::PERM_READ
| zx::VmarFlags::ALLOW_FAULTS
| zx::VmarFlags::REQUIRE_NON_RESIZABLE;
let map_addr = fuchsia_runtime::vmar_root_self()
.map(0, &security_server.get_status_vmo(), 0, size_of::<TestSeLinuxStatusT>(), flags)
.unwrap();
let mapped_status = unsafe { &mut *(map_addr as *mut TestSeLinuxStatusT) };
let result = do_test(mapped_status);
unsafe {
fuchsia_runtime::vmar_root_self()
.unmap(map_addr, size_of::<TestSeLinuxStatusT>())
.unwrap()
};
result
}
#[fuchsia::test]
fn status_file_layout() {
let security_server = SecurityServer::new(Mode::Enable);
security_server.set_enforcing(false);
let mut seq_no: u32 = 0;
with_status_t(&security_server, |status| {
assert_eq!(status.version, SELINUX_STATUS_VERSION);
assert_eq!(status.enforcing, 0);
seq_no = status.sequence;
assert_eq!(seq_no % 2, 0);
});
security_server.set_enforcing(true);
with_status_t(&security_server, |status| {
assert_eq!(status.version, SELINUX_STATUS_VERSION);
assert_eq!(status.enforcing, 1);
assert_ne!(status.sequence, seq_no);
seq_no = status.sequence;
assert_eq!(seq_no % 2, 0);
});
}
#[fuchsia::test]
fn parse_security_context_no_policy() {
let security_server = SecurityServer::new(Mode::Enable);
let error = security_server
.security_context_to_sid(b"user0:unconfined_r:unconfined_t:s0")
.expect_err("expected error");
let error_string = format!("{:?}", error);
assert!(error_string.contains("no policy"));
}
#[fuchsia::test]
fn compute_new_file_sid_no_policy() {
let security_server = SecurityServer::new(Mode::Enable);
// Only initial SIDs can exist, until a policy has been loaded.
let source_sid = SecurityId::initial(InitialSid::Kernel);
let target_sid = SecurityId::initial(InitialSid::Unlabeled);
let error = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect_err("expected error");
let error_string = format!("{:?}", error);
assert!(error_string.contains("no policy"));
}
#[fuchsia::test]
fn compute_new_file_sid_unknown_sids() {
let security_server = security_server_with_tests_policy();
// Synthesize two SIDs having been created, and subsequently invalidated.
let source_sid = SecurityId(NonZeroU32::new(FIRST_UNUSED_SID + 1).unwrap());
let target_sid = SecurityId(NonZeroU32::new(FIRST_UNUSED_SID + 2).unwrap());
// Both source and target will fall-back to the "unlabeled" Context,
// so that the new file Context will be identical to "unlabeled", and the
// same SID will be returned.
let sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
assert_eq!(sid, SecurityId::initial(InitialSid::Unlabeled));
}
#[fuchsia::test]
fn compute_new_file_sid_no_defaults() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_no_defaults_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0-s1")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User and low security level should be copied from the source,
// and the role and type from the target.
assert_eq!(computed_context, b"user_u:object_r:file_t:s0");
}
#[fuchsia::test]
fn compute_new_file_sid_source_defaults() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_source_defaults_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0-s2:c0")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s1-s3:c0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// All fields should be copied from the source, but only the "low" part of the security
// range.
assert_eq!(computed_context, b"user_u:unconfined_r:unconfined_t:s0");
}
#[fuchsia::test]
fn compute_new_file_sid_target_defaults() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_target_defaults_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0-s2:c0")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s1-s3:c0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User, role and type copied from target, with source's low security level.
assert_eq!(computed_context, b"file_u:object_r:file_t:s0");
}
#[fuchsia::test]
fn compute_new_file_sid_range_source_low_default() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_range_source_low_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0-s1:c0")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s1")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User and low security level copied from source, role and type as default.
assert_eq!(computed_context, b"user_u:object_r:file_t:s0");
}
#[fuchsia::test]
fn compute_new_file_sid_range_source_low_high_default() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_range_source_low_high_policy.pp")
.to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0-s1:c0")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s1")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User and full security range copied from source, role and type as default.
assert_eq!(computed_context, b"user_u:object_r:file_t:s0-s1:c0");
}
#[fuchsia::test]
fn compute_new_file_sid_range_source_high_default() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_range_source_high_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0-s1:c0")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User and high security level copied from source, role and type as default.
assert_eq!(computed_context, b"user_u:object_r:file_t:s1:c0");
}
#[fuchsia::test]
fn compute_new_file_sid_range_target_low_default() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_range_target_low_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s1")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s0-s1:c0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User copied from source, low security level from target, role and type as default.
assert_eq!(computed_context, b"user_u:object_r:file_t:s0");
}
#[fuchsia::test]
fn compute_new_file_sid_range_target_low_high_default() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_range_target_low_high_policy.pp")
.to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s1")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s0-s1:c0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User copied from source, full security range from target, role and type as default.
assert_eq!(computed_context, b"user_u:object_r:file_t:s0-s1:c0");
}
#[fuchsia::test]
fn compute_new_file_sid_range_target_high_default() {
let security_server = SecurityServer::new(Mode::Enable);
let policy_bytes =
include_bytes!("../testdata/micro_policies/file_range_target_high_policy.pp").to_vec();
security_server.load_policy(policy_bytes).expect("binary policy loads");
let source_sid = security_server
.security_context_to_sid(b"user_u:unconfined_r:unconfined_t:s0")
.expect("creating SID from security context should succeed");
let target_sid = security_server
.security_context_to_sid(b"file_u:object_r:file_t:s0-s1:c0")
.expect("creating SID from security context should succeed");
let computed_sid = security_server
.compute_new_file_sid(source_sid, target_sid, FileClass::File)
.expect("new sid computed");
let computed_context = security_server
.sid_to_security_context(computed_sid)
.expect("computed sid associated with context");
// User copied from source, high security level from target, role and type as default.
assert_eq!(computed_context, b"user_u:object_r:file_t:s1:c0");
}
}