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