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