Skip to main content

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