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        has_permission(
73            self.security_server.is_enforcing(),
74            self.local_cache,
75            self.access_vector_cache,
76            source_sid,
77            target_sid,
78            permission.into(),
79        )
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        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.security_server.is_enforcing(),
115                    self.access_vector_cache,
116                    xperms_kind,
117                    source_sid,
118                    target_sid,
119                    permission.into(),
120                    xperm,
121                )
122            },
123        )
124    }
125
126    // TODO: https://fxbug.dev/362699811 - Remove this once `SecurityServer` APIs such as `sid_to_security_context()`
127    // are exposed via a trait rather than directly by that implementation.
128    pub fn security_server(&self) -> &SecurityServer {
129        self.security_server
130    }
131
132    /// Returns the SID with which to label a new `file_class` instance created by `subject_sid`, with `target_sid`
133    /// as its parent, taking into account role & type transition rules, and filename-transition rules.
134    /// If a filename-transition rule matches the `fs_node_name` then that will be used, otherwise the
135    /// filename-independent computation will be applied.
136    pub fn compute_new_fs_node_sid(
137        &self,
138        source_sid: SecurityId,
139        target_sid: SecurityId,
140        fs_node_class: FsNodeClass,
141        fs_node_name: NullessByteStr<'_>,
142    ) -> Result<SecurityId, anyhow::Error> {
143        // TODO: https://fxbug.dev/385075470 - Stop skipping empty name lookups once by-name lookup is better optimized.
144        if !fs_node_name.as_bytes().is_empty() {
145            if let Some(sid) = self.access_vector_cache.compute_new_fs_node_sid_with_name(
146                source_sid,
147                target_sid,
148                fs_node_class,
149                fs_node_name,
150            ) {
151                return Ok(sid);
152            }
153        }
154        self.access_vector_cache.compute_create_sid(source_sid, target_sid, fs_node_class.into())
155    }
156
157    /// Returns the SID with which to label a new `target_class` instance created by `subject_sid`, with `target_sid`
158    /// as its parent, taking into account role & type transition rules.
159    pub fn compute_create_sid(
160        &self,
161        source_sid: SecurityId,
162        target_sid: SecurityId,
163        target_class: KernelClass,
164    ) -> Result<SecurityId, anyhow::Error> {
165        self.access_vector_cache.compute_create_sid(source_sid, target_sid, target_class)
166    }
167
168    /// Returns the raw `AccessDecision` for a specified source, target and class.
169    pub fn compute_access_decision(
170        &self,
171        source_sid: SecurityId,
172        target_sid: SecurityId,
173        target_class: KernelClass,
174    ) -> KernelAccessDecision {
175        self.local_cache.lookup_access_decision(source_sid, target_sid, target_class, || {
176            self.access_vector_cache.compute_access_decision(source_sid, target_sid, target_class)
177        })
178    }
179}
180
181/// Internal implementation of the `has_permission()` API, in terms of the `Query` trait.
182fn has_permission(
183    is_enforcing: bool,
184    local_cache: &PerThreadCache,
185    query: &impl Query,
186    source_sid: SecurityId,
187    target_sid: SecurityId,
188    permission: KernelPermission,
189) -> PermissionCheckResult {
190    let permission_access_vector = permission.as_access_vector();
191
192    if permission == KernelPermission::Fd(FdPermission::Use) {
193        // fd use checks are cached separately.
194        return local_cache.lookup_fd_use(source_sid, target_sid, || {
195            let decision = query.compute_access_decision(source_sid, target_sid, KernelClass::Fd);
196            access_decision_to_permission_check_result(
197                is_enforcing,
198                permission_access_vector,
199                decision,
200            )
201        });
202    }
203
204    let decision =
205        local_cache.lookup_access_decision(source_sid, target_sid, permission.class(), || {
206            query.compute_access_decision(source_sid, target_sid, permission.class())
207        });
208    let result = access_decision_to_permission_check_result(
209        is_enforcing,
210        permission_access_vector,
211        decision,
212    );
213
214    result
215}
216
217fn access_decision_to_permission_check_result(
218    is_enforcing: bool,
219    permission_access_vector: AccessVector,
220    decision: KernelAccessDecision,
221) -> PermissionCheckResult {
222    let permissive = decision.flags & SELINUX_AVD_FLAGS_PERMISSIVE != 0;
223    let granted = permission_access_vector & decision.allow == permission_access_vector;
224    let audit = permission_access_vector & decision.audit != AccessVector::NONE;
225    let mut result = PermissionCheckResult { granted, audit, permissive, todo_bug: None };
226
227    if !result.granted {
228        if !is_enforcing {
229            result.permissive = true;
230        } else if decision.todo_bug.is_some() {
231            result.granted = true;
232            result.todo_bug = decision.todo_bug;
233        }
234    }
235
236    result
237}
238
239/// Internal implementation of the `has_extended_permission()` API, in terms of the `Query` trait.
240fn has_extended_permission(
241    is_enforcing: bool,
242    query: &impl Query,
243    xperms_kind: XpermsKind,
244    source_sid: SecurityId,
245    target_sid: SecurityId,
246    permission: KernelPermission,
247    xperm: u16,
248) -> PermissionCheckResult {
249    let [xperms_postfix, xperms_prefix] = xperm.to_le_bytes();
250    let xperms_decision = query.compute_xperms_access_decision(
251        xperms_kind,
252        source_sid,
253        target_sid,
254        permission,
255        xperms_prefix,
256    );
257
258    let granted = xperms_decision.allow.contains(xperms_postfix);
259    let audit = xperms_decision.audit.contains(xperms_postfix);
260    let permissive = xperms_decision.permissive;
261    let mut result = PermissionCheckResult { granted, audit, permissive, todo_bug: None };
262
263    if !result.granted {
264        if !is_enforcing {
265            result.permissive = true;
266        } else if xperms_decision.has_todo {
267            // A todo_bug applies to this entry. Look up the base decision for details.
268            // This will re-compute the base decision if it is not cached.
269            let base_decision =
270                query.compute_access_decision(source_sid, target_sid, permission.class());
271            result.todo_bug = base_decision.todo_bug;
272        }
273    }
274
275    result
276}
277
278#[cfg(test)]
279mod tests {
280    use super::*;
281    use crate::access_vector_cache::KernelXpermsAccessDecision;
282    use crate::policy::{
283        AccessDecision, AccessVector, AccessVectorComputer, KernelAccessDecision, XpermsBitmap,
284    };
285    use crate::{CommonFsNodePermission, FileClass, ForClass, KernelClass, ProcessPermission};
286
287    use std::num::NonZeroU32;
288    use std::sync::LazyLock;
289    use std::sync::atomic::{AtomicU32, Ordering};
290
291    /// SID to use where any value will do.
292    static A_TEST_SID: LazyLock<SecurityId> = LazyLock::new(unique_sid);
293
294    /// Returns a new `SecurityId` with unique id.
295    fn unique_sid() -> SecurityId {
296        static NEXT_ID: AtomicU32 = AtomicU32::new(1000);
297        SecurityId(NonZeroU32::new(NEXT_ID.fetch_add(1, Ordering::AcqRel)).unwrap())
298    }
299
300    // Assume permissions are mapped one to one.
301    fn access_decision_to_kernel_access_decision(
302        _class: KernelClass,
303        decision: AccessDecision,
304    ) -> KernelAccessDecision {
305        KernelAccessDecision {
306            allow: decision.allow,
307            audit: (decision.allow & decision.auditallow) | (!decision.allow & decision.auditdeny),
308            todo_bug: decision.todo_bug,
309            flags: decision.flags,
310        }
311    }
312
313    #[derive(Default)]
314    pub struct DenyAllPermissions;
315
316    impl Query for DenyAllPermissions {
317        fn compute_access_decision(
318            &self,
319            _source_sid: SecurityId,
320            _target_sid: SecurityId,
321            _target_class: KernelClass,
322        ) -> KernelAccessDecision {
323            KernelAccessDecision {
324                allow: AccessVector::NONE,
325                audit: AccessVector::ALL,
326                flags: 0,
327                todo_bug: None,
328            }
329        }
330
331        fn compute_create_sid(
332            &self,
333            _source_sid: SecurityId,
334            _target_sid: SecurityId,
335            _target_class: KernelClass,
336        ) -> Result<SecurityId, anyhow::Error> {
337            unreachable!();
338        }
339
340        fn compute_new_fs_node_sid_with_name(
341            &self,
342            _source_sid: SecurityId,
343            _target_sid: SecurityId,
344            _fs_node_class: FsNodeClass,
345            _fs_node_name: NullessByteStr<'_>,
346        ) -> Option<SecurityId> {
347            unreachable!();
348        }
349
350        fn compute_xperms_access_decision(
351            &self,
352            _xperms_kind: XpermsKind,
353            _source_sid: SecurityId,
354            _target_sid: SecurityId,
355            _permission: KernelPermission,
356            _xperms_prefix: u8,
357        ) -> KernelXpermsAccessDecision {
358            KernelXpermsAccessDecision {
359                allow: XpermsBitmap::NONE,
360                audit: XpermsBitmap::ALL,
361                permissive: false,
362                has_todo: false,
363            }
364        }
365    }
366
367    impl AccessVectorComputer for DenyAllPermissions {
368        fn access_decision_to_kernel_access_decision(
369            &self,
370            class: KernelClass,
371            av: AccessDecision,
372        ) -> KernelAccessDecision {
373            access_decision_to_kernel_access_decision(class, av)
374        }
375    }
376
377    /// A [`Query`] that permits all [`AccessVector`].
378    #[derive(Default)]
379    struct AllowAllPermissions;
380
381    impl Query for AllowAllPermissions {
382        fn compute_access_decision(
383            &self,
384            _source_sid: SecurityId,
385            _target_sid: SecurityId,
386            _target_class: KernelClass,
387        ) -> KernelAccessDecision {
388            KernelAccessDecision {
389                allow: AccessVector::ALL,
390                audit: AccessVector::NONE,
391                flags: 0,
392                todo_bug: None,
393            }
394        }
395
396        fn compute_create_sid(
397            &self,
398            _source_sid: SecurityId,
399            _target_sid: SecurityId,
400            _target_class: KernelClass,
401        ) -> Result<SecurityId, anyhow::Error> {
402            unreachable!();
403        }
404
405        fn compute_new_fs_node_sid_with_name(
406            &self,
407            _source_sid: SecurityId,
408            _target_sid: SecurityId,
409            _fs_node_class: FsNodeClass,
410            _fs_node_name: NullessByteStr<'_>,
411        ) -> Option<SecurityId> {
412            unreachable!();
413        }
414
415        fn compute_xperms_access_decision(
416            &self,
417            _xperms_kind: XpermsKind,
418            _source_sid: SecurityId,
419            _target_sid: SecurityId,
420            _permission: KernelPermission,
421            _xperms_prefix: u8,
422        ) -> KernelXpermsAccessDecision {
423            KernelXpermsAccessDecision {
424                allow: XpermsBitmap::ALL,
425                audit: XpermsBitmap::NONE,
426                permissive: false,
427                has_todo: false,
428            }
429        }
430    }
431
432    impl AccessVectorComputer for AllowAllPermissions {
433        fn access_decision_to_kernel_access_decision(
434            &self,
435            class: KernelClass,
436            av: AccessDecision,
437        ) -> KernelAccessDecision {
438            access_decision_to_kernel_access_decision(class, av)
439        }
440    }
441
442    #[test]
443    fn has_permission_both() {
444        let deny_all = DenyAllPermissions::default();
445        let allow_all = AllowAllPermissions::default();
446
447        // Use permissions that are mapped to access vector bits in
448        // `access_vector_from_permission`.
449        let permissions = [ProcessPermission::Fork, ProcessPermission::Transition];
450        for permission in permissions {
451            let local_cache1 = PerThreadCache::default();
452            // DenyAllPermissions denies.
453            let result = has_permission(
454                /*is_enforcing=*/ true,
455                &local_cache1,
456                &deny_all,
457                *A_TEST_SID,
458                *A_TEST_SID,
459                permission.into(),
460            );
461            assert_eq!(
462                result,
463                PermissionCheckResult {
464                    granted: false,
465                    audit: true,
466                    permissive: false,
467                    todo_bug: None
468                }
469            );
470            assert!(!result.permit());
471
472            let local_cache2 = PerThreadCache::default();
473            // AllowAllPermissions allows.
474            let result = has_permission(
475                /*is_enforcing=*/ true,
476                &local_cache2,
477                &allow_all,
478                *A_TEST_SID,
479                *A_TEST_SID,
480                permission.into(),
481            );
482            assert_eq!(
483                result,
484                PermissionCheckResult {
485                    granted: true,
486                    audit: false,
487                    permissive: false,
488                    todo_bug: None
489                }
490            );
491            assert!(result.permit());
492        }
493    }
494
495    #[test]
496    fn has_ioctl_permission_enforcing() {
497        let deny_all = DenyAllPermissions::default();
498        let allow_all = AllowAllPermissions::default();
499        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
500
501        // DenyAllPermissions denies.
502        let result = has_extended_permission(
503            /*is_enforcing=*/ true,
504            &deny_all,
505            XpermsKind::Ioctl,
506            *A_TEST_SID,
507            *A_TEST_SID,
508            permission.into(),
509            0xabcd,
510        );
511        assert_eq!(
512            result,
513            PermissionCheckResult {
514                granted: false,
515                audit: true,
516                permissive: false,
517                todo_bug: None
518            }
519        );
520        assert!(!result.permit());
521
522        // AllowAllPermissions allows.
523        let result = has_extended_permission(
524            /*is_enforcing=*/ true,
525            &allow_all,
526            XpermsKind::Ioctl,
527            *A_TEST_SID,
528            *A_TEST_SID,
529            permission.into(),
530            0xabcd,
531        );
532        assert_eq!(
533            result,
534            PermissionCheckResult {
535                granted: true,
536                audit: false,
537                permissive: false,
538                todo_bug: None
539            }
540        );
541        assert!(result.permit());
542    }
543
544    #[test]
545    fn has_ioctl_permission_not_enforcing() {
546        let deny_all = DenyAllPermissions::default();
547        let permission = CommonFsNodePermission::Ioctl.for_class(FileClass::File);
548
549        // DenyAllPermissions denies, but the permission is allowed when the security server
550        // is not in enforcing mode. The decision should still be audited.
551        let result = has_extended_permission(
552            /*is_enforcing=*/ false,
553            &deny_all,
554            XpermsKind::Ioctl,
555            *A_TEST_SID,
556            *A_TEST_SID,
557            permission,
558            0xabcd,
559        );
560        assert_eq!(
561            result,
562            PermissionCheckResult { granted: false, audit: true, permissive: true, todo_bug: None }
563        );
564        assert!(result.permit());
565    }
566}