Skip to main content

selinux/
permission_check.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::access_vector_cache::{AccessVectorCache, Query};
6use crate::policy::{AccessVector, KernelAccessDecision, SELINUX_AVD_FLAGS_PERMISSIVE, XpermsKind};
7use crate::security_server::{PolicySeqNo, SecurityServer};
8use crate::{ClassPermission, FdPermission, KernelClass, KernelPermission, SecurityId};
9
10use std::num::NonZeroU32;
11
12pub use crate::local_cache::PerThreadCache;
13
14/// Describes the result of a permission lookup between two Security Contexts.
15#[derive(Clone, Debug, PartialEq)]
16pub struct PermissionCheckResult {
17    /// True if the specified permissions are granted by policy.
18    pub granted: bool,
19
20    /// True if details of the check should be audit logged. Audit logs are by default only output
21    /// when the policy defines that the permissions should be denied (whether or not the check is
22    /// "permissive"), but may be suppressed for some denials ("dontaudit"), or for some allowed
23    /// permissions ("auditallow").
24    pub audit: bool,
25
26    /// True if the access should be granted because either the security server is running in
27    /// permissive mode, or the subject domain is marked as permissive.
28    pub permissive: bool,
29
30    /// If the `AccessDecision` indicates that permission denials should not be enforced then `permit`
31    /// will be true, and this field will hold the Id of the bug to reference in audit logging.
32    pub todo_bug: Option<NonZeroU32>,
33}
34
35impl PermissionCheckResult {
36    /// Returns true if the request was granted, or was made in permissive mode.
37    pub fn permit(&self) -> bool {
38        self.granted || self.permissive
39    }
40}
41
42/// Implements the `has_permission()` API, based on supplied `SecurityServer` and
43/// `AccessVectorCache` implementations.
44// TODO: https://fxbug.dev/362699811 - Revise the traits to avoid direct dependencies on `SecurityServer`.
45pub struct PermissionCheck<'a> {
46    security_server: &'a SecurityServer,
47    access_vector_cache: &'a AccessVectorCache,
48    local_cache: &'a PerThreadCache,
49}
50
51impl<'a> PermissionCheck<'a> {
52    pub(crate) fn new(
53        security_server: &'a SecurityServer,
54        access_vector_cache: &'a AccessVectorCache,
55        local_cache: &'a PerThreadCache,
56    ) -> Self {
57        Self { security_server, access_vector_cache, local_cache }
58    }
59
60    /// Returns whether the `source_sid` has the specified `permission` on `target_sid`.
61    /// The result indicates both whether `permission` is `permit`ted, and whether the caller
62    /// should `audit` log the query.
63    pub fn has_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
64        &self,
65        source_sid: SecurityId,
66        target_sid: SecurityId,
67        permission: P,
68    ) -> PermissionCheckResult {
69        let policy_seqno = self.security_server.policy_seqno();
70        let result = has_permission(
71            self.local_cache,
72            self.access_vector_cache,
73            policy_seqno,
74            source_sid,
75            target_sid,
76            permission.into(),
77        );
78        self.apply_enforcement(result)
79    }
80
81    /// Returns whether the `source_sid` has both a base permission (i.e. `ioctl` or `nlmsg`) and
82    /// the specified extended permission on `target_sid`, and whether the decision should be
83    /// audited.
84    ///
85    /// A request is allowed if the base permission is `allow`ed and either the numeric extended
86    /// permission of this `xperms_kind` is included in an `allowxperm` statement, or extended
87    /// permissions of this kind are not filtered for this domain.
88    ///
89    /// A granted request is audited if the base permission is `auditallow` and the extended
90    /// permission is `auditallowxperm`.
91    ///
92    /// A denied request is audited if the base permission is `dontaudit` or the extended
93    /// permission is `dontauditxperm`.
94    pub fn has_extended_permission<
95        P: ClassPermission + Into<KernelPermission> + Clone + 'static,
96    >(
97        &self,
98        xperms_kind: XpermsKind,
99        source_sid: SecurityId,
100        target_sid: SecurityId,
101        permission: P,
102        xperm: u16,
103    ) -> PermissionCheckResult {
104        let permission: KernelPermission = permission.into();
105        let policy_seqno = self.security_server.policy_seqno();
106        let result = self.local_cache.check_xperm(
107            policy_seqno,
108            xperms_kind,
109            source_sid,
110            target_sid,
111            permission,
112            xperm,
113            || {
114                has_extended_permission(
115                    self.access_vector_cache,
116                    xperms_kind,
117                    source_sid,
118                    target_sid,
119                    permission,
120                    xperm,
121                )
122            },
123        );
124        self.apply_enforcement(result)
125    }
126
127    fn apply_enforcement(&self, mut result: PermissionCheckResult) -> PermissionCheckResult {
128        if !result.granted {
129            if !self.security_server.is_enforcing() {
130                result.permissive = true;
131                result.todo_bug = None;
132            } else if result.todo_bug.is_some() {
133                result.granted = true;
134            }
135        } else {
136            result.todo_bug = None;
137        }
138        result
139    }
140
141    // TODO: https://fxbug.dev/362699811 - Remove this once `SecurityServer` APIs such as `sid_to_security_context()`
142    // are exposed via a trait rather than directly by that implementation.
143    pub fn security_server(&self) -> &SecurityServer {
144        self.security_server
145    }
146
147    /// Returns the SID with which to label a new `target_class` instance created by `source_sid`
148    /// in a container labeled `target_sid`, taking into account role, type, and optional filename
149    /// transition rules.
150    /// Callers pass an empty slice (`&[]`) for `name` to express nameless transitions.
151    pub fn compute_create_sid(
152        &self,
153        source_sid: SecurityId,
154        target_sid: SecurityId,
155        target_class: KernelClass,
156        name: &[u8],
157    ) -> Result<SecurityId, anyhow::Error> {
158        self.access_vector_cache.compute_create_sid(source_sid, target_sid, target_class, name)
159    }
160
161    /// Returns the raw `AccessDecision` for a specified source, target and class.
162    pub fn compute_access_decision(
163        &self,
164        source_sid: SecurityId,
165        target_sid: SecurityId,
166        target_class: KernelClass,
167    ) -> KernelAccessDecision {
168        self.local_cache.lookup_access_decision(
169            self.security_server.policy_seqno(),
170            source_sid,
171            target_sid,
172            target_class,
173            || {
174                self.access_vector_cache.compute_access_decision(
175                    source_sid,
176                    target_sid,
177                    target_class,
178                )
179            },
180        )
181    }
182}
183
184/// Internal implementation of the `has_permission()` API, in terms of the `Query` trait.
185fn has_permission(
186    local_cache: &PerThreadCache,
187    query: &impl Query,
188    policy_seqno: PolicySeqNo,
189    source_sid: SecurityId,
190    target_sid: SecurityId,
191    permission: KernelPermission,
192) -> PermissionCheckResult {
193    let permission_access_vector = permission.as_access_vector();
194
195    if permission == KernelPermission::Fd(FdPermission::Use) {
196        // fd use checks are cached separately.
197        return local_cache.lookup_fd_use(policy_seqno, source_sid, target_sid, || {
198            let decision = query.compute_access_decision(source_sid, target_sid, KernelClass::Fd);
199            access_decision_to_permission_check_result(permission_access_vector, decision)
200        });
201    }
202
203    let decision = local_cache.lookup_access_decision(
204        policy_seqno,
205        source_sid,
206        target_sid,
207        permission.class(),
208        || query.compute_access_decision(source_sid, target_sid, permission.class()),
209    );
210    access_decision_to_permission_check_result(permission_access_vector, decision)
211}
212
213fn access_decision_to_permission_check_result(
214    permission_access_vector: AccessVector,
215    decision: KernelAccessDecision,
216) -> PermissionCheckResult {
217    let permissive = decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0;
218    let granted = permission_access_vector & decision.allow == permission_access_vector;
219    let audit = permission_access_vector & decision.audit != AccessVector::NONE;
220    PermissionCheckResult { granted, audit, permissive, todo_bug: decision.todo_bug }
221}
222
223/// Internal implementation of the `has_extended_permission()` API, in terms of the `Query` trait.
224fn has_extended_permission(
225    query: &impl Query,
226    xperms_kind: XpermsKind,
227    source_sid: SecurityId,
228    target_sid: SecurityId,
229    permission: KernelPermission,
230    xperm: u16,
231) -> PermissionCheckResult {
232    let [xperms_postfix, xperms_prefix] = xperm.to_le_bytes();
233    let xperms_decision = query.compute_xperms_access_decision(
234        xperms_kind,
235        source_sid,
236        target_sid,
237        permission,
238        xperms_prefix,
239    );
240
241    let granted = xperms_decision.allow.contains(xperms_postfix);
242    let audit = xperms_decision.audit.contains(xperms_postfix);
243    let permissive = xperms_decision.permissive;
244    let mut result = PermissionCheckResult { granted, audit, permissive, todo_bug: None };
245
246    if !result.permit() && xperms_decision.has_todo {
247        // A todo_bug applies to this entry. Look up the base decision for details.
248        // This will re-compute the base decision if it is not cached.
249        let base_decision =
250            query.compute_access_decision(source_sid, target_sid, permission.class());
251        result.todo_bug = base_decision.todo_bug;
252    }
253
254    result
255}
256
257#[cfg(test)]
258mod tests {
259    use super::*;
260    use crate::access_vector_cache::KernelXpermsAccessDecision;
261    use crate::policy::{
262        AccessDecision, AccessVector, AccessVectorComputer, KernelAccessDecision, XpermsBitmap,
263    };
264    use crate::{CommonFsNodePermission, FileClass, ForClass, KernelClass, ProcessPermission};
265
266    use std::num::NonZeroU32;
267    use std::sync::LazyLock;
268    use std::sync::atomic::{AtomicU32, Ordering};
269
270    /// SID to use where any value will do.
271    static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
272
273    /// Returns a new `SecurityId` with unique id.
274    fn unique_sid() -> SecurityId {
275        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
276        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
277    }
278
279    // Assume permissions are mapped one to one.
280    fn access_decision_to_kernel_access_decision(
281        _class: KernelClass,
282        decision: AccessDecision,
283    ) -> KernelAccessDecision {
284        KernelAccessDecision {
285            allow: decision.allow,
286            audit: (decision.allow & decision.auditallow) | (!decision.allow & decision.auditdeny),
287            todo_bug: decision.todo_bug,
288            flags: decision.flags,
289        }
290    }
291
292    #[derive(Default)]
293    pub struct DenyAllPermissions;
294
295    impl Query for DenyAllPermissions {
296        fn compute_access_decision(
297            &self,
298            _source_sid: SecurityId,
299            _target_sid: SecurityId,
300            _target_class: KernelClass,
301        ) -> KernelAccessDecision {
302            KernelAccessDecision {
303                allow: AccessVector::NONE,
304                audit: AccessVector::ALL,
305                flags: 0,
306                todo_bug: None,
307            }
308        }
309
310        fn compute_create_sid(
311            &self,
312            _source_sid: SecurityId,
313            _target_sid: SecurityId,
314            _target_class: KernelClass,
315            _name: &[u8],
316        ) -> Result<SecurityId, anyhow::Error> {
317            unreachable!();
318        }
319
320        fn compute_xperms_access_decision(
321            &self,
322            _xperms_kind: XpermsKind,
323            _source_sid: SecurityId,
324            _target_sid: SecurityId,
325            _permission: KernelPermission,
326            _xperms_prefix: u8,
327        ) -> KernelXpermsAccessDecision {
328            KernelXpermsAccessDecision {
329                allow: XpermsBitmap::NONE,
330                audit: XpermsBitmap::ALL,
331                permissive: false,
332                has_todo: false,
333            }
334        }
335    }
336
337    impl AccessVectorComputer for DenyAllPermissions {
338        fn access_decision_to_kernel_access_decision(
339            &self,
340            class: KernelClass,
341            av: AccessDecision,
342        ) -> KernelAccessDecision {
343            access_decision_to_kernel_access_decision(class, av)
344        }
345    }
346
347    /// A [`Query`] that permits all [`AccessVector`].
348    #[derive(Default)]
349    struct AllowAllPermissions;
350
351    impl Query for AllowAllPermissions {
352        fn compute_access_decision(
353            &self,
354            _source_sid: SecurityId,
355            _target_sid: SecurityId,
356            _target_class: KernelClass,
357        ) -> KernelAccessDecision {
358            KernelAccessDecision {
359                allow: AccessVector::ALL,
360                audit: AccessVector::NONE,
361                flags: 0,
362                todo_bug: None,
363            }
364        }
365
366        fn compute_create_sid(
367            &self,
368            _source_sid: SecurityId,
369            _target_sid: SecurityId,
370            _target_class: KernelClass,
371            _name: &[u8],
372        ) -> Result<SecurityId, anyhow::Error> {
373            unreachable!();
374        }
375
376        fn compute_xperms_access_decision(
377            &self,
378            _xperms_kind: XpermsKind,
379            _source_sid: SecurityId,
380            _target_sid: SecurityId,
381            _permission: KernelPermission,
382            _xperms_prefix: u8,
383        ) -> KernelXpermsAccessDecision {
384            KernelXpermsAccessDecision {
385                allow: XpermsBitmap::ALL,
386                audit: XpermsBitmap::NONE,
387                permissive: false,
388                has_todo: false,
389            }
390        }
391    }
392
393    impl AccessVectorComputer for AllowAllPermissions {
394        fn access_decision_to_kernel_access_decision(
395            &self,
396            class: KernelClass,
397            av: AccessDecision,
398        ) -> KernelAccessDecision {
399            access_decision_to_kernel_access_decision(class, av)
400        }
401    }
402
403    #[test]
404    fn has_permission_both() {
405        let deny_all = DenyAllPermissions::default();
406        let allow_all = AllowAllPermissions::default();
407
408        // Use permissions that are mapped to access vector bits in
409        // `access_vector_from_permission`.
410        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
411        for permission in permissions {
412            let local_cache1 = PerThreadCache::default();
413            // DenyAllPermissions denies.
414            let result = has_permission(
415                &local_cache1,
416                &deny_all,
417                PolicySeqNo::INITIAL,
418                *A_TEST_SID,
419                *A_TEST_SID,
420                permission.into(),
421            );
422            assert_eq!(
423                result,
424                PermissionCheckResult {
425                    granted: false,
426                    audit: true,
427                    permissive: false,
428                    todo_bug: None
429                }
430            );
431            assert!(!result.permit());
432
433            let local_cache2 = PerThreadCache::default();
434            // AllowAllPermissions allows.
435            let result = has_permission(
436                &local_cache2,
437                &allow_all,
438                PolicySeqNo::INITIAL,
439                *A_TEST_SID,
440                *A_TEST_SID,
441                permission.into(),
442            );
443            assert_eq!(
444                result,
445                PermissionCheckResult {
446                    granted: true,
447                    audit: false,
448                    permissive: false,
449                    todo_bug: None
450                }
451            );
452            assert!(result.permit());
453        }
454    }
455
456    #[test]
457    fn has_ioctl_permission_enforcing() {
458        let deny_all = DenyAllPermissions::default();
459        let allow_all = AllowAllPermissions::default();
460        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
461
462        // DenyAllPermissions denies.
463        let result = has_extended_permission(
464            &deny_all,
465            XpermsKind::Ioctl,
466            *A_TEST_SID,
467            *A_TEST_SID,
468            permission.into(),
469            0xabcd,
470        );
471        assert_eq!(
472            result,
473            PermissionCheckResult {
474                granted: false,
475                audit: true,
476                permissive: false,
477                todo_bug: None
478            }
479        );
480        assert!(!result.permit());
481
482        // AllowAllPermissions allows.
483        let result = has_extended_permission(
484            &allow_all,
485            XpermsKind::Ioctl,
486            *A_TEST_SID,
487            *A_TEST_SID,
488            permission.into(),
489            0xabcd,
490        );
491        assert_eq!(
492            result,
493            PermissionCheckResult {
494                granted: true,
495                audit: false,
496                permissive: false,
497                todo_bug: None
498            }
499        );
500        assert!(result.permit());
501    }
502
503    fn security_server_with_tests_policy() -> std::sync::Arc<SecurityServer> {
504        const POLICY: &[u8] =
505            include_bytes!("../testdata/micro_policies/security_server_tests_policy");
506        let security_server = SecurityServer::new_default();
507        assert!(security_server.load_policy(POLICY.into()).is_ok());
508        security_server
509    }
510
511    #[test]
512    fn has_ioctl_permission_not_enforcing() {
513        let security_server = security_server_with_tests_policy();
514        let enforcing_values = [true, false];
515        for enforcing in enforcing_values {
516            security_server.set_enforcing(enforcing);
517
518            let sid =
519                security_server.security_context_to_sid("user0:object_r:type0:s0".into()).unwrap();
520            let local_cache = PerThreadCache::default();
521            let permission_check = security_server.as_permission_check(&local_cache);
522
523            let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
524
525            // The test policy does not grant the permission, but when the security server
526            // is not in enforcing mode the permission will still be granted.
527            // Because the permission was not granted by policy, the check will be audit logged.
528            let result = permission_check.has_extended_permission(
529                XpermsKind::Ioctl,
530                sid,
531                sid,
532                permission,
533                0xabcd,
534            );
535            assert_eq!(
536                result,
537                PermissionCheckResult {
538                    granted: false,
539                    audit: true,
540                    permissive: !enforcing,
541                    todo_bug: None
542                }
543            );
544            assert_eq!(result.permit(), !enforcing);
545        }
546    }
547}