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::{FsNodeClass, KernelClass, NullessByteStr, 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 `object_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).
49    ///
50    /// This computation does not take into account filename transition rules, for which the
51    /// `compute_fs_node_sid_with_name()` lookup should be used instead.
52    fn compute_create_sid(
53        &self,
54        source_sid: SecurityId,
55        target_sid: SecurityId,
56        target_class: KernelClass,
57    ) -> Result<SecurityId, anyhow::Error>;
58
59    /// Returns the security identifier (SID) with which to label a new `fs_node_class` instance of
60    /// name `fs_node_name`, created by `source_sid` in a parent directory labeled `target_sid`.
61    /// If no filename-transition rules exist for the specified `fs_node_name` then `None` is
62    /// returned.
63    fn compute_new_fs_node_sid_with_name(
64        &self,
65        source_sid: SecurityId,
66        target_sid: SecurityId,
67        fs_node_class: FsNodeClass,
68        fs_node_name: NullessByteStr<'_>,
69    ) -> Option<SecurityId>;
70
71    /// Computes the [`XpermsAccessDecision`] permitted to `source_sid` for accessing `target_sid`,
72    /// an object of type `target_class`, for xperms of kind `xperms_kind` with high byte
73    /// `xperms_prefix`.
74    fn compute_xperms_access_decision(
75        &self,
76        xperms_kind: XpermsKind,
77        source_sid: SecurityId,
78        target_sid: SecurityId,
79        permission: KernelPermission,
80        xperms_prefix: u8,
81    ) -> KernelXpermsAccessDecision;
82}
83
84#[derive(Clone, PartialEq, Eq, zerocopy::IntoBytes, zerocopy::Immutable)]
85#[repr(C)]
86pub struct AccessQueryArgs {
87    pub source_sid: SecurityId,
88    pub target_sid: SecurityId,
89    pub target_class: KernelClass,
90}
91
92#[derive(Clone, Hash, PartialEq, Eq)]
93pub(super) struct XpermsAccessQueryArgs {
94    pub(super) xperms_kind: XpermsKind,
95    pub(super) source_sid: SecurityId,
96    pub(super) target_sid: SecurityId,
97    pub(super) permission: KernelPermission,
98    pub(super) xperms_prefix: u8,
99}
100
101/// Concurrent set-associative cache with capacity defined at construction and CLOCK eviction.
102pub(super) struct FifoQueryCache {
103    access_cache: ConcurrentAccessCache,
104    create_sid_cache: ConcurrentSidCache,
105    xperms_access_cache: ConcurrentXpermsCache,
106}
107
108#[derive(Copy, Clone, Debug)]
109pub struct QueryCacheCapacity {
110    /// Capacities for the different caches. Due to limitations of the cache implementation,
111    /// these will be rounded up so the number of buckets is a power of two.
112    pub access_cache_capacity: usize,
113    pub sid_cache_capacity: usize,
114    pub xperms_cache_capacity: usize,
115}
116
117impl FifoQueryCache {
118    /// Constructs a fixed-size access vector cache.
119    pub fn new(capacity: QueryCacheCapacity) -> Self {
120        Self {
121            access_cache: ConcurrentAccessCache::new(capacity.access_cache_capacity),
122            create_sid_cache: ConcurrentSidCache::new(capacity.sid_cache_capacity),
123            xperms_access_cache: ConcurrentXpermsCache::new(capacity.xperms_cache_capacity),
124        }
125    }
126
127    pub fn cache_stats(&self) -> CacheStats {
128        let stats = &self.access_cache.cache_stats() + &self.create_sid_cache.cache_stats();
129        &stats + &self.xperms_access_cache.cache_stats()
130    }
131
132    pub fn compute_kernel_access_decision(
133        &self,
134        delegate: &impl Query,
135        source_sid: SecurityId,
136        target_sid: SecurityId,
137        target_class: KernelClass,
138    ) -> KernelAccessDecision {
139        let query_args = AccessQueryArgs { source_sid, target_sid, target_class };
140        self.access_cache.get_or_insert(&query_args, || {
141            delegate.compute_access_decision(source_sid, target_sid, target_class)
142        })
143    }
144
145    pub fn compute_create_sid(
146        &self,
147        delegate: &impl Query,
148        source_sid: SecurityId,
149        target_sid: SecurityId,
150        target_class: KernelClass,
151    ) -> Result<SecurityId, anyhow::Error> {
152        let query_args = AccessQueryArgs { source_sid, target_sid, target_class };
153        self.create_sid_cache.get_or_try_insert(&query_args, || {
154            delegate.compute_create_sid(source_sid, target_sid, target_class)
155        })
156    }
157
158    pub fn compute_new_fs_node_sid_with_name(
159        &self,
160        delegate: &impl Query,
161        source_sid: SecurityId,
162        target_sid: SecurityId,
163        fs_node_class: FsNodeClass,
164        fs_node_name: NullessByteStr<'_>,
165    ) -> Option<SecurityId> {
166        delegate.compute_new_fs_node_sid_with_name(
167            source_sid,
168            target_sid,
169            fs_node_class,
170            fs_node_name,
171        )
172    }
173
174    pub fn compute_kernel_xperms_access_decision(
175        &self,
176        delegate: &impl Query,
177        xperms_kind: XpermsKind,
178        source_sid: SecurityId,
179        target_sid: SecurityId,
180        permission: KernelPermission,
181        xperms_prefix: u8,
182    ) -> KernelXpermsAccessDecision {
183        let query_args = XpermsAccessQueryArgs {
184            xperms_kind,
185            source_sid,
186            target_sid,
187            permission,
188            xperms_prefix,
189        };
190        self.xperms_access_cache.get_or_insert(&query_args, || {
191            delegate.compute_xperms_access_decision(
192                xperms_kind,
193                source_sid,
194                target_sid,
195                permission,
196                xperms_prefix,
197            )
198        })
199    }
200
201    pub fn reset(&self) {
202        self.access_cache.reset();
203        self.create_sid_cache.reset();
204        self.xperms_access_cache.reset();
205    }
206
207    /// Returns true if the main access decision cache has reached capacity.
208    #[cfg(test)]
209    fn access_cache_is_full(&self) -> bool {
210        self.access_cache.is_full()
211    }
212}
213
214/// Default size of an access vector cache shared by all threads in the system.
215pub const DEFAULT_SHARED_SIZE: QueryCacheCapacity = QueryCacheCapacity {
216    // This was empirically determined to be a good default,
217    access_cache_capacity: 2048,
218    // The following were determined as a fraction of the access cache capacity.
219    sid_cache_capacity: 2048,
220    xperms_cache_capacity: 512,
221};
222
223/// An access vector cache.
224#[derive(Clone)]
225pub(super) struct AccessVectorCache {
226    cache: Arc<FifoQueryCache>,
227    backend: Arc<SecurityServerBackend>,
228}
229
230impl AccessVectorCache {
231    pub fn new(backend: Arc<SecurityServerBackend>) -> Self {
232        let cache = FifoQueryCache::new(DEFAULT_SHARED_SIZE);
233        Self { cache: Arc::new(cache), backend }
234    }
235
236    pub fn cache_stats(&self) -> CacheStats {
237        self.cache.cache_stats()
238    }
239
240    pub fn reset(&self) {
241        self.cache.reset()
242    }
243}
244
245impl Query for AccessVectorCache {
246    fn compute_access_decision(
247        &self,
248        source_sid: SecurityId,
249        target_sid: SecurityId,
250        target_class: KernelClass,
251    ) -> KernelAccessDecision {
252        self.cache.compute_kernel_access_decision(
253            self.backend.as_ref(),
254            source_sid,
255            target_sid,
256            target_class,
257        )
258    }
259
260    fn compute_create_sid(
261        &self,
262        source_sid: SecurityId,
263        target_sid: SecurityId,
264        target_class: KernelClass,
265    ) -> Result<SecurityId, anyhow::Error> {
266        self.cache.compute_create_sid(self.backend.as_ref(), source_sid, target_sid, target_class)
267    }
268
269    fn compute_new_fs_node_sid_with_name(
270        &self,
271        source_sid: SecurityId,
272        target_sid: SecurityId,
273        fs_node_class: FsNodeClass,
274        fs_node_name: NullessByteStr<'_>,
275    ) -> Option<SecurityId> {
276        self.cache.compute_new_fs_node_sid_with_name(
277            self.backend.as_ref(),
278            source_sid,
279            target_sid,
280            fs_node_class,
281            fs_node_name,
282        )
283    }
284
285    fn compute_xperms_access_decision(
286        &self,
287        xperms_kind: XpermsKind,
288        source_sid: SecurityId,
289        target_sid: SecurityId,
290        permission: KernelPermission,
291        xperms_prefix: u8,
292    ) -> KernelXpermsAccessDecision {
293        self.cache.compute_kernel_xperms_access_decision(
294            self.backend.as_ref(),
295            xperms_kind,
296            source_sid,
297            target_sid,
298            permission,
299            xperms_prefix,
300        )
301    }
302}
303
304/// Test constants and helpers shared by `tests` and `starnix_tests`.
305#[cfg(test)]
306mod testing {
307    use super::*;
308    use crate::SecurityId;
309
310    use std::num::NonZeroU32;
311    use std::sync::LazyLock;
312    use std::sync::atomic::{AtomicU32, Ordering};
313
314    /// SID to use where any value will do.
315    pub(super) static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
316
317    /// Default fixed cache capacity to request in tests.
318    pub(super) const TEST_CAPACITY: QueryCacheCapacity = QueryCacheCapacity {
319        access_cache_capacity: 16,
320        sid_cache_capacity: 16,
321        xperms_cache_capacity: 4,
322    };
323
324    /// Returns a new `SecurityId` with unique id.
325    pub(super) fn unique_sid() -> SecurityId {
326        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
327        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
328    }
329}
330
331#[cfg(test)]
332mod tests {
333    use super::testing::*;
334    use super::*;
335    use crate::policy::{AccessVector, XpermsBitmap};
336    use crate::{KernelClass, ProcessPermission};
337
338    use std::sync::atomic::{AtomicUsize, Ordering};
339
340    /// No-op policy query delegate that allows all permissions and maintains no internal state, for testing.
341    #[derive(Default)]
342    struct TestDelegate {
343        query_count: AtomicUsize,
344    }
345
346    impl TestDelegate {
347        fn query_count(&self) -> usize {
348            self.query_count.load(Ordering::Relaxed)
349        }
350    }
351
352    impl Query for TestDelegate {
353        fn compute_access_decision(
354            &self,
355            _source_sid: SecurityId,
356            _target_sid: SecurityId,
357            _target_class: KernelClass,
358        ) -> KernelAccessDecision {
359            self.query_count.fetch_add(1, Ordering::Relaxed);
360            KernelAccessDecision {
361                allow: AccessVector::ALL,
362                audit: AccessVector::NONE,
363                flags: 0,
364                todo_bug: None,
365            }
366        }
367
368        fn compute_create_sid(
369            &self,
370            _source_sid: SecurityId,
371            _target_sid: SecurityId,
372            _target_class: KernelClass,
373        ) -> Result<SecurityId, anyhow::Error> {
374            unreachable!()
375        }
376
377        fn compute_new_fs_node_sid_with_name(
378            &self,
379            _source_sid: SecurityId,
380            _target_sid: SecurityId,
381            _fs_node_class: FsNodeClass,
382            _fs_node_name: NullessByteStr<'_>,
383        ) -> Option<SecurityId> {
384            unreachable!()
385        }
386
387        fn compute_xperms_access_decision(
388            &self,
389            _xperms_kind: XpermsKind,
390            _source_sid: SecurityId,
391            _target_sid: SecurityId,
392            _target_class: KernelPermission,
393            _xperms_prefix: u8,
394        ) -> KernelXpermsAccessDecision {
395            self.query_count.fetch_add(1, Ordering::Relaxed);
396            KernelXpermsAccessDecision {
397                allow: XpermsBitmap::ALL,
398                audit: XpermsBitmap::NONE,
399                permissive: false,
400                has_todo: false,
401            }
402        }
403    }
404
405    #[test]
406    fn fixed_access_vector_cache_add_entry() {
407        let delegate = TestDelegate::default();
408        let avc = FifoQueryCache::new(TEST_CAPACITY);
409        assert_eq!(0, delegate.query_count());
410        assert_eq!(
411            AccessVector::ALL,
412            avc.compute_kernel_access_decision(
413                &delegate,
414                A_TEST_SID.clone(),
415                A_TEST_SID.clone(),
416                KernelClass::Process
417            )
418            .allow
419        );
420        assert_eq!(1, delegate.query_count());
421        assert_eq!(
422            AccessVector::ALL,
423            avc.compute_kernel_access_decision(
424                &delegate,
425                A_TEST_SID.clone(),
426                A_TEST_SID.clone(),
427                KernelClass::Process
428            )
429            .allow
430        );
431        assert_eq!(1, delegate.query_count());
432        assert_eq!(false, avc.access_cache_is_full());
433    }
434
435    #[test]
436    fn fixed_access_vector_cache_reset() {
437        let delegate = TestDelegate::default();
438        let avc = FifoQueryCache::new(TEST_CAPACITY);
439
440        avc.reset();
441        assert_eq!(false, avc.access_cache_is_full());
442
443        assert_eq!(0, delegate.query_count());
444        assert_eq!(
445            AccessVector::ALL,
446            avc.compute_kernel_access_decision(
447                &delegate,
448                A_TEST_SID.clone(),
449                A_TEST_SID.clone(),
450                KernelClass::Process
451            )
452            .allow
453        );
454        assert_eq!(1, delegate.query_count());
455        assert_eq!(false, avc.access_cache_is_full());
456
457        avc.reset();
458        assert_eq!(false, avc.access_cache_is_full());
459    }
460
461    #[test]
462    fn access_vector_cache_ioctl_hit() {
463        let delegate = TestDelegate::default();
464        let avc = FifoQueryCache::new(TEST_CAPACITY);
465        assert_eq!(0, delegate.query_count());
466        assert_eq!(
467            XpermsBitmap::ALL,
468            avc.compute_kernel_xperms_access_decision(
469                &delegate,
470                XpermsKind::Ioctl,
471                A_TEST_SID.clone(),
472                A_TEST_SID.clone(),
473                ProcessPermission::Fork.into(),
474                0x0,
475            )
476            .allow
477        );
478        assert_eq!(1, delegate.query_count());
479        // The second request for the same key is a cache hit.
480        assert_eq!(
481            XpermsBitmap::ALL,
482            avc.compute_kernel_xperms_access_decision(
483                &delegate,
484                XpermsKind::Ioctl,
485                A_TEST_SID.clone(),
486                A_TEST_SID.clone(),
487                ProcessPermission::Fork.into(),
488                0x0
489            )
490            .allow
491        );
492        assert_eq!(1, delegate.query_count());
493    }
494
495    #[test]
496    fn access_vector_cache_nlmsg_hit() {
497        let delegate = TestDelegate::default();
498        let avc = FifoQueryCache::new(TEST_CAPACITY);
499        assert_eq!(0, delegate.query_count());
500        assert_eq!(
501            XpermsBitmap::ALL,
502            avc.compute_kernel_xperms_access_decision(
503                &delegate,
504                XpermsKind::Nlmsg,
505                A_TEST_SID.clone(),
506                A_TEST_SID.clone(),
507                ProcessPermission::Fork.into(),
508                0x0,
509            )
510            .allow
511        );
512        assert_eq!(1, delegate.query_count());
513        // The second request for the same key is a cache hit.
514        assert_eq!(
515            XpermsBitmap::ALL,
516            avc.compute_kernel_xperms_access_decision(
517                &delegate,
518                XpermsKind::Nlmsg,
519                A_TEST_SID.clone(),
520                A_TEST_SID.clone(),
521                ProcessPermission::Fork.into(),
522                0x0
523            )
524            .allow
525        );
526        assert_eq!(1, delegate.query_count());
527    }
528
529    #[test]
530    fn access_vector_cache_nlmsg_and_ioctl() {
531        let delegate = TestDelegate::default();
532        let avc = FifoQueryCache::new(TEST_CAPACITY);
533
534        avc.compute_kernel_xperms_access_decision(
535            &delegate,
536            XpermsKind::Ioctl,
537            A_TEST_SID.clone(),
538            A_TEST_SID.clone(),
539            ProcessPermission::Fork.into(),
540            0x0,
541        );
542        assert_eq!(avc.cache_stats().allocs, 1);
543
544        // Query for an `nlmsg` extended permission for the same source, target, class,
545        // and prefix. This should cause a new allocation.
546        avc.compute_kernel_xperms_access_decision(
547            &delegate,
548            XpermsKind::Nlmsg,
549            A_TEST_SID.clone(),
550            A_TEST_SID.clone(),
551            ProcessPermission::Fork.into(),
552            0x0,
553        );
554        assert_eq!(avc.cache_stats().allocs, 2);
555    }
556}