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