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::{
7    AccessDecision, AccessVector, AccessVectorComputer, SELINUX_AVD_FLAGS_PERMISSIVE, XpermsKind,
8};
9use crate::security_server::SecurityServer;
10use crate::{
11    ClassPermission, FsNodeClass, KernelPermission, NullessByteStr, ObjectClass, SecurityId,
12};
13
14use std::num::NonZeroU64;
15
16/// Describes the result of a permission lookup between two Security Contexts.
17#[derive(Clone, Debug, PartialEq)]
18pub struct PermissionCheckResult {
19    /// True if the specified permissions should be permitted.
20    pub permit: bool,
21
22    /// True if details of the check should be audit logged. Audit logs are by default only output
23    /// when the policy defines that the permissions should be denied (whether or not the check is
24    /// "permissive"), but may be suppressed for some denials ("dontaudit"), or for some allowed
25    /// permissions ("auditallow").
26    pub audit: bool,
27
28    /// If the `AccessDecision` indicates that permission denials should not be enforced then `permit`
29    /// will be true, and this field will hold the Id of the bug to reference in audit logging.
30    pub todo_bug: Option<NonZeroU64>,
31}
32
33/// Implements the `has_permission()` API, based on supplied `SecurityServer` and
34/// `AccessVectorCache` implementations.
35// TODO: https://fxbug.dev/362699811 - Revise the traits to avoid direct dependencies on `SecurityServer`.
36pub struct PermissionCheck<'a> {
37    security_server: &'a SecurityServer,
38    access_vector_cache: &'a AccessVectorCache,
39}
40
41impl<'a> PermissionCheck<'a> {
42    pub(crate) fn new(
43        security_server: &'a SecurityServer,
44        access_vector_cache: &'a AccessVectorCache,
45    ) -> Self {
46        Self { security_server, access_vector_cache }
47    }
48
49    /// Returns whether the `source_sid` has the specified `permission` on `target_sid`.
50    /// The result indicates both whether `permission` is `permit`ted, and whether the caller
51    /// should `audit` log the query.
52    pub fn has_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
53        &self,
54        source_sid: SecurityId,
55        target_sid: SecurityId,
56        permission: P,
57    ) -> PermissionCheckResult {
58        has_permission(
59            self.security_server.is_enforcing(),
60            self.access_vector_cache,
61            self.security_server,
62            source_sid,
63            target_sid,
64            permission,
65        )
66    }
67
68    /// Returns whether the `source_sid` has both a base permission (i.e. `ioctl` or `nlmsg`) and
69    /// the specified extended permission on `target_sid`, and whether the decision should be
70    /// audited.
71    ///
72    /// A request is allowed if the base permission is `allow`ed and either the numeric extended
73    /// permission of this `xperms_kind` is included in an `allowxperm` statement, or extended
74    /// permissions of this kind are not filtered for this domain.
75    ///
76    /// A granted request is audited if the base permission is `auditallow` and the extended
77    /// permission is `auditallowxperm`.
78    ///
79    /// A denied request is audited if the base permission is `dontaudit` or the extended
80    /// permission is `dontauditxperm`.
81    pub fn has_extended_permission<
82        P: ClassPermission + Into<KernelPermission> + Clone + 'static,
83    >(
84        &self,
85        xperms_kind: XpermsKind,
86        source_sid: SecurityId,
87        target_sid: SecurityId,
88        permission: P,
89        xperm: u16,
90    ) -> PermissionCheckResult {
91        has_extended_permission(
92            self.security_server.is_enforcing(),
93            self.access_vector_cache,
94            self.security_server,
95            xperms_kind,
96            source_sid,
97            target_sid,
98            permission,
99            xperm,
100        )
101    }
102
103    // TODO: https://fxbug.dev/362699811 - Remove this once `SecurityServer` APIs such as `sid_to_security_context()`
104    // are exposed via a trait rather than directly by that implementation.
105    pub fn security_server(&self) -> &SecurityServer {
106        self.security_server
107    }
108
109    /// Returns the SID with which to label a new `file_class` instance created by `subject_sid`, with `target_sid`
110    /// as its parent, taking into account role & type transition rules, and filename-transition rules.
111    /// If a filename-transition rule matches the `fs_node_name` then that will be used, otherwise the
112    /// filename-independent computation will be applied.
113    pub fn compute_new_fs_node_sid(
114        &self,
115        source_sid: SecurityId,
116        target_sid: SecurityId,
117        fs_node_class: FsNodeClass,
118        fs_node_name: NullessByteStr<'_>,
119    ) -> Result<SecurityId, anyhow::Error> {
120        // TODO: https://fxbug.dev/385075470 - Stop skipping empty name lookups once by-name lookup is better optimized.
121        if !fs_node_name.as_bytes().is_empty() {
122            if let Some(sid) = self.access_vector_cache.compute_new_fs_node_sid_with_name(
123                source_sid,
124                target_sid,
125                fs_node_class,
126                fs_node_name,
127            ) {
128                return Ok(sid);
129            }
130        }
131        self.access_vector_cache.compute_create_sid(source_sid, target_sid, fs_node_class.into())
132    }
133
134    /// Returns the raw `AccessDecision` for a specified source, target and class.
135    pub fn compute_access_decision(
136        &self,
137        source_sid: SecurityId,
138        target_sid: SecurityId,
139        target_class: ObjectClass,
140    ) -> AccessDecision {
141        self.access_vector_cache.compute_access_decision(source_sid, target_sid, target_class)
142    }
143}
144
145/// Internal implementation of the `has_permission()` API, in terms of the `Query` and `AccessVectorComputer` traits.
146fn has_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
147    is_enforcing: bool,
148    query: &impl Query,
149    access_vector_computer: &impl AccessVectorComputer,
150    source_sid: SecurityId,
151    target_sid: SecurityId,
152    permission: P,
153) -> PermissionCheckResult {
154    let target_class = permission.class();
155
156    let decision = query.compute_access_decision(source_sid, target_sid, target_class.into());
157
158    let mut result = if let Some(permission_access_vector) =
159        access_vector_computer.kernel_permissions_to_access_vector(&[permission])
160    {
161        let permit = permission_access_vector & decision.allow == permission_access_vector;
162        let audit = if permit {
163            permission_access_vector & decision.auditallow != AccessVector::NONE
164        } else {
165            permission_access_vector & decision.auditdeny != AccessVector::NONE
166        };
167        PermissionCheckResult { permit, audit, todo_bug: None }
168    } else {
169        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
170    };
171
172    if !result.permit {
173        if decision.todo_bug.is_some() {
174            // If the access decision includes a `todo_bug` then permit the access and return the
175            // bug Id to the caller, for audit logging.
176            //
177            // This is checked before the "permissive" settings because exceptions work-around
178            // issues in our implementation, or policy builds, so permissive treatment can lead
179            // to logspam as well as losing bug-tracking information.
180            result.permit = true;
181            result.todo_bug = decision.todo_bug;
182        } else if !is_enforcing {
183            // If the security server is not currently enforcing then permit all access.
184            result.permit = true;
185        } else if decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
186            // If the access decision indicates that the source domain is permissive then permit
187            // all access.
188            result.permit = true;
189        }
190    }
191
192    result
193}
194
195/// Internal implementation of the `has_extended_permission()` API, in terms of the `Query` and
196/// `AccessVectorComputer` traits.
197fn has_extended_permission<P: ClassPermission + Into<KernelPermission> + Clone + 'static>(
198    is_enforcing: bool,
199    query: &impl Query,
200    access_vector_computer: &impl AccessVectorComputer,
201    xperms_kind: XpermsKind,
202    source_sid: SecurityId,
203    target_sid: SecurityId,
204    permission: P,
205    xperm: u16,
206) -> PermissionCheckResult {
207    let target_class = ObjectClass::from(permission.class());
208
209    let permission_decision = query.compute_access_decision(source_sid, target_sid, target_class);
210
211    let [xperms_postfix, xperms_prefix] = xperm.to_le_bytes();
212    let xperms_decision = query.compute_xperms_access_decision(
213        xperms_kind,
214        source_sid,
215        target_sid,
216        target_class,
217        xperms_prefix,
218    );
219
220    let mut result = if let Some(permission_access_vector) =
221        access_vector_computer.kernel_permissions_to_access_vector(&[permission])
222    {
223        let permit = (permission_access_vector & permission_decision.allow
224            == permission_access_vector)
225            && xperms_decision.allow.contains(xperms_postfix);
226        let audit = if permit {
227            (permission_access_vector & permission_decision.auditallow == permission_access_vector)
228                && xperms_decision.auditallow.contains(xperms_postfix)
229        } else {
230            (permission_access_vector & permission_decision.auditdeny == permission_access_vector)
231                && xperms_decision.auditdeny.contains(xperms_postfix)
232        };
233        PermissionCheckResult { permit, audit, todo_bug: None }
234    } else {
235        PermissionCheckResult { permit: false, audit: true, todo_bug: None }
236    };
237
238    if !result.permit {
239        if !is_enforcing {
240            result.permit = true;
241        } else if permission_decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0 {
242            result.permit = true;
243        } else if permission_decision.todo_bug.is_some() {
244            // Currently we can make an exception for the base permission but not for specific
245            // extended permissions.
246            result.permit = true;
247            result.todo_bug = permission_decision.todo_bug;
248        }
249    }
250
251    result
252}
253
254#[cfg(test)]
255mod tests {
256    use super::*;
257    use crate::policy::testing::{ACCESS_VECTOR_0001, ACCESS_VECTOR_0010};
258    use crate::policy::{AccessDecision, AccessVector, XpermsAccessDecision};
259    use crate::{
260        CommonFsNodePermission, FileClass, FilePermission, ForClass, ObjectClass, ProcessPermission,
261    };
262
263    use std::num::NonZeroU32;
264    use std::sync::LazyLock;
265    use std::sync::atomic::{AtomicU32, Ordering};
266
267    /// SID to use where any value will do.
268    static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
269
270    /// Returns a new `SecurityId` with unique id.
271    fn unique_sid() -> SecurityId {
272        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
273        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
274    }
275
276    fn access_vector_from_permission<P: ClassPermission + Into<KernelPermission> + 'static>(
277        permission: P,
278    ) -> AccessVector {
279        match permission.into() {
280            // Process class permissions
281            KernelPermission::Process(ProcessPermission::Fork) => ACCESS_VECTOR_0001,
282            KernelPermission::Process(ProcessPermission::Transition) => ACCESS_VECTOR_0010,
283            // File class permissions
284            KernelPermission::File(FilePermission::Ioctl) => ACCESS_VECTOR_0001,
285            _ => AccessVector::NONE,
286        }
287    }
288
289    fn access_vector_from_permissions<
290        'a,
291        P: ClassPermission + Into<KernelPermission> + Clone + 'static,
292    >(
293        permissions: &[P],
294    ) -> AccessVector {
295        let mut access_vector = AccessVector::NONE;
296        for permission in permissions {
297            access_vector |= access_vector_from_permission(permission.clone());
298        }
299        access_vector
300    }
301
302    #[derive(Default)]
303    pub struct DenyAllPermissions;
304
305    impl Query for DenyAllPermissions {
306        fn compute_access_decision(
307            &self,
308            _source_sid: SecurityId,
309            _target_sid: SecurityId,
310            _target_class: ObjectClass,
311        ) -> AccessDecision {
312            AccessDecision::allow(AccessVector::NONE)
313        }
314
315        fn compute_create_sid(
316            &self,
317            _source_sid: SecurityId,
318            _target_sid: SecurityId,
319            _target_class: ObjectClass,
320        ) -> Result<SecurityId, anyhow::Error> {
321            unreachable!();
322        }
323
324        fn compute_new_fs_node_sid_with_name(
325            &self,
326            _source_sid: SecurityId,
327            _target_sid: SecurityId,
328            _fs_node_class: FsNodeClass,
329            _fs_node_name: NullessByteStr<'_>,
330        ) -> Option<SecurityId> {
331            unreachable!();
332        }
333
334        fn compute_xperms_access_decision(
335            &self,
336            _xperms_kind: XpermsKind,
337            _source_sid: SecurityId,
338            _target_sid: SecurityId,
339            _target_class: ObjectClass,
340            _xperms_prefix: u8,
341        ) -> XpermsAccessDecision {
342            XpermsAccessDecision::DENY_ALL
343        }
344    }
345
346    impl AccessVectorComputer for DenyAllPermissions {
347        fn kernel_permissions_to_access_vector<
348            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
349        >(
350            &self,
351            permissions: &[P],
352        ) -> Option<AccessVector> {
353            Some(access_vector_from_permissions(permissions))
354        }
355    }
356
357    /// A [`Query`] that permits all [`AccessVector`].
358    #[derive(Default)]
359    struct AllowAllPermissions;
360
361    impl Query for AllowAllPermissions {
362        fn compute_access_decision(
363            &self,
364            _source_sid: SecurityId,
365            _target_sid: SecurityId,
366            _target_class: ObjectClass,
367        ) -> AccessDecision {
368            AccessDecision::allow(AccessVector::ALL)
369        }
370
371        fn compute_create_sid(
372            &self,
373            _source_sid: SecurityId,
374            _target_sid: SecurityId,
375            _target_class: ObjectClass,
376        ) -> Result<SecurityId, anyhow::Error> {
377            unreachable!();
378        }
379
380        fn compute_new_fs_node_sid_with_name(
381            &self,
382            _source_sid: SecurityId,
383            _target_sid: SecurityId,
384            _fs_node_class: FsNodeClass,
385            _fs_node_name: NullessByteStr<'_>,
386        ) -> Option<SecurityId> {
387            unreachable!();
388        }
389
390        fn compute_xperms_access_decision(
391            &self,
392            _xperms_kind: XpermsKind,
393            _source_sid: SecurityId,
394            _target_sid: SecurityId,
395            _target_class: ObjectClass,
396            _xperms_prefix: u8,
397        ) -> XpermsAccessDecision {
398            XpermsAccessDecision::ALLOW_ALL
399        }
400    }
401
402    impl AccessVectorComputer for AllowAllPermissions {
403        fn kernel_permissions_to_access_vector<
404            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
405        >(
406            &self,
407            permissions: &[P],
408        ) -> Option<AccessVector> {
409            Some(access_vector_from_permissions(permissions))
410        }
411    }
412
413    /// A [`Query`] that denies all [`AccessVectors`] and allows all extended permissions.
414    #[derive(Default)]
415    struct DenyPermissionsAllowXperms;
416
417    impl Query for DenyPermissionsAllowXperms {
418        fn compute_access_decision(
419            &self,
420            _source_sid: SecurityId,
421            _target_sid: SecurityId,
422            _target_class: ObjectClass,
423        ) -> AccessDecision {
424            AccessDecision::allow(AccessVector::NONE)
425        }
426
427        fn compute_create_sid(
428            &self,
429            _source_sid: SecurityId,
430            _target_sid: SecurityId,
431            _target_class: ObjectClass,
432        ) -> Result<SecurityId, anyhow::Error> {
433            unreachable!();
434        }
435
436        fn compute_new_fs_node_sid_with_name(
437            &self,
438            _source_sid: SecurityId,
439            _target_sid: SecurityId,
440            _fs_node_class: FsNodeClass,
441            _fs_node_name: NullessByteStr<'_>,
442        ) -> Option<SecurityId> {
443            unreachable!();
444        }
445
446        fn compute_xperms_access_decision(
447            &self,
448            _xperms_kind: XpermsKind,
449            _source_sid: SecurityId,
450            _target_sid: SecurityId,
451            _target_class: ObjectClass,
452            _xperms_prefix: u8,
453        ) -> XpermsAccessDecision {
454            XpermsAccessDecision::ALLOW_ALL
455        }
456    }
457
458    impl AccessVectorComputer for DenyPermissionsAllowXperms {
459        fn kernel_permissions_to_access_vector<
460            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
461        >(
462            &self,
463            permissions: &[P],
464        ) -> Option<AccessVector> {
465            Some(access_vector_from_permissions(permissions))
466        }
467    }
468
469    /// A [`Query`] that allows all [`AccessVectors`] and denies all extended permissions.
470    #[derive(Default)]
471    struct AllowPermissionsDenyXperms;
472
473    impl Query for AllowPermissionsDenyXperms {
474        fn compute_access_decision(
475            &self,
476            _source_sid: SecurityId,
477            _target_sid: SecurityId,
478            _target_class: ObjectClass,
479        ) -> AccessDecision {
480            AccessDecision::allow(AccessVector::ALL)
481        }
482
483        fn compute_create_sid(
484            &self,
485            _source_sid: SecurityId,
486            _target_sid: SecurityId,
487            _target_class: ObjectClass,
488        ) -> Result<SecurityId, anyhow::Error> {
489            unreachable!();
490        }
491
492        fn compute_new_fs_node_sid_with_name(
493            &self,
494            _source_sid: SecurityId,
495            _target_sid: SecurityId,
496            _fs_node_class: FsNodeClass,
497            _fs_node_name: NullessByteStr<'_>,
498        ) -> Option<SecurityId> {
499            unreachable!();
500        }
501
502        fn compute_xperms_access_decision(
503            &self,
504            _xperms_kind: XpermsKind,
505            _source_sid: SecurityId,
506            _target_sid: SecurityId,
507            _target_class: ObjectClass,
508            _xperms_prefix: u8,
509        ) -> XpermsAccessDecision {
510            XpermsAccessDecision::DENY_ALL
511        }
512    }
513
514    impl AccessVectorComputer for AllowPermissionsDenyXperms {
515        fn kernel_permissions_to_access_vector<
516            P: ClassPermission + Into<KernelPermission> + Clone + 'static,
517        >(
518            &self,
519            permissions: &[P],
520        ) -> Option<AccessVector> {
521            Some(access_vector_from_permissions(permissions))
522        }
523    }
524
525    #[test]
526    fn has_permission_both() {
527        let deny_all = DenyAllPermissions::default();
528        let allow_all = AllowAllPermissions::default();
529
530        // Use permissions that are mapped to access vector bits in
531        // `access_vector_from_permission`.
532        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
533        for permission in &permissions {
534            // DenyAllPermissions denies.
535            assert_eq!(
536                PermissionCheckResult { permit: false, audit: true, todo_bug: None },
537                has_permission(
538                    /*is_enforcing=*/ true,
539                    &deny_all,
540                    &deny_all,
541                    *A_TEST_SID,
542                    *A_TEST_SID,
543                    permission.clone()
544                )
545            );
546            // AllowAllPermissions allows.
547            assert_eq!(
548                PermissionCheckResult { permit: true, audit: false, todo_bug: None },
549                has_permission(
550                    /*is_enforcing=*/ true,
551                    &allow_all,
552                    &allow_all,
553                    *A_TEST_SID,
554                    *A_TEST_SID,
555                    permission.clone()
556                )
557            );
558        }
559    }
560
561    #[test]
562    fn has_ioctl_permission_enforcing() {
563        let deny_all = DenyAllPermissions::default();
564        let allow_all = AllowAllPermissions::default();
565        let deny_perms_allow_xperms = DenyPermissionsAllowXperms::default();
566        let allow_perms_deny_xperms = AllowPermissionsDenyXperms::default();
567        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
568
569        // DenyAllPermissions denies.
570        assert_eq!(
571            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
572            has_extended_permission(
573                /*is_enforcing=*/ true,
574                &deny_all,
575                &deny_all,
576                XpermsKind::Ioctl,
577                *A_TEST_SID,
578                *A_TEST_SID,
579                permission.clone(),
580                0xabcd
581            )
582        );
583        // AllowAllPermissions allows.
584        assert_eq!(
585            PermissionCheckResult { permit: true, audit: false, todo_bug: None },
586            has_extended_permission(
587                /*is_enforcing=*/ true,
588                &allow_all,
589                &allow_all,
590                XpermsKind::Ioctl,
591                *A_TEST_SID,
592                *A_TEST_SID,
593                permission.clone(),
594                0xabcd
595            )
596        );
597        // DenyPermissionsAllowXperms denies.
598        assert_eq!(
599            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
600            has_extended_permission(
601                /*is_enforcing=*/ true,
602                &deny_perms_allow_xperms,
603                &deny_perms_allow_xperms,
604                XpermsKind::Ioctl,
605                *A_TEST_SID,
606                *A_TEST_SID,
607                permission.clone(),
608                0xabcd
609            )
610        );
611        // AllowPermissionsDenyXperms denies.
612        assert_eq!(
613            PermissionCheckResult { permit: false, audit: true, todo_bug: None },
614            has_extended_permission(
615                /*is_enforcing=*/ true,
616                &allow_perms_deny_xperms,
617                &allow_perms_deny_xperms,
618                XpermsKind::Ioctl,
619                *A_TEST_SID,
620                *A_TEST_SID,
621                permission,
622                0xabcd
623            )
624        );
625    }
626
627    #[test]
628    fn has_ioctl_permission_not_enforcing() {
629        let deny_all = DenyAllPermissions::default();
630        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
631
632        // DenyAllPermissions denies, but the permission is allowed when the security server
633        // is not in enforcing mode. The decision should still be audited.
634        assert_eq!(
635            PermissionCheckResult { permit: true, audit: true, todo_bug: None },
636            has_extended_permission(
637                /*is_enforcing=*/ false,
638                &deny_all,
639                &deny_all,
640                XpermsKind::Ioctl,
641                *A_TEST_SID,
642                *A_TEST_SID,
643                permission,
644                0xabcd
645            )
646        );
647    }
648}