use crate::policy::AccessDecision;
use crate::sync::Mutex;
use crate::{AbstractObjectClass, FileClass, ObjectClass, SecurityId};
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{Arc, Weak};
pub trait Query {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision;
fn compute_new_file_sid(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error>;
}
pub trait HasCacheStats {
fn cache_stats(&self) -> CacheStats;
}
pub trait QueryMut {
fn query(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision;
fn compute_new_file_sid(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error>;
}
impl<Q: Query> QueryMut for Q {
fn query(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
(self as &dyn Query).query(source_sid, target_sid, target_class)
}
fn compute_new_file_sid(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
(self as &dyn Query).compute_new_file_sid(source_sid, target_sid, file_class)
}
}
pub(super) trait Reset {
fn reset(&self) -> bool;
}
pub(super) trait ResetMut {
fn reset(&mut self) -> bool;
}
impl<R: Reset> ResetMut for R {
fn reset(&mut self) -> bool {
(self as &dyn Reset).reset()
}
}
pub(super) trait ProxyMut<D> {
fn set_delegate(&mut self, delegate: D) -> D;
}
#[derive(Default)]
pub(super) struct DenyAll;
impl Query for DenyAll {
fn query(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_target_class: AbstractObjectClass,
) -> AccessDecision {
AccessDecision::default()
}
fn compute_new_file_sid(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
unreachable!()
}
}
impl Reset for DenyAll {
fn reset(&self) -> bool {
true
}
}
#[derive(Clone)]
struct QueryAndResult {
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
access_decision: AccessDecision,
new_file_sid: Option<SecurityId>,
}
#[derive(Default)]
struct Empty<D = DenyAll> {
delegate: D,
}
impl<D> Empty<D> {
#[allow(dead_code)]
pub fn new(delegate: D) -> Self {
Self { delegate }
}
}
impl<D: QueryMut> QueryMut for Empty<D> {
fn query(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.delegate.query(source_sid, target_sid, target_class)
}
fn compute_new_file_sid(
&mut self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
unreachable!()
}
}
impl<D: ResetMut> ResetMut for Empty<D> {
fn reset(&mut self) -> bool {
self.delegate.reset()
}
}
pub(super) const DEFAULT_FIXED_SIZE: usize = 10;
pub(super) struct Fixed<D = DenyAll, const SIZE: usize = DEFAULT_FIXED_SIZE> {
cache: [Option<QueryAndResult>; SIZE],
next_index: usize,
is_full: bool,
delegate: D,
stats: CacheStats,
}
impl<D, const SIZE: usize> Fixed<D, SIZE> {
pub fn new(delegate: D) -> Self {
if SIZE == 0 {
panic!("cannot instantiate fixed access vector cache of size 0");
}
Self {
cache: std::array::from_fn(|_| None),
next_index: 0,
is_full: false,
delegate,
stats: CacheStats::default(),
}
}
#[inline]
fn is_empty(&self) -> bool {
self.next_index == 0 && !self.is_full
}
fn search(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> Option<usize> {
self.stats.lookups += 1;
if !self.is_empty() {
let mut index = if self.next_index == 0 { SIZE - 1 } else { self.next_index - 1 };
loop {
let query_and_result = self.cache[index].as_ref().unwrap();
if &source_sid == &query_and_result.source_sid
&& &target_sid == &query_and_result.target_sid
&& &target_class == &query_and_result.target_class
{
self.stats.hits += 1;
return Some(index);
}
if index == self.next_index || (index == 0 && !self.is_full) {
break;
}
index = if index == 0 { SIZE - 1 } else { index - 1 };
}
}
self.stats.misses += 1;
None
}
#[inline]
fn insert(&mut self, query_and_result: QueryAndResult) -> usize {
let index = self.next_index;
let entry = &mut self.cache[index];
self.stats.allocs += 1;
if entry.is_some() {
self.stats.reclaims += 1;
}
*entry = Some(query_and_result);
self.next_index = (self.next_index + 1) % SIZE;
if self.next_index == 0 {
self.is_full = true;
}
index
}
}
impl<D: QueryMut, const SIZE: usize> QueryMut for Fixed<D, SIZE> {
fn query(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
if let Some(hit_index) = self.search(source_sid, target_sid, target_class.clone()) {
return self.cache[hit_index].as_ref().unwrap().access_decision.clone();
}
let access_decision = self.delegate.query(source_sid, target_sid, target_class.clone());
self.insert(QueryAndResult {
source_sid,
target_sid,
target_class,
access_decision: access_decision.clone(),
new_file_sid: None,
});
access_decision
}
fn compute_new_file_sid(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
let target_class = AbstractObjectClass::System(ObjectClass::from(file_class));
let index = if let Some(index) = self.search(source_sid, target_sid, target_class.clone()) {
index
} else {
let access_decision = self.delegate.query(source_sid, target_sid, target_class.clone());
self.insert(QueryAndResult {
source_sid,
target_sid,
target_class,
access_decision,
new_file_sid: None,
})
};
let query_and_result = &mut self.cache[index].as_mut().unwrap();
if let Some(new_file_sid) = query_and_result.new_file_sid {
Ok(new_file_sid)
} else {
let new_file_sid =
self.delegate.compute_new_file_sid(source_sid, target_sid, file_class);
if let Ok(new_file_sid) = new_file_sid {
query_and_result.new_file_sid = Some(new_file_sid);
}
new_file_sid
}
}
}
impl<D, const SIZE: usize> HasCacheStats for Fixed<D, SIZE> {
fn cache_stats(&self) -> CacheStats {
self.stats.clone()
}
}
impl<D, const SIZE: usize> ResetMut for Fixed<D, SIZE> {
fn reset(&mut self) -> bool {
self.next_index = 0;
self.is_full = false;
self.stats = CacheStats::default();
true
}
}
impl<D, const SIZE: usize> ProxyMut<D> for Fixed<D, SIZE> {
fn set_delegate(&mut self, mut delegate: D) -> D {
std::mem::swap(&mut self.delegate, &mut delegate);
delegate
}
}
pub(super) struct Locked<D = DenyAll> {
delegate: Arc<Mutex<D>>,
}
impl<D> Clone for Locked<D> {
fn clone(&self) -> Self {
Self { delegate: self.delegate.clone() }
}
}
impl<D> Locked<D> {
pub fn new(delegate: D) -> Self {
Self { delegate: Arc::new(Mutex::new(delegate)) }
}
}
impl<D: QueryMut> Query for Locked<D> {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.delegate.lock().query(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> {
self.delegate.lock().compute_new_file_sid(source_sid, target_sid, file_class)
}
}
impl<D: HasCacheStats> HasCacheStats for Locked<D> {
fn cache_stats(&self) -> CacheStats {
self.delegate.lock().cache_stats()
}
}
impl<D: ResetMut> Reset for Locked<D> {
fn reset(&self) -> bool {
self.delegate.lock().reset()
}
}
impl<D> Locked<D> {
pub fn set_stateful_cache_delegate<PD>(&self, delegate: PD) -> PD
where
D: ProxyMut<PD>,
{
self.delegate.lock().set_delegate(delegate)
}
}
#[derive(Default)]
pub struct AtomicVersion(AtomicU64);
impl AtomicVersion {
pub fn version(&self) -> u64 {
self.0.load(Ordering::Relaxed)
}
pub fn increment_version(&self) {
self.0.fetch_add(1, Ordering::Relaxed);
}
}
impl Reset for AtomicVersion {
fn reset(&self) -> bool {
self.increment_version();
true
}
}
impl<Q: Query> Query for Arc<Q> {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.as_ref().query(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> {
self.as_ref().compute_new_file_sid(source_sid, target_sid, file_class)
}
}
impl<R: Reset> Reset for Arc<R> {
fn reset(&self) -> bool {
self.as_ref().reset()
}
}
impl<Q: Query> Query for Weak<Q> {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.upgrade().map(|q| q.query(source_sid, target_sid, target_class)).unwrap_or_default()
}
fn compute_new_file_sid(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
self.upgrade()
.map(|q| q.compute_new_file_sid(source_sid, target_sid, file_class))
.unwrap_or(Err(anyhow::anyhow!("weak reference failed to resolve")))
}
}
impl<R: Reset> Reset for Weak<R> {
fn reset(&self) -> bool {
self.upgrade().as_deref().map(Reset::reset).unwrap_or(false)
}
}
pub(super) struct ThreadLocalQuery<D = DenyAll> {
delegate: D,
current_version: u64,
active_version: Arc<AtomicVersion>,
}
impl<D> ThreadLocalQuery<D> {
pub fn new(active_version: Arc<AtomicVersion>, delegate: D) -> Self {
Self { delegate, current_version: Default::default(), active_version }
}
}
impl<D: QueryMut + ResetMut> QueryMut for ThreadLocalQuery<D> {
fn query(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
let version = self.active_version.as_ref().version();
if self.current_version != version {
self.current_version = version;
self.delegate.reset();
}
self.delegate.query(source_sid, target_sid, target_class)
}
fn compute_new_file_sid(
&mut self,
source_sid: SecurityId,
target_sid: SecurityId,
file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
let version = self.active_version.as_ref().version();
if self.current_version != version {
self.current_version = version;
self.delegate.reset();
}
self.delegate.compute_new_file_sid(source_sid, target_sid, file_class)
}
}
pub(super) const DEFAULT_SHARED_SIZE: usize = 1000;
pub(super) struct Manager<
SS,
const SHARED_SIZE: usize = DEFAULT_SHARED_SIZE,
const THREAD_LOCAL_SIZE: usize = DEFAULT_FIXED_SIZE,
> {
shared_cache: Locked<Fixed<Weak<SS>, SHARED_SIZE>>,
thread_local_version: Arc<AtomicVersion>,
}
impl<SS, const SHARED_SIZE: usize, const THREAD_LOCAL_SIZE: usize>
Manager<SS, SHARED_SIZE, THREAD_LOCAL_SIZE>
{
pub fn new() -> Self {
Self {
shared_cache: Locked::new(Fixed::new(Weak::<SS>::new())),
thread_local_version: Arc::new(AtomicVersion::default()),
}
}
pub fn set_security_server(&self, security_server: Weak<SS>) -> Weak<SS> {
self.shared_cache.set_stateful_cache_delegate(security_server)
}
pub fn get_shared_cache(&self) -> &Locked<Fixed<Weak<SS>, SHARED_SIZE>> {
&self.shared_cache
}
pub fn new_thread_local_cache(
&self,
) -> ThreadLocalQuery<Fixed<Locked<Fixed<Weak<SS>, SHARED_SIZE>>, THREAD_LOCAL_SIZE>> {
ThreadLocalQuery::new(
self.thread_local_version.clone(),
Fixed::new(self.shared_cache.clone()),
)
}
}
impl<SS, const SHARED_SIZE: usize, const THREAD_LOCAL_SIZE: usize> Reset
for Manager<SS, SHARED_SIZE, THREAD_LOCAL_SIZE>
{
fn reset(&self) -> bool {
self.shared_cache.reset();
self.thread_local_version.reset();
true
}
}
#[derive(Default, Debug, Clone)]
pub struct CacheStats {
pub lookups: u64,
pub hits: u64,
pub misses: u64,
pub allocs: u64,
pub reclaims: u64,
pub frees: u64,
}
#[cfg(test)]
mod testing {
use crate::SecurityId;
use std::num::NonZeroU32;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::LazyLock;
pub(super) static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
pub(super) const CACHE_ENTRIES: usize = 10;
pub(super) fn unique_sid() -> SecurityId {
static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
}
pub(super) fn unique_sids(count: usize) -> Vec<SecurityId> {
(0..count).map(|_| unique_sid()).collect()
}
}
#[cfg(test)]
mod tests {
use super::testing::*;
use super::*;
use crate::policy::AccessVector;
use crate::ObjectClass;
use std::sync::atomic::AtomicUsize;
#[derive(Default)]
struct Counter<D = DenyAll> {
query_count: AtomicUsize,
reset_count: AtomicUsize,
delegate: D,
}
impl<D> Counter<D> {
fn query_count(&self) -> usize {
self.query_count.load(Ordering::Relaxed)
}
fn reset_count(&self) -> usize {
self.reset_count.load(Ordering::Relaxed)
}
}
impl<D: Query> Query for Counter<D> {
fn query(
&self,
source_sid: SecurityId,
target_sid: SecurityId,
target_class: AbstractObjectClass,
) -> AccessDecision {
self.query_count.fetch_add(1, Ordering::Relaxed);
self.delegate.query(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> {
unreachable!()
}
}
impl<D: Reset> Reset for Counter<D> {
fn reset(&self) -> bool {
self.reset_count.fetch_add(1, Ordering::Relaxed);
self.delegate.reset();
true
}
}
#[test]
fn empty_access_vector_cache_default_deny_all() {
let mut avc = Empty::<DenyAll>::default();
assert_eq!(
AccessVector::NONE,
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into()).allow
);
}
#[test]
fn fixed_access_vector_cache_add_entry() {
let mut avc = Fixed::<_, CACHE_ENTRIES>::new(Counter::<DenyAll>::default());
assert_eq!(0, avc.delegate.query_count());
assert_eq!(
AccessVector::NONE,
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into()).allow
);
assert_eq!(1, avc.delegate.query_count());
assert_eq!(
AccessVector::NONE,
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into()).allow
);
assert_eq!(1, avc.delegate.query_count());
assert_eq!(1, avc.next_index);
assert_eq!(false, avc.is_full);
}
#[test]
fn fixed_access_vector_cache_reset() {
let mut avc = Fixed::<_, CACHE_ENTRIES>::new(Counter::<DenyAll>::default());
avc.reset();
assert_eq!(0, avc.next_index);
assert_eq!(false, avc.is_full);
assert_eq!(0, avc.delegate.query_count());
assert_eq!(
AccessVector::NONE,
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into()).allow
);
assert_eq!(1, avc.delegate.query_count());
assert_eq!(1, avc.next_index);
assert_eq!(false, avc.is_full);
avc.reset();
assert_eq!(0, avc.next_index);
assert_eq!(false, avc.is_full);
}
#[test]
fn fixed_access_vector_cache_fill() {
let mut avc = Fixed::<_, CACHE_ENTRIES>::new(Counter::<DenyAll>::default());
for sid in unique_sids(CACHE_ENTRIES) {
avc.query(sid, A_TEST_SID.clone(), ObjectClass::Process.into());
}
assert_eq!(0, avc.next_index);
assert_eq!(true, avc.is_full);
avc.reset();
assert_eq!(0, avc.next_index);
assert_eq!(false, avc.is_full);
for sid in unique_sids(CACHE_ENTRIES) {
avc.query(A_TEST_SID.clone(), sid, ObjectClass::Process.into());
}
assert_eq!(0, avc.next_index);
assert_eq!(true, avc.is_full);
avc.reset();
assert_eq!(0, avc.next_index);
assert_eq!(false, avc.is_full);
}
#[test]
fn fixed_access_vector_cache_full_miss() {
let mut avc = Fixed::<_, CACHE_ENTRIES>::new(Counter::<DenyAll>::default());
let delegate_query_count = avc.delegate.query_count();
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into());
assert_eq!(delegate_query_count + 1, avc.delegate.query_count());
assert!(!avc.is_full);
for sid in unique_sids(CACHE_ENTRIES) {
avc.query(sid, A_TEST_SID.clone(), ObjectClass::Process.into());
}
assert!(avc.is_full);
let delegate_query_count = avc.delegate.query_count();
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into());
assert_eq!(delegate_query_count + 1, avc.delegate.query_count());
for sid in unique_sids(CACHE_ENTRIES) {
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into());
avc.query(sid, A_TEST_SID.clone(), ObjectClass::Process.into());
}
let delegate_query_count = avc.delegate.query_count();
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into());
assert_eq!(delegate_query_count + 1, avc.delegate.query_count());
}
#[test]
fn thread_local_query_access_vector_cache_reset() {
let cache_version = Arc::new(AtomicVersion::default());
let mut avc = ThreadLocalQuery::new(cache_version.clone(), Counter::<DenyAll>::default());
assert_eq!(0, avc.delegate.reset_count());
cache_version.reset();
assert_eq!(0, avc.delegate.reset_count());
avc.query(A_TEST_SID.clone(), A_TEST_SID.clone(), ObjectClass::Process.into());
assert_eq!(1, avc.delegate.reset_count());
}
}
#[cfg(test)]
#[cfg(feature = "selinux_starnix")]
mod starnix_tests {
use super::testing::*;
use super::*;
use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
use crate::policy::AccessVector;
use crate::ObjectClass;
use rand::distributions::Uniform;
use rand::{thread_rng, Rng as _};
use std::collections::{HashMap, HashSet};
use std::sync::atomic::AtomicU32;
use std::thread::spawn;
const NO_RIGHTS: u32 = 0;
const READ_RIGHTS: u32 = 1;
const WRITE_RIGHTS: u32 = 2;
const ACCESS_VECTOR_READ: AccessDecision = AccessDecision::allow(ACCESS_VECTOR_0001);
const ACCESS_VECTOR_WRITE: AccessDecision = AccessDecision::allow(ACCESS_VECTOR_0010);
struct PolicyServer {
policy: Arc<AtomicU32>,
}
impl PolicyServer {
fn set_policy(&self, policy: u32) {
if policy > 2 {
panic!("attempt to set policy to invalid value: {}", policy);
}
self.policy.as_ref().store(policy, Ordering::Relaxed);
}
}
impl Query for PolicyServer {
fn query(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_target_class: AbstractObjectClass,
) -> AccessDecision {
let policy = self.policy.as_ref().load(Ordering::Relaxed);
if policy == NO_RIGHTS {
AccessDecision::default()
} else if policy == READ_RIGHTS {
ACCESS_VECTOR_READ
} else if policy == WRITE_RIGHTS {
ACCESS_VECTOR_WRITE
} else {
panic!("query found invalid policy: {}", policy);
}
}
fn compute_new_file_sid(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
unreachable!()
}
}
impl Reset for PolicyServer {
fn reset(&self) -> bool {
true
}
}
#[fuchsia::test]
async fn thread_local_query_access_vector_cache_coherence() {
for _ in 0..CACHE_ENTRIES {
test_thread_local_query_access_vector_cache_coherence().await
}
}
async fn test_thread_local_query_access_vector_cache_coherence() {
let active_policy: Arc<AtomicU32> = Arc::new(Default::default());
let policy_server: Arc<PolicyServer> =
Arc::new(PolicyServer { policy: active_policy.clone() });
let cache_version = Arc::new(AtomicVersion::default());
let fixed_avc = Fixed::<_, CACHE_ENTRIES>::new(policy_server.clone());
let cache_version_for_avc = cache_version.clone();
let mut query_avc = ThreadLocalQuery::new(cache_version_for_avc, fixed_avc);
policy_server.set_policy(NO_RIGHTS);
let (tx, rx) = futures::channel::oneshot::channel();
let query_thread = spawn(move || {
let mut trace = vec![];
for _ in 0..2000 {
trace.push(query_avc.query(
A_TEST_SID.clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
))
}
tx.send(trace).expect("send trace");
});
let policy_server = PolicyServer { policy: active_policy.clone() };
let cache_version_for_read = cache_version.clone();
let set_read_thread = spawn(move || {
std::thread::sleep(std::time::Duration::from_micros(1));
policy_server.set_policy(READ_RIGHTS);
cache_version_for_read.reset();
});
let policy_server = PolicyServer { policy: active_policy.clone() };
let cache_version_for_write = cache_version;
let set_write_thread = spawn(move || {
std::thread::sleep(std::time::Duration::from_micros(2));
policy_server.set_policy(WRITE_RIGHTS);
cache_version_for_write.reset();
});
set_read_thread.join().expect("join set-policy-to-read");
set_write_thread.join().expect("join set-policy-to-write");
query_thread.join().expect("join query");
let trace = rx.await.expect("receive trace");
let mut observed_rights: HashSet<AccessVector> = Default::default();
let mut prev_rights = AccessVector::NONE;
for (i, rights) in trace.into_iter().enumerate() {
if i != 0 && rights.allow != prev_rights {
assert!(!observed_rights.contains(&rights.allow));
observed_rights.insert(rights.allow);
}
prev_rights = rights.allow;
}
}
#[fuchsia::test]
async fn locked_fixed_access_vector_cache_coherence() {
for _ in 0..10 {
test_locked_fixed_access_vector_cache_coherence().await
}
}
async fn test_locked_fixed_access_vector_cache_coherence() {
let active_policy: Arc<AtomicU32> = Arc::new(Default::default());
let policy_server = Arc::new(PolicyServer { policy: active_policy.clone() });
let fixed_avc = Fixed::<_, CACHE_ENTRIES>::new(policy_server.clone());
let avc = Locked::new(fixed_avc);
let sids = unique_sids(30);
policy_server.set_policy(NO_RIGHTS);
let (tx_last_policy_change_1, rx_last_policy_change_1) =
futures::channel::oneshot::channel();
let (tx_last_policy_change_2, rx_last_policy_change_2) =
futures::channel::oneshot::channel();
let (tx1, rx1) = futures::channel::oneshot::channel();
let avc_for_query_1 = avc.clone();
let sids_for_query_1 = sids.clone();
let query_thread_1 = spawn(|| async move {
let sids = sids_for_query_1;
let mut trace = vec![];
for i in thread_rng().sample_iter(&Uniform::new(0, 20)).take(2000) {
trace.push((
sids[i].clone(),
avc_for_query_1.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
rx_last_policy_change_1.await.expect("receive last-policy-change signal (1)");
for i in thread_rng().sample_iter(&Uniform::new(0, 20)).take(10) {
trace.push((
sids[i].clone(),
avc_for_query_1.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
tx1.send(trace).expect("send trace 1");
for item in avc_for_query_1.delegate.lock().cache.iter() {
assert_eq!(ACCESS_VECTOR_WRITE, item.as_ref().unwrap().access_decision);
}
});
let (tx2, rx2) = futures::channel::oneshot::channel();
let avc_for_query_2 = avc.clone();
let sids_for_query_2 = sids.clone();
let query_thread_2 = spawn(|| async move {
let sids = sids_for_query_2;
let mut trace = vec![];
for i in thread_rng().sample_iter(&Uniform::new(10, 30)).take(2000) {
trace.push((
sids[i].clone(),
avc_for_query_2.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
rx_last_policy_change_2.await.expect("receive last-policy-change signal (2)");
for i in thread_rng().sample_iter(&Uniform::new(10, 30)).take(10) {
trace.push((
sids[i].clone(),
avc_for_query_2.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
tx2.send(trace).expect("send trace 2");
for item in avc_for_query_2.delegate.lock().cache.iter() {
assert_eq!(ACCESS_VECTOR_WRITE, item.as_ref().unwrap().access_decision);
}
});
let policy_server_for_set_read = policy_server.clone();
let avc_for_set_read = avc.clone();
let (tx_set_read, rx_set_read) = futures::channel::oneshot::channel();
let set_read_thread = spawn(move || {
std::thread::sleep(std::time::Duration::from_micros(1));
policy_server_for_set_read.set_policy(READ_RIGHTS);
avc_for_set_read.reset();
tx_set_read.send(true).expect("send set-read signal")
});
let policy_server_for_set_write = policy_server.clone();
let avc_for_set_write = avc;
let set_write_thread = spawn(|| async move {
rx_set_read.await.expect("receive set-write signal");
std::thread::sleep(std::time::Duration::from_micros(1));
policy_server_for_set_write.set_policy(WRITE_RIGHTS);
avc_for_set_write.reset();
tx_last_policy_change_1.send(true).expect("send last-policy-change signal (1)");
tx_last_policy_change_2.send(true).expect("send last-policy-change signal (2)");
});
set_read_thread.join().expect("join set-policy-to-read");
let _ = set_write_thread.join().expect("join set-policy-to-write").await;
let _ = query_thread_1.join().expect("join query").await;
let _ = query_thread_2.join().expect("join query").await;
let trace_1 = rx1.await.expect("receive trace 1");
let trace_2 = rx2.await.expect("receive trace 2");
for trace in [trace_1, trace_2] {
let mut trace_by_sid = HashMap::<SecurityId, Vec<AccessVector>>::new();
for (sid, access_decision) in trace {
trace_by_sid.entry(sid).or_insert(vec![]).push(access_decision.allow);
}
for access_vectors in trace_by_sid.values() {
let initial_rights = AccessVector::NONE;
let mut prev_rights = &initial_rights;
for rights in access_vectors.iter() {
assert!(rights >= prev_rights);
prev_rights = rights;
}
}
}
}
struct SecurityServer {
manager: Manager<SecurityServer>,
policy: Arc<AtomicU32>,
}
impl SecurityServer {
fn manager(&self) -> &Manager<SecurityServer> {
&self.manager
}
}
impl Query for SecurityServer {
fn query(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_target_class: AbstractObjectClass,
) -> AccessDecision {
let policy = self.policy.as_ref().load(Ordering::Relaxed);
if policy == NO_RIGHTS {
AccessDecision::default()
} else if policy == READ_RIGHTS {
ACCESS_VECTOR_READ
} else if policy == WRITE_RIGHTS {
ACCESS_VECTOR_WRITE
} else {
panic!("query found invalid policy: {}", policy);
}
}
fn compute_new_file_sid(
&self,
_source_sid: SecurityId,
_target_sid: SecurityId,
_file_class: FileClass,
) -> Result<SecurityId, anyhow::Error> {
unreachable!()
}
}
impl Reset for SecurityServer {
fn reset(&self) -> bool {
true
}
}
#[fuchsia::test]
async fn manager_cache_coherence() {
for _ in 0..10 {
test_manager_cache_coherence().await
}
}
async fn test_manager_cache_coherence() {
let (active_policy, security_server) = {
let manager = Manager::new();
let active_policy: Arc<AtomicU32> = Arc::new(Default::default());
let security_server =
Arc::new(SecurityServer { manager, policy: active_policy.clone() });
security_server
.as_ref()
.manager()
.set_security_server(Arc::downgrade(&security_server));
(active_policy, security_server)
};
let sids = unique_sids(30);
fn set_policy(owner: &Arc<AtomicU32>, policy: u32) {
if policy > 2 {
panic!("attempt to set policy to invalid value: {}", policy);
}
owner.as_ref().store(policy, Ordering::Relaxed);
}
set_policy(&active_policy, NO_RIGHTS);
let (tx_last_policy_change_1, rx_last_policy_change_1) =
futures::channel::oneshot::channel();
let (tx_last_policy_change_2, rx_last_policy_change_2) =
futures::channel::oneshot::channel();
let (tx1, rx1) = futures::channel::oneshot::channel();
let mut avc_for_query_1 = security_server.manager().new_thread_local_cache();
let sids_for_query_1 = sids.clone();
let query_thread_1 = spawn(|| async move {
let sids = sids_for_query_1;
let mut trace = vec![];
for i in thread_rng().sample_iter(&Uniform::new(0, 20)).take(2000) {
trace.push((
sids[i].clone(),
avc_for_query_1.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
rx_last_policy_change_1.await.expect("receive last-policy-change signal (1)");
for i in thread_rng().sample_iter(&Uniform::new(0, 20)).take(10) {
trace.push((
sids[i].clone(),
avc_for_query_1.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
tx1.send(trace).expect("send trace 1");
for item in avc_for_query_1.delegate.cache.iter() {
assert_eq!(ACCESS_VECTOR_WRITE, item.as_ref().unwrap().access_decision);
}
});
let (tx2, rx2) = futures::channel::oneshot::channel();
let mut avc_for_query_2 = security_server.manager().new_thread_local_cache();
let sids_for_query_2 = sids.clone();
let query_thread_2 = spawn(|| async move {
let sids = sids_for_query_2;
let mut trace = vec![];
for i in thread_rng().sample_iter(&Uniform::new(10, 30)).take(2000) {
trace.push((
sids[i].clone(),
avc_for_query_2.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
rx_last_policy_change_2.await.expect("receive last-policy-change signal (2)");
for i in thread_rng().sample_iter(&Uniform::new(10, 30)).take(10) {
trace.push((
sids[i].clone(),
avc_for_query_2.query(
sids[i].clone(),
A_TEST_SID.clone(),
ObjectClass::Process.into(),
),
))
}
tx2.send(trace).expect("send trace 2");
for item in avc_for_query_2.delegate.cache.iter() {
assert_eq!(ACCESS_VECTOR_WRITE, item.as_ref().unwrap().access_decision);
}
});
let active_policy_for_set_read = active_policy.clone();
let security_server_for_set_read = security_server.clone();
let (tx_set_read, rx_set_read) = futures::channel::oneshot::channel();
let set_read_thread = spawn(move || {
std::thread::sleep(std::time::Duration::from_micros(1));
set_policy(&active_policy_for_set_read, READ_RIGHTS);
security_server_for_set_read.manager().reset();
tx_set_read.send(true).expect("send set-read signal")
});
let active_policy_for_set_write = active_policy.clone();
let security_server_for_set_write = security_server.clone();
let set_write_thread = spawn(|| async move {
rx_set_read.await.expect("receive set-read signal");
std::thread::sleep(std::time::Duration::from_micros(1));
set_policy(&active_policy_for_set_write, WRITE_RIGHTS);
security_server_for_set_write.manager().reset();
tx_last_policy_change_1.send(true).expect("send last-policy-change signal (1)");
tx_last_policy_change_2.send(true).expect("send last-policy-change signal (2)");
});
set_read_thread.join().expect("join set-policy-to-read");
let _ = set_write_thread.join().expect("join set-policy-to-write").await;
let _ = query_thread_1.join().expect("join query").await;
let _ = query_thread_2.join().expect("join query").await;
let trace_1 = rx1.await.expect("receive trace 1");
let trace_2 = rx2.await.expect("receive trace 2");
for trace in [trace_1, trace_2] {
let mut trace_by_sid = HashMap::<SecurityId, Vec<AccessVector>>::new();
for (sid, access_decision) in trace {
trace_by_sid.entry(sid).or_insert(vec![]).push(access_decision.allow);
}
for access_vectors in trace_by_sid.values() {
let initial_rights = AccessVector::NONE;
let mut prev_rights = &initial_rights;
for rights in access_vectors.iter() {
assert!(rights >= prev_rights);
prev_rights = rights;
}
}
}
let shared_cache = security_server.manager().shared_cache.delegate.lock();
if shared_cache.is_full {
for item in shared_cache.cache.iter() {
assert_eq!(ACCESS_VECTOR_WRITE, item.as_ref().unwrap().access_decision);
}
} else {
for i in 0..shared_cache.next_index {
assert_eq!(
ACCESS_VECTOR_WRITE,
shared_cache.cache[i].as_ref().unwrap().access_decision
);
}
}
}
}