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