use crate::access_vector_cache::{
CacheStats, HasCacheStats, Manager as AvcManager, Query, QueryMut, Reset,
};
use crate::permission_check::PermissionCheck;
use crate::policy::metadata::HandleUnknown;
use crate::policy::parser::ByValue;
use crate::policy::{
parse_policy_by_value, AccessDecision, AccessVector, AccessVectorComputer, ClassId,
FsUseLabelAndType, FsUseType, Policy,
};
use crate::sid_table::SidTable;
use crate::sync::Mutex;
use crate::{
AbstractObjectClass, ClassPermission, FileClass, FileSystemLabel, FileSystemLabelingScheme,
FileSystemMountOptions, InitialSid, NullessByteStr, ObjectClass, Permission, SeLinuxStatus,
SeLinuxStatusPublisher, SecurityId,
};
use anyhow::Context as _;
use std::collections::HashMap;
use std::ops::DerefMut;
use std::sync::Arc;
const ROOT_PATH: &'static str = "/";
struct ActivePolicy {
parsed: Arc<Policy<ByValue<Vec<u8>>>>,
binary: Vec<u8>,
sid_table: SidTable,
}
#[derive(Default)]
struct SeLinuxBooleans {
active: HashMap<String, bool>,
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(&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(&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 {
active_policy: Option<ActivePolicy>,
booleans: SeLinuxBooleans,
status_publisher: Option<Box<dyn SeLinuxStatusPublisher>>,
enforcing: bool,
policy_change_count: u32,
}
impl SecurityServerState {
fn deny_unknown(&self) -> bool {
self.active_policy
.as_ref()
.map_or(true, |p| p.parsed.handle_unknown() != HandleUnknown::Allow)
}
fn reject_unknown(&self) -> bool {
self.active_policy
.as_ref()
.map_or(false, |p| p.parsed.handle_unknown() == HandleUnknown::Reject)
}
fn expect_active_policy(&self) -> &ActivePolicy {
&self.active_policy.as_ref().expect("policy should be loaded")
}
fn expect_active_policy_mut(&mut self) -> &mut ActivePolicy {
self.active_policy.as_mut().expect("policy should be loaded")
}
}
pub struct SecurityServer {
avc_manager: AvcManager<SecurityServer>,
state: Mutex<SecurityServerState>,
}
impl SecurityServer {
pub fn new() -> Arc<Self> {
let avc_manager = AvcManager::new();
let state = Mutex::new(SecurityServerState {
active_policy: None,
booleans: SeLinuxBooleans::default(),
status_publisher: None,
enforcing: false,
policy_change_count: 0,
});
let security_server = Arc::new(Self { avc_manager, state });
security_server.as_ref().avc_manager.set_security_server(Arc::downgrade(&security_server));
security_server
}
pub fn as_permission_check<'a>(self: &'a Self) -> PermissionCheck<'a> {
PermissionCheck::new(self, self.avc_manager.get_shared_cache())
}
pub fn security_context_to_sid(
&self,
security_context: NullessByteStr<'_>,
) -> Result<SecurityId, anyhow::Error> {
let mut locked_state = self.state.lock();
let active_policy = locked_state
.active_policy
.as_mut()
.ok_or_else(|| anyhow::anyhow!("no policy loaded"))?;
let context = active_policy
.parsed
.parse_security_context(security_context)
.map_err(anyhow::Error::from)?;
active_policy.sid_table.security_context_to_sid(&context).map_err(anyhow::Error::from)
}
pub fn sid_to_security_context(&self, sid: SecurityId) -> Option<Vec<u8>> {
let locked_state = self.state.lock();
let active_policy = locked_state.active_policy.as_ref()?;
let context = active_policy.sid_table.try_sid_to_security_context(sid)?;
Some(active_policy.parsed.serialize_security_context(context))
}
pub fn load_policy(&self, binary_policy: Vec<u8>) -> Result<(), anyhow::Error> {
let (parsed, binary) = parse_policy_by_value(binary_policy)?;
let parsed = Arc::new(parsed.validate()?);
self.with_state_and_update_status(|state| {
let sid_table = if let Some(previous_active_policy) = &state.active_policy {
SidTable::new_from_previous(parsed.clone(), &previous_active_policy.sid_table)
} else {
SidTable::new(parsed.clone())
};
state.booleans.reset(
parsed
.conditional_booleans()
.iter()
.map(|(name, value)| (String::from_utf8((*name).to_vec()).unwrap(), *value))
.collect(),
);
state.active_policy = Some(ActivePolicy { parsed, binary, sid_table });
state.policy_change_count += 1;
});
self.avc_manager.reset();
Ok(())
}
pub fn get_binary_policy(&self) -> Vec<u8> {
self.state.lock().active_policy.as_ref().map_or(Vec::new(), |p| p.binary.clone())
}
pub fn has_policy(&self) -> bool {
self.state.lock().active_policy.is_some()
}
pub fn set_enforcing(&self, enforcing: bool) {
self.with_state_and_update_status(|state| state.enforcing = enforcing);
}
pub fn is_enforcing(&self) -> bool {
self.state.lock().enforcing
}
pub fn deny_unknown(&self) -> bool {
self.state.lock().deny_unknown()
}
pub fn reject_unknown(&self) -> bool {
self.state.lock().reject_unknown()
}
pub fn conditional_booleans(&self) -> Vec<String> {
self.state.lock().booleans.names()
}
pub fn get_boolean(&self, name: &str) -> Result<(bool, bool), ()> {
self.state.lock().booleans.get(name)
}
pub fn set_pending_boolean(&self, name: &str, value: bool) -> Result<(), ()> {
self.state.lock().booleans.set_pending(name, value)
}
pub fn commit_pending_booleans(&self) {
self.with_state_and_update_status(|state| {
state.booleans.commit_pending();
state.policy_change_count += 1;
});
}
pub fn avc_cache_stats(&self) -> CacheStats {
self.avc_manager.get_shared_cache().cache_stats()
}
pub fn class_names(&self) -> Result<Vec<Vec<u8>>, ()> {
let locked_state = self.state.lock();
let names = locked_state
.expect_active_policy()
.parsed
.classes()
.iter()
.map(|class| class.class_name.to_vec())
.collect();
Ok(names)
}
pub fn class_id_by_name(&self, name: &str) -> Result<ClassId, ()> {
let locked_state = self.state.lock();
Ok(locked_state
.expect_active_policy()
.parsed
.classes()
.iter()
.find(|class| class.class_name == name.as_bytes())
.ok_or(())?
.class_id)
}
pub fn class_permissions_by_name(&self, name: &str) -> Result<Vec<(u32, Vec<u8>)>, ()> {
let locked_state = self.state.lock();
locked_state.expect_active_policy().parsed.find_class_permissions_by_name(name)
}
pub fn resolve_fs_label(
&self,
fs_type: NullessByteStr<'_>,
mount_options: &FileSystemMountOptions,
) -> FileSystemLabel {
let mut locked_state = self.state.lock();
let active_policy = locked_state.expect_active_policy_mut();
let mountpoint_sid_from_mount_option =
sid_from_mount_option(active_policy, &mount_options.context);
let fs_sid_from_mount_option =
sid_from_mount_option(active_policy, &mount_options.fs_context);
let def_sid_from_mount_option =
sid_from_mount_option(active_policy, &mount_options.def_context);
let root_sid_from_mount_option =
sid_from_mount_option(active_policy, &mount_options.root_context);
if let Some(mountpoint_sid) = mountpoint_sid_from_mount_option {
FileSystemLabel {
sid: fs_sid_from_mount_option.unwrap_or(mountpoint_sid),
scheme: FileSystemLabelingScheme::Mountpoint { sid: mountpoint_sid },
}
} else if let Some(FsUseLabelAndType { context, use_type }) =
active_policy.parsed.fs_use_label_and_type(fs_type)
{
let fs_sid_from_policy =
active_policy.sid_table.security_context_to_sid(&context).unwrap();
let fs_sid = fs_sid_from_mount_option.unwrap_or(fs_sid_from_policy);
FileSystemLabel {
sid: fs_sid,
scheme: FileSystemLabelingScheme::FsUse {
fs_use_type: use_type,
def_sid: def_sid_from_mount_option
.unwrap_or_else(|| SecurityId::initial(InitialSid::File)),
root_sid: root_sid_from_mount_option.unwrap_or(fs_sid),
},
}
} else if let Some(context) =
active_policy.parsed.genfscon_label_for_fs_and_path(fs_type, ROOT_PATH.into(), None)
{
let genfscon_sid = active_policy.sid_table.security_context_to_sid(&context).unwrap();
let fs_sid = fs_sid_from_mount_option.unwrap_or(genfscon_sid);
FileSystemLabel { sid: fs_sid, scheme: FileSystemLabelingScheme::GenFsCon }
} else {
let unrecognized_filesystem_type_sid = SecurityId::initial(InitialSid::File);
let unrecognized_filesystem_type_fs_use_type = FsUseType::Xattr;
FileSystemLabel {
sid: fs_sid_from_mount_option.unwrap_or(unrecognized_filesystem_type_sid),
scheme: FileSystemLabelingScheme::FsUse {
fs_use_type: unrecognized_filesystem_type_fs_use_type,
def_sid: def_sid_from_mount_option.unwrap_or(unrecognized_filesystem_type_sid),
root_sid: root_sid_from_mount_option
.unwrap_or(unrecognized_filesystem_type_sid),
},
}
}
}
pub fn genfscon_label_for_fs_and_path(
&self,
fs_type: NullessByteStr<'_>,
node_path: NullessByteStr<'_>,
class_id: Option<ClassId>,
) -> Option<SecurityId> {
let mut locked_state = self.state.lock();
let active_policy = locked_state.expect_active_policy_mut();
let security_context = active_policy.parsed.genfscon_label_for_fs_and_path(
fs_type,
node_path.into(),
class_id,
)?;
Some(active_policy.sid_table.security_context_to_sid(&security_context).unwrap())
}
fn compute_access_vector(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
let locked_state = self.state.lock();
let active_policy = match &locked_state.active_policy {
Some(active_policy) => active_policy,
None => return AccessDecision::allow(AccessVector::ALL),
};
let source_context = active_policy.sid_table.sid_to_security_context(source_sid);
let target_context = active_policy.sid_table.sid_to_security_context(target_sid);
match target_class {
AbstractObjectClass::System(target_class) => {
active_policy.parsed.compute_explicitly_allowed(
source_context.type_(),
target_context.type_(),
target_class,
)
}
AbstractObjectClass::Custom(target_class) => {
active_policy.parsed.compute_explicitly_allowed_custom(
source_context.type_(),
target_context.type_(),
&target_class,
)
}
_ => AccessDecision::allow(AccessVector::NONE),
}
}
pub fn compute_new_sid(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: ObjectClass,
) -> Result<SecurityId, anyhow::Error> {
let mut locked_state = self.state.lock();
let active_policy = locked_state.expect_active_policy_mut();
let source_context = active_policy.sid_table.sid_to_security_context(source_sid);
let target_context = active_policy.sid_table.sid_to_security_context(target_sid);
active_policy
.sid_table
.security_context_to_sid(&active_policy.parsed.new_security_context(
source_context,
target_context,
&target_class,
))
.map_err(anyhow::Error::from)
.context("computing new security context from policy")
}
pub fn is_bounded_by(&self, bounded_sid: SecurityId, parent_sid: SecurityId) -> bool {
let locked_state = self.state.lock();
let active_policy = locked_state.expect_active_policy();
let bounded_type = active_policy.sid_table.sid_to_security_context(bounded_sid).type_();
let parent_type = active_policy.sid_table.sid_to_security_context(parent_sid).type_();
active_policy.parsed.is_bounded_by(bounded_type, parent_type)
}
pub fn set_status_publisher(&self, status_holder: Box<dyn SeLinuxStatusPublisher>) {
self.with_state_and_update_status(|state| {
assert!(state.status_publisher.is_none());
state.status_publisher = Some(status_holder);
});
}
pub fn get_shared_avc(&self) -> &impl Query {
self.avc_manager.get_shared_cache()
}
pub fn new_thread_local_avc(&self) -> impl QueryMut {
self.avc_manager.new_thread_local_cache()
}
fn with_state_and_update_status(&self, f: impl FnOnce(&mut SecurityServerState)) {
let mut locked_state = self.state.lock();
f(locked_state.deref_mut());
let new_value = SeLinuxStatus {
is_enforcing: locked_state.enforcing,
change_count: locked_state.policy_change_count,
deny_unknown: locked_state.deny_unknown(),
};
if let Some(status_publisher) = &mut locked_state.status_publisher {
status_publisher.set_status(new_value);
}
}
}
impl Query for SecurityServer {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.compute_access_vector(source_sid, target_sid, target_class)
}
fn compute_new_file_sid(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
let mut locked_state = self.state.lock();
let active_policy = match &mut locked_state.active_policy {
Some(active_policy) => active_policy,
None => {
return Err(anyhow::anyhow!("no policy loaded")).context("computing new file sid")
}
};
let source_context = active_policy.sid_table.sid_to_security_context(source_sid);
let target_context = active_policy.sid_table.sid_to_security_context(target_sid);
let new_file_context = active_policy.parsed.new_file_security_context(
source_context,
target_context,
&file_class,
);
active_policy
.sid_table
.security_context_to_sid(&new_file_context)
.map_err(anyhow::Error::from)
.context("computing new file security context from policy")
}
}
impl AccessVectorComputer for SecurityServer {
fn access_vector_from_permissions<P: ClassPermission + Into<Permission> + Clone + 'static>(
&self,
permissions: &[P],
) -> Option<AccessVector> {
match &self.state.lock().active_policy {
Some(policy) => policy.parsed.access_vector_from_permissions(permissions),
None => Some(AccessVector::NONE),
}
}
}
fn sid_from_mount_option(
active_policy: &mut ActivePolicy,
mount_option: &Option<Vec<u8>>,
) -> Option<SecurityId> {
if let Some(label) = mount_option.as_ref() {
Some(
if let Some(context) = active_policy.parsed.parse_security_context(label.into()).ok() {
active_policy.sid_table.security_context_to_sid(&context).unwrap()
} else {
SecurityId::initial(InitialSid::Unlabeled)
},
)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::permission_check::PermissionCheckResult;
use crate::{CommonFilePermission, ProcessPermission};
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");
const MINIMAL_BINARY_POLICY: &[u8] =
include_bytes!("../testdata/composite_policies/compiled/minimal_policy.pp");
fn security_server_with_tests_policy() -> Arc<SecurityServer> {
let policy_bytes = TESTS_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new();
assert_eq!(
Ok(()),
security_server.load_policy(policy_bytes).map_err(|e| format!("{:?}", e))
);
security_server
}
#[test]
fn compute_access_vector_allows_all() {
let security_server = SecurityServer::new();
let sid1 = SecurityId::initial(InitialSid::Kernel);
let sid2 = SecurityId::initial(InitialSid::Unlabeled);
assert_eq!(
security_server.compute_access_vector(sid1, sid2, ObjectClass::Process.into()).allow,
AccessVector::ALL
);
}
#[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());
}
#[test]
fn loaded_policy_is_validated() {
let not_really_a_policy = "not a real policy".as_bytes().to_vec();
let security_server = SecurityServer::new();
assert!(security_server.load_policy(not_really_a_policy.clone()).is_err());
}
#[test]
fn enforcing_mode_is_reported() {
let security_server = SecurityServer::new();
assert!(!security_server.is_enforcing());
security_server.set_enforcing(true);
assert!(security_server.is_enforcing());
}
#[test]
fn without_policy_conditional_booleans_are_empty() {
let security_server = SecurityServer::new();
assert!(security_server.conditional_booleans().is_empty());
}
#[test]
fn conditional_booleans_can_be_queried() {
let policy_bytes = TESTSUITE_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new();
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());
}
#[test]
fn conditional_booleans_can_be_changed() {
let policy_bytes = TESTSUITE_BINARY_POLICY.to_vec();
let security_server = SecurityServer::new();
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");
}
#[test]
fn parse_security_context_no_policy() {
let security_server = SecurityServer::new();
let error = security_server
.security_context_to_sid(b"unconfined_u:unconfined_r:unconfined_t:s0".into())
.expect_err("expected error");
let error_string = format!("{:?}", error);
assert!(error_string.contains("no policy"));
}
#[test]
fn compute_new_file_sid_no_policy() {
let security_server = SecurityServer::new();
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"));
}
#[test]
fn compute_new_file_sid_no_defaults() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s0");
}
#[test]
fn compute_new_file_sid_source_defaults() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:unconfined_r:unconfined_t:s0");
}
#[test]
fn compute_new_file_sid_target_defaults() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"file_u:object_r:file_t:s0");
}
#[test]
fn compute_new_file_sid_range_source_low_default() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s0");
}
#[test]
fn compute_new_file_sid_range_source_low_high_default() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s0-s1:c0");
}
#[test]
fn compute_new_file_sid_range_source_high_default() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s1:c0");
}
#[test]
fn compute_new_file_sid_range_target_low_default() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s0");
}
#[test]
fn compute_new_file_sid_range_target_low_high_default() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s0-s1:c0");
}
#[test]
fn compute_new_file_sid_range_target_high_default() {
let security_server = SecurityServer::new();
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".into())
.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".into())
.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");
assert_eq!(computed_context, b"user_u:object_r:file_t:s1:c0");
}
#[test]
fn permissions_are_fresh_after_different_policy_load() {
let minimal_bytes = MINIMAL_BINARY_POLICY.to_vec();
let allow_fork_bytes =
include_bytes!("../testdata/composite_policies/compiled/allow_fork.pp").to_vec();
let context = b"source_u:object_r:source_t:s0:c0";
let security_server = SecurityServer::new();
security_server.set_enforcing(true);
let permission_check = security_server.as_permission_check();
assert_eq!(
Ok(()),
security_server.load_policy(minimal_bytes).map_err(|e| format!("{:?}", e))
);
let sid = security_server.security_context_to_sid(context.into()).unwrap();
assert!(!permission_check.has_permission(sid, sid, ProcessPermission::Fork).permit);
assert_eq!(
Ok(()),
security_server.load_policy(allow_fork_bytes).map_err(|e| format!("{:?}", e))
);
assert!(permission_check.has_permission(sid, sid, ProcessPermission::Fork).permit);
}
#[test]
fn unknown_sids_are_effectively_unlabeled() {
let with_unlabeled_access_domain_policy_bytes = include_bytes!(
"../testdata/composite_policies/compiled/with_unlabeled_access_domain_policy.pp"
)
.to_vec();
let with_additional_domain_policy_bytes = include_bytes!(
"../testdata/composite_policies/compiled/with_additional_domain_policy.pp"
)
.to_vec();
let allowed_type_context = b"source_u:object_r:allowed_t:s0:c0";
let additional_type_context = b"source_u:object_r:additional_t:s0:c0";
let security_server = SecurityServer::new();
security_server.set_enforcing(true);
assert_eq!(
Ok(()),
security_server
.load_policy(with_unlabeled_access_domain_policy_bytes.clone())
.map_err(|e| format!("{:?}", e))
);
let allowed_type_sid =
security_server.security_context_to_sid(allowed_type_context.into()).unwrap();
assert!(security_server.security_context_to_sid(additional_type_context.into()).is_err());
assert_eq!(
Ok(()),
security_server
.load_policy(with_additional_domain_policy_bytes.clone())
.map_err(|e| format!("{:?}", e))
);
let additional_type_sid =
security_server.security_context_to_sid(additional_type_context.into()).unwrap();
assert_eq!(
allowed_type_sid,
security_server.security_context_to_sid(allowed_type_context.into()).unwrap()
);
let permission_check = security_server.as_permission_check();
assert!(
!permission_check
.has_permission(additional_type_sid, allowed_type_sid, ProcessPermission::GetSched)
.permit
);
assert!(
!permission_check
.has_permission(additional_type_sid, allowed_type_sid, ProcessPermission::SetSched)
.permit
);
assert!(
!permission_check
.has_permission(allowed_type_sid, additional_type_sid, ProcessPermission::GetSched)
.permit
);
assert!(
!permission_check
.has_permission(allowed_type_sid, additional_type_sid, ProcessPermission::SetSched)
.permit
);
assert_eq!(
Ok(()),
security_server
.load_policy(with_unlabeled_access_domain_policy_bytes)
.map_err(|e| format!("{:?}", e))
);
assert!(
permission_check
.has_permission(allowed_type_sid, additional_type_sid, ProcessPermission::GetSched)
.permit
);
assert!(
!permission_check
.has_permission(allowed_type_sid, additional_type_sid, ProcessPermission::SetSched)
.permit
);
assert!(
!permission_check
.has_permission(additional_type_sid, allowed_type_sid, ProcessPermission::GetSched)
.permit
);
assert!(
permission_check
.has_permission(additional_type_sid, allowed_type_sid, ProcessPermission::SetSched)
.permit
);
assert!(security_server.sid_to_security_context(additional_type_sid).is_none());
assert_eq!(
Ok(()),
security_server
.load_policy(with_additional_domain_policy_bytes)
.map_err(|e| format!("{:?}", e))
);
assert_eq!(
additional_type_context.to_vec(),
security_server.sid_to_security_context(additional_type_sid).unwrap()
);
}
#[test]
fn permission_check_permissive() {
let security_server = security_server_with_tests_policy();
security_server.set_enforcing(false);
assert!(!security_server.is_enforcing());
let sid =
security_server.security_context_to_sid("user0:object_r:type0:s0".into()).unwrap();
let permission_check = security_server.as_permission_check();
assert_eq!(
permission_check.has_permission(sid, sid, ProcessPermission::Fork),
PermissionCheckResult { permit: true, audit: false }
);
assert_eq!(
permission_check.has_permission(sid, sid, ProcessPermission::GetRlimit),
PermissionCheckResult { permit: true, audit: true }
);
assert_eq!(
permission_check.has_permission(
sid,
sid,
CommonFilePermission::GetAttr.for_class(FileClass::Block)
),
PermissionCheckResult { permit: true, audit: true }
);
}
#[test]
fn permission_check_enforcing() {
let security_server = security_server_with_tests_policy();
security_server.set_enforcing(true);
assert!(security_server.is_enforcing());
let sid =
security_server.security_context_to_sid("user0:object_r:type0:s0".into()).unwrap();
let permission_check = security_server.as_permission_check();
assert_eq!(
permission_check.has_permission(sid, sid, ProcessPermission::Fork),
PermissionCheckResult { permit: true, audit: false }
);
assert_eq!(
permission_check.has_permission(sid, sid, ProcessPermission::GetRlimit),
PermissionCheckResult { permit: false, audit: true }
);
assert_eq!(
permission_check.has_permission(
sid,
sid,
CommonFilePermission::GetAttr.for_class(FileClass::Block)
),
PermissionCheckResult { permit: false, audit: true }
);
}
#[test]
fn permissive_domain() {
let security_server = security_server_with_tests_policy();
security_server.set_enforcing(true);
assert!(security_server.is_enforcing());
let permissive_sid = security_server
.security_context_to_sid("user0:object_r:permissive_t:s0".into())
.unwrap();
let non_permissive_sid = security_server
.security_context_to_sid("user0:object_r:non_permissive_t:s0".into())
.unwrap();
let permission_check = security_server.as_permission_check();
assert_eq!(
permission_check.has_permission(
permissive_sid,
permissive_sid,
ProcessPermission::GetSched
),
PermissionCheckResult { permit: true, audit: false }
);
assert_eq!(
permission_check.has_permission(
non_permissive_sid,
non_permissive_sid,
ProcessPermission::GetSched
),
PermissionCheckResult { permit: true, audit: false }
);
assert_eq!(
permission_check.has_permission(
permissive_sid,
non_permissive_sid,
ProcessPermission::GetSched
),
PermissionCheckResult { permit: true, audit: true }
);
assert_eq!(
permission_check.has_permission(
non_permissive_sid,
permissive_sid,
ProcessPermission::GetSched
),
PermissionCheckResult { permit: false, audit: true }
);
assert_eq!(
permission_check.has_permission(
permissive_sid,
non_permissive_sid,
CommonFilePermission::GetAttr.for_class(FileClass::Block)
),
PermissionCheckResult { permit: true, audit: true }
);
assert_eq!(
permission_check.has_permission(
non_permissive_sid,
non_permissive_sid,
CommonFilePermission::GetAttr.for_class(FileClass::Block)
),
PermissionCheckResult { permit: false, audit: true }
);
}
#[test]
fn auditallow_and_dontaudit() {
let security_server = security_server_with_tests_policy();
security_server.set_enforcing(true);
assert!(security_server.is_enforcing());
let audit_sid = security_server
.security_context_to_sid("user0:object_r:test_audit_t:s0".into())
.unwrap();
let permission_check = security_server.as_permission_check();
assert_eq!(
permission_check.has_permission(audit_sid, audit_sid, ProcessPermission::Fork),
PermissionCheckResult { permit: true, audit: true }
);
assert_eq!(
permission_check.has_permission(audit_sid, audit_sid, ProcessPermission::SetSched),
PermissionCheckResult { permit: true, audit: false }
);
assert_eq!(
permission_check.has_permission(audit_sid, audit_sid, ProcessPermission::GetSched),
PermissionCheckResult { permit: false, audit: false }
);
assert_eq!(
permission_check.has_permission(audit_sid, audit_sid, ProcessPermission::GetPgid),
PermissionCheckResult { permit: false, audit: true }
);
}
}