Skip to main content

selinux/
permission_check.rs

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