Skip to main content

selinux/
access_vector_cache.rs

1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::concurrent_access_cache::{
6    ConcurrentAccessCache, ConcurrentSidCache, ConcurrentXpermsCache,
7};
8use crate::kernel_permissions::KernelPermission;
9use crate::policy::{KernelAccessDecision, XpermsBitmap, XpermsKind};
10use crate::security_server::SecurityServerBackend;
11use crate::{KernelClass, SecurityId};
12use std::hash::Hash;
13use std::sync::Arc;
14
15pub use crate::cache_stats::CacheStats;
16
17/// An xperm access decision as seen from the kernel.
18#[derive(Clone, Copy, PartialEq, Debug)]
19pub struct KernelXpermsAccessDecision {
20    /// The set of xperms that are allowed.
21    pub allow: XpermsBitmap,
22    /// The set of xperms that should be audited (as allowed or denials depending on `allow`)
23    pub audit: XpermsBitmap,
24    /// Whether the domain is permissive.
25    pub permissive: bool,
26    /// Whether the entry has an associated todo.
27    pub has_todo: bool,
28}
29
30/// Interface used internally by the `SecurityServer` implementation to implement policy queries
31/// such as looking up the set of permissions to grant, or the Security Context to apply to new
32/// files, etc.
33///
34/// This trait allows layering of caching, delegation, and thread-safety between the policy-backed
35/// calculations, and the caller-facing permission-check interface.
36pub(super) trait Query {
37    /// Computes the [`AccessDecision`] permitted to `source_sid` for accessing `target_sid`, an
38    /// object of type `target_class`.
39    fn compute_access_decision(
40        &self,
41        source_sid: SecurityId,
42        target_sid: SecurityId,
43        target_class: KernelClass,
44    ) -> KernelAccessDecision;
45
46    /// Returns the security identifier (SID) with which to label a new object of `target_class`.
47    /// The label is calculated based on the creating `source_sid` and the `target_sid` of the
48    /// container (e.g. file-system, parent file node, process, etc) and optional `name`.
49    /// Callers pass an empty slice (`&[]`) for `name` to express nameless transitions.
50    fn compute_create_sid(
51        &self,
52        source_sid: SecurityId,
53        target_sid: SecurityId,
54        target_class: KernelClass,
55        name: &[u8],
56    ) -> Result<SecurityId, anyhow::Error>;
57
58    /// Computes the [`XpermsAccessDecision`] permitted to `source_sid` for accessing `target_sid`,
59    /// an object of type `target_class`, for xperms of kind `xperms_kind` with high byte
60    /// `xperms_prefix`.
61    fn compute_xperms_access_decision(
62        &self,
63        xperms_kind: XpermsKind,
64        source_sid: SecurityId,
65        target_sid: SecurityId,
66        permission: KernelPermission,
67        xperms_prefix: u8,
68    ) -> KernelXpermsAccessDecision;
69}
70
71#[derive(Clone, PartialEq, Eq, zerocopy::IntoBytes, zerocopy::Immutable)]
72#[repr(C)]
73pub struct AccessQueryArgs {
74    pub source_sid: SecurityId,
75    pub target_sid: SecurityId,
76    pub target_class: KernelClass,
77}
78
79#[derive(Clone, Hash, PartialEq, Eq)]
80pub(super) struct XpermsAccessQueryArgs {
81    pub(super) xperms_kind: XpermsKind,
82    pub(super) source_sid: SecurityId,
83    pub(super) target_sid: SecurityId,
84    pub(super) permission: KernelPermission,
85    pub(super) xperms_prefix: u8,
86}
87
88/// Concurrent set-associative cache with capacity defined at construction and CLOCK eviction.
89pub(super) struct FifoQueryCache {
90    access_cache: ConcurrentAccessCache,
91    create_sid_cache: ConcurrentSidCache,
92    xperms_access_cache: ConcurrentXpermsCache,
93}
94
95#[derive(Copy, Clone, Debug)]
96pub struct QueryCacheCapacity {
97    /// Capacities for the different caches. Due to limitations of the cache implementation,
98    /// these will be rounded up so the number of buckets is a power of two.
99    pub access_cache_capacity: usize,
100    pub sid_cache_capacity: usize,
101    pub xperms_cache_capacity: usize,
102}
103
104impl FifoQueryCache {
105    /// Constructs a fixed-size access vector cache.
106    pub fn new(capacity: QueryCacheCapacity) -> Self {
107        Self {
108            access_cache: ConcurrentAccessCache::new(capacity.access_cache_capacity),
109            create_sid_cache: ConcurrentSidCache::new(capacity.sid_cache_capacity),
110            xperms_access_cache: ConcurrentXpermsCache::new(capacity.xperms_cache_capacity),
111        }
112    }
113
114    pub fn cache_stats(&self) -> CacheStats {
115        let stats = &self.access_cache.cache_stats() + &self.create_sid_cache.cache_stats();
116        &stats + &self.xperms_access_cache.cache_stats()
117    }
118
119    pub fn compute_kernel_access_decision(
120        &self,
121        delegate: &impl Query,
122        source_sid: SecurityId,
123        target_sid: SecurityId,
124        target_class: KernelClass,
125    ) -> KernelAccessDecision {
126        let query_args = AccessQueryArgs { source_sid, target_sid, target_class };
127        self.access_cache.get_or_insert(&query_args, || {
128            delegate.compute_access_decision(source_sid, target_sid, target_class)
129        })
130    }
131
132    pub fn compute_create_sid(
133        &self,
134        delegate: &impl Query,
135        source_sid: SecurityId,
136        target_sid: SecurityId,
137        target_class: KernelClass,
138        name: &[u8],
139    ) -> Result<SecurityId, anyhow::Error> {
140        if !name.is_empty() {
141            delegate.compute_create_sid(source_sid, target_sid, target_class, name)
142        } else {
143            let query_args = AccessQueryArgs { source_sid, target_sid, target_class };
144            self.create_sid_cache.get_or_try_insert(&query_args, || {
145                delegate.compute_create_sid(source_sid, target_sid, target_class, name)
146            })
147        }
148    }
149
150    pub fn compute_kernel_xperms_access_decision(
151        &self,
152        delegate: &impl Query,
153        xperms_kind: XpermsKind,
154        source_sid: SecurityId,
155        target_sid: SecurityId,
156        permission: KernelPermission,
157        xperms_prefix: u8,
158    ) -> KernelXpermsAccessDecision {
159        let query_args = XpermsAccessQueryArgs {
160            xperms_kind,
161            source_sid,
162            target_sid,
163            permission,
164            xperms_prefix,
165        };
166        self.xperms_access_cache.get_or_insert(&query_args, || {
167            delegate.compute_xperms_access_decision(
168                xperms_kind,
169                source_sid,
170                target_sid,
171                permission,
172                xperms_prefix,
173            )
174        })
175    }
176
177    pub fn reset(&self) {
178        self.access_cache.reset();
179        self.create_sid_cache.reset();
180        self.xperms_access_cache.reset();
181    }
182
183    /// Returns true if the main access decision cache has reached capacity.
184    #[cfg(test)]
185    fn access_cache_is_full(&self) -> bool {
186        self.access_cache.is_full()
187    }
188}
189
190/// Default size of an access vector cache shared by all threads in the system.
191pub const DEFAULT_SHARED_SIZE: QueryCacheCapacity = QueryCacheCapacity {
192    // This was empirically determined to be a good default,
193    access_cache_capacity: 2048,
194    // The following were determined as a fraction of the access cache capacity.
195    sid_cache_capacity: 2048,
196    xperms_cache_capacity: 512,
197};
198
199/// An access vector cache.
200#[derive(Clone)]
201pub(super) struct AccessVectorCache {
202    cache: Arc<FifoQueryCache>,
203    backend: Arc<SecurityServerBackend>,
204}
205
206impl AccessVectorCache {
207    pub fn new(backend: Arc<SecurityServerBackend>) -> Self {
208        let cache = FifoQueryCache::new(DEFAULT_SHARED_SIZE);
209        Self { cache: Arc::new(cache), backend }
210    }
211
212    pub fn cache_stats(&self) -> CacheStats {
213        self.cache.cache_stats()
214    }
215
216    pub fn reset(&self) {
217        self.cache.reset()
218    }
219}
220
221impl Query for AccessVectorCache {
222    fn compute_access_decision(
223        &self,
224        source_sid: SecurityId,
225        target_sid: SecurityId,
226        target_class: KernelClass,
227    ) -> KernelAccessDecision {
228        self.cache.compute_kernel_access_decision(
229            self.backend.as_ref(),
230            source_sid,
231            target_sid,
232            target_class,
233        )
234    }
235
236    fn compute_create_sid(
237        &self,
238        source_sid: SecurityId,
239        target_sid: SecurityId,
240        target_class: KernelClass,
241        name: &[u8],
242    ) -> Result<SecurityId, anyhow::Error> {
243        self.cache.compute_create_sid(
244            self.backend.as_ref(),
245            source_sid,
246            target_sid,
247            target_class,
248            name,
249        )
250    }
251
252    fn compute_xperms_access_decision(
253        &self,
254        xperms_kind: XpermsKind,
255        source_sid: SecurityId,
256        target_sid: SecurityId,
257        permission: KernelPermission,
258        xperms_prefix: u8,
259    ) -> KernelXpermsAccessDecision {
260        self.cache.compute_kernel_xperms_access_decision(
261            self.backend.as_ref(),
262            xperms_kind,
263            source_sid,
264            target_sid,
265            permission,
266            xperms_prefix,
267        )
268    }
269}
270
271/// Test constants and helpers shared by `tests` and `starnix_tests`.
272#[cfg(test)]
273mod testing {
274    use super::*;
275    use crate::SecurityId;
276
277    use std::num::NonZeroU32;
278    use std::sync::LazyLock;
279    use std::sync::atomic::{AtomicU32, Ordering};
280
281    /// SID to use where any value will do.
282    pub(super) static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
283
284    /// Default fixed cache capacity to request in tests.
285    pub(super) const TEST_CAPACITY: QueryCacheCapacity = QueryCacheCapacity {
286        access_cache_capacity: 16,
287        sid_cache_capacity: 16,
288        xperms_cache_capacity: 4,
289    };
290
291    /// Returns a new `SecurityId` with unique id.
292    pub(super) fn unique_sid() -> SecurityId {
293        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
294        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::testing::*;
301    use super::*;
302    use crate::policy::{AccessVector, XpermsBitmap};
303    use crate::{KernelClass, ProcessPermission};
304
305    use std::sync::atomic::{AtomicUsize, Ordering};
306
307    /// No-op policy query delegate that allows all permissions and maintains no internal state, for testing.
308    #[derive(Default)]
309    struct TestDelegate {
310        query_count: AtomicUsize,
311    }
312
313    impl TestDelegate {
314        fn query_count(&self) -> usize {
315            self.query_count.load(Ordering::Relaxed)
316        }
317    }
318
319    impl Query for TestDelegate {
320        fn compute_access_decision(
321            &self,
322            _source_sid: SecurityId,
323            _target_sid: SecurityId,
324            _target_class: KernelClass,
325        ) -> KernelAccessDecision {
326            self.query_count.fetch_add(1, Ordering::Relaxed);
327            KernelAccessDecision {
328                allow: AccessVector::ALL,
329                audit: AccessVector::NONE,
330                flags: 0,
331                todo_bug: None,
332            }
333        }
334
335        fn compute_create_sid(
336            &self,
337            _source_sid: SecurityId,
338            _target_sid: SecurityId,
339            _target_class: KernelClass,
340            _name: &[u8],
341        ) -> Result<SecurityId, anyhow::Error> {
342            unreachable!()
343        }
344
345        fn compute_xperms_access_decision(
346            &self,
347            _xperms_kind: XpermsKind,
348            _source_sid: SecurityId,
349            _target_sid: SecurityId,
350            _target_class: KernelPermission,
351            _xperms_prefix: u8,
352        ) -> KernelXpermsAccessDecision {
353            self.query_count.fetch_add(1, Ordering::Relaxed);
354            KernelXpermsAccessDecision {
355                allow: XpermsBitmap::ALL,
356                audit: XpermsBitmap::NONE,
357                permissive: false,
358                has_todo: false,
359            }
360        }
361    }
362
363    #[test]
364    fn fixed_access_vector_cache_add_entry() {
365        let delegate = TestDelegate::default();
366        let avc = FifoQueryCache::new(TEST_CAPACITY);
367        assert_eq!(0, delegate.query_count());
368        assert_eq!(
369            AccessVector::ALL,
370            avc.compute_kernel_access_decision(
371                &delegate,
372                A_TEST_SID.clone(),
373                A_TEST_SID.clone(),
374                KernelClass::Process
375            )
376            .allow
377        );
378        assert_eq!(1, delegate.query_count());
379        assert_eq!(
380            AccessVector::ALL,
381            avc.compute_kernel_access_decision(
382                &delegate,
383                A_TEST_SID.clone(),
384                A_TEST_SID.clone(),
385                KernelClass::Process
386            )
387            .allow
388        );
389        assert_eq!(1, delegate.query_count());
390        assert_eq!(false, avc.access_cache_is_full());
391    }
392
393    #[test]
394    fn fixed_access_vector_cache_reset() {
395        let delegate = TestDelegate::default();
396        let avc = FifoQueryCache::new(TEST_CAPACITY);
397
398        avc.reset();
399        assert_eq!(false, avc.access_cache_is_full());
400
401        assert_eq!(0, delegate.query_count());
402        assert_eq!(
403            AccessVector::ALL,
404            avc.compute_kernel_access_decision(
405                &delegate,
406                A_TEST_SID.clone(),
407                A_TEST_SID.clone(),
408                KernelClass::Process
409            )
410            .allow
411        );
412        assert_eq!(1, delegate.query_count());
413        assert_eq!(false, avc.access_cache_is_full());
414
415        avc.reset();
416        assert_eq!(false, avc.access_cache_is_full());
417    }
418
419    #[test]
420    fn access_vector_cache_ioctl_hit() {
421        let delegate = TestDelegate::default();
422        let avc = FifoQueryCache::new(TEST_CAPACITY);
423        assert_eq!(0, delegate.query_count());
424        assert_eq!(
425            XpermsBitmap::ALL,
426            avc.compute_kernel_xperms_access_decision(
427                &delegate,
428                XpermsKind::Ioctl,
429                A_TEST_SID.clone(),
430                A_TEST_SID.clone(),
431                ProcessPermission::Fork.into(),
432                0x0,
433            )
434            .allow
435        );
436        assert_eq!(1, delegate.query_count());
437        // The second request for the same key is a cache hit.
438        assert_eq!(
439            XpermsBitmap::ALL,
440            avc.compute_kernel_xperms_access_decision(
441                &delegate,
442                XpermsKind::Ioctl,
443                A_TEST_SID.clone(),
444                A_TEST_SID.clone(),
445                ProcessPermission::Fork.into(),
446                0x0
447            )
448            .allow
449        );
450        assert_eq!(1, delegate.query_count());
451    }
452
453    #[test]
454    fn access_vector_cache_nlmsg_hit() {
455        let delegate = TestDelegate::default();
456        let avc = FifoQueryCache::new(TEST_CAPACITY);
457        assert_eq!(0, delegate.query_count());
458        assert_eq!(
459            XpermsBitmap::ALL,
460            avc.compute_kernel_xperms_access_decision(
461                &delegate,
462                XpermsKind::Nlmsg,
463                A_TEST_SID.clone(),
464                A_TEST_SID.clone(),
465                ProcessPermission::Fork.into(),
466                0x0,
467            )
468            .allow
469        );
470        assert_eq!(1, delegate.query_count());
471        // The second request for the same key is a cache hit.
472        assert_eq!(
473            XpermsBitmap::ALL,
474            avc.compute_kernel_xperms_access_decision(
475                &delegate,
476                XpermsKind::Nlmsg,
477                A_TEST_SID.clone(),
478                A_TEST_SID.clone(),
479                ProcessPermission::Fork.into(),
480                0x0
481            )
482            .allow
483        );
484        assert_eq!(1, delegate.query_count());
485    }
486
487    #[test]
488    fn access_vector_cache_nlmsg_and_ioctl() {
489        let delegate = TestDelegate::default();
490        let avc = FifoQueryCache::new(TEST_CAPACITY);
491
492        avc.compute_kernel_xperms_access_decision(
493            &delegate,
494            XpermsKind::Ioctl,
495            A_TEST_SID.clone(),
496            A_TEST_SID.clone(),
497            ProcessPermission::Fork.into(),
498            0x0,
499        );
500        assert_eq!(avc.cache_stats().allocs, 1);
501
502        // Query for an `nlmsg` extended permission for the same source, target, class,
503        // and prefix. This should cause a new allocation.
504        avc.compute_kernel_xperms_access_decision(
505            &delegate,
506            XpermsKind::Nlmsg,
507            A_TEST_SID.clone(),
508            A_TEST_SID.clone(),
509            ProcessPermission::Fork.into(),
510            0x0,
511        );
512        assert_eq!(avc.cache_stats().allocs, 2);
513    }
514}