Skip to main content

selinux/policy/
compute.rs

1// Copyright 2026 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 super::rules::{
6    ExtendedPermissions, HasRuleKey, RuleKind, XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES,
7    XPERMS_TYPE_IOCTL_PREFIXES, XPERMS_TYPE_NLMSG, XpermsBitmap,
8};
9use super::traits::{HasName, HasPolicyId};
10use super::{
11    AccessDecision, AccessVector, CategorySet, Class, ClassDefault, ClassDefaultRange, ClassId,
12    FsUseType, GenfsConPath, MlsLevel, OBJECT_R_ROLE_ID, PermissionId, Policy, PolicyCap, RoleId,
13    SELINUX_AVD_FLAGS_PERMISSIVE, SecurityContext, TypeId,
14};
15use crate::{InitialSid, NullessByteStr};
16
17/// Kind of extended permission, corresponding to the base permission that triggers a check of an
18/// extended permission.
19#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq)]
20pub enum XpermsKind {
21    Ioctl,
22    Nlmsg,
23}
24
25/// Result of an extended permissions calculation between source and target domains for a specific
26/// class, kind of extended permissions, and xperm prefix byte.
27///
28/// Decisions describe which 16-bit xperms are allowed, and whether xperms should be audit-logged
29/// when allowed and when denied.
30#[derive(Debug, Clone, PartialEq)]
31pub struct XpermsAccessDecision {
32    pub allow: XpermsBitmap,
33    pub auditallow: XpermsBitmap,
34    pub auditdeny: XpermsBitmap,
35}
36
37impl XpermsAccessDecision {
38    pub const DENY_ALL: Self = Self {
39        allow: XpermsBitmap::NONE,
40        auditallow: XpermsBitmap::NONE,
41        auditdeny: XpermsBitmap::ALL,
42    };
43    #[cfg(test)]
44    pub const ALLOW_ALL: Self = Self {
45        allow: XpermsBitmap::ALL,
46        auditallow: XpermsBitmap::NONE,
47        auditdeny: XpermsBitmap::ALL,
48    };
49}
50
51/// [`SecurityContext`] and [`FsUseType`] derived from an `fs_use_*` line of the policy.
52#[derive(Clone, Debug, Eq, PartialEq)]
53pub struct FsUseLabelAndType {
54    pub context: SecurityContext,
55    pub use_type: FsUseType,
56}
57
58impl Policy {
59    /// Returns true if the specified capability is in the policy's enabled capabilities set.
60    pub fn has_policycap(&self, policy_cap: PolicyCap) -> bool {
61        self.policy_capabilities().contains(policy_cap)
62    }
63
64    /// Returns true if `bounded` is bounded by `parent` in the policy.
65    pub fn is_bounded_by(&self, bounded: TypeId, parent: TypeId) -> bool {
66        self.types().get_by_id(bounded).unwrap().bounded_by() == Some(parent)
67    }
68
69    /// Returns true if the policy has marked the type/domain for permissive checks.
70    pub fn is_permissive(&self, type_: TypeId) -> bool {
71        self.permissive_map().contains(type_)
72    }
73
74    /// Returns the [`RoleId`] of the `"object_r"` role within the policy, for use when validating
75    /// and computing Security Context fields.
76    pub(super) fn object_role(&self) -> RoleId {
77        OBJECT_R_ROLE_ID
78    }
79
80    /// Returns the [`SecurityContext`] defined by this policy for the specified [`InitialSid`].
81    ///
82    /// # Panics
83    ///
84    /// Panics if [`Self::validate`] has not verified that all required initial SIDs are present.
85    pub fn initial_context(&self, mut id: InitialSid) -> &SecurityContext {
86        let need_init_sid = self.has_policycap(PolicyCap::UserspaceInitialContext);
87        if id == InitialSid::Init && !need_init_sid {
88            id = InitialSid::Kernel;
89        }
90        self.initial_sids()
91            .get_by_id(id as u32)
92            .expect("initial SID must be present in validated policy")
93    }
94
95    /// Returns the set of permissions for the given class, including both the explicitly owned
96    /// permissions and the inherited ones from common symbols. Each permission is a tuple of the
97    /// [`PermissionId`] (in the scope of the given class) and the permission name.
98    pub fn find_class_permissions_by_name(
99        &self,
100        class_name: &str,
101    ) -> Result<Vec<(PermissionId, Vec<u8>)>, ()> {
102        let classes = self.classes();
103        let class = classes.get_by_name(class_name.as_bytes()).ok_or(())?;
104        let owned_permissions = class.permissions();
105
106        let mut result: Vec<_> = owned_permissions
107            .iter()
108            .map(|permission| (permission.id(), permission.name().to_vec()))
109            .collect();
110
111        // common_name() is empty when the class doesn't inherit from a CommonSymbol.
112        if class.common_name().is_empty() {
113            return Ok(result);
114        }
115
116        let common_symbol_permissions =
117            self.common_symbols().get_by_name(class.common_name()).ok_or(())?.permissions();
118
119        result.extend(
120            common_symbol_permissions
121                .iter()
122                .map(|permission| (permission.id(), permission.name().to_vec())),
123        );
124
125        Ok(result)
126    }
127
128    /// Returns the [`PermissionId`] of the specified permission for the specified security
129    /// [`Class`], looking up the permission in the class's common symbol, if any.
130    pub fn find_permission_id_by_name(&self, class: &Class, name: &str) -> Option<PermissionId> {
131        let name = name.as_bytes();
132        if let Some(permission) = class.permissions().get_by_name(name) {
133            return Some(permission.id());
134        }
135        let common_name = class.common_name();
136        if !common_name.is_empty() {
137            let common_symbol = self.common_symbols().get_by_name(common_name)?;
138            let permission = common_symbol.permissions().get_by_name(name)?;
139            return Some(permission.id());
140        }
141        None
142    }
143
144    /// If there is an `fs_use` statement for the given filesystem type, returns the associated
145    /// [`SecurityContext`] and [`FsUseType`].
146    pub fn fs_use_label_and_type(&self, fs_type: NullessByteStr<'_>) -> Option<FsUseLabelAndType> {
147        self.object_contexts()
148            .fs_uses()
149            .iter()
150            .find(|fs_use| fs_use.fs_type() == fs_type.as_bytes())
151            .map(|fs_use| FsUseLabelAndType {
152                context: fs_use.context().clone(),
153                use_type: fs_use.behavior(),
154            })
155    }
156
157    /// Returns an iterator over all `genfscon` [`GenfsConPath`] entries for the given filesystem
158    /// type.
159    fn genfscon_find_all<'a>(
160        &'a self,
161        fs_type: &'a [u8],
162    ) -> impl Iterator<Item = &'a GenfsConPath> {
163        self.generic_fs_contexts()
164            .iter()
165            .filter(move |entry| entry.fs_type() == fs_type)
166            .flat_map(|entry| entry.paths().iter())
167    }
168
169    /// If there is a `genfscon` statement for the given filesystem type, returns the associated
170    /// [`SecurityContext`], taking the `node_path` into account. `class` defines the type of
171    /// the file in the given `node_path`. It can only be omitted when looking up the filesystem
172    /// label.
173    pub fn genfscon_label_for_fs_and_path(
174        &self,
175        fs_type: NullessByteStr<'_>,
176        node_path: NullessByteStr<'_>,
177        class: Option<ClassId>,
178    ) -> Option<SecurityContext> {
179        // All contexts listed in the policy for the file system type.
180        let fs_contexts = self.genfscon_find_all(fs_type.as_bytes());
181
182        #[derive(PartialEq)]
183        enum OrderType {
184            Alphabetic,
185            ByLength,
186            Unknown,
187        }
188        // The correct match is the closest parent among the ones given in the policy file.
189        // E.g. if in the policy we have
190        //     genfscon foofs "/" label1
191        //     genfscon foofs "/abc/" label2
192        //     genfscon foofs "/abc/def" label3
193        //
194        // The correct label for a file "/abc/def/g/h/i" is label3, as "/abc/def" is the closest parent
195        // among those defined.
196        //
197        // Partial paths are prefix-matched, so that "/abc/default" would also be assigned label3.
198        //
199        // TODO(372212126): Optimize the algorithm.
200        let mut result: Option<&GenfsConPath> = None;
201        let mut order_type = OrderType::Unknown;
202        let mut prev_path_bytes: Option<Vec<u8>> = None;
203        for fs_context in fs_contexts {
204            // Determine the order type based on the first entries.
205            let path = fs_context.partial_path();
206            if order_type == OrderType::Unknown {
207                if let Some(prev) = &prev_path_bytes {
208                    if path.len() > prev.len() {
209                        order_type = OrderType::Alphabetic;
210                    } else if path < prev.as_slice() {
211                        order_type = OrderType::ByLength;
212                    }
213                }
214                prev_path_bytes = Some(path.to_vec());
215            }
216
217            // Check if the class matches.
218            let class_matches = class.is_none()
219                || fs_context.class().map(|other| other == class.unwrap()).unwrap_or(true);
220            if !class_matches {
221                continue;
222            }
223
224            if order_type == OrderType::Alphabetic
225                && fs_context.partial_path() > node_path.as_bytes()
226            {
227                // We know that:
228                // - We have alphabetic order,
229                // - The current path is lexicographically greater than our target path.
230                // We can infer that we have passed any potential prefixes in alphabetical order.
231                break;
232            }
233
234            if node_path.as_bytes().starts_with(fs_context.partial_path()) {
235                if result
236                    .as_ref()
237                    .map_or(true, |c| c.partial_path().len() < fs_context.partial_path().len())
238                {
239                    // The path matches, and it's the closest parent so far.
240                    result = Some(fs_context);
241                    if order_type == OrderType::ByLength {
242                        break;
243                    }
244                }
245            }
246        }
247
248        // The returned SecurityContext must be valid with respect to the policy, since otherwise
249        // we'd have rejected the policy load.
250        Some(result?.context().clone())
251    }
252
253    /// Returns the [`SecurityContext`] that should be applied to a newly created SELinux object
254    /// according to `source` and `target`, as well as the new object's `class` and `name`.
255    ///
256    /// Computation follows the "create" algorithm for labeling newly created objects:
257    /// - user is taken from the `source` by default, or `target` if specified by policy.
258    /// - role, type and range are taken from the matching transition rules, if any.
259    /// - role, type and range fall-back to the `source` or `target` values according to policy.
260    ///
261    /// Callers pass an empty slice (`&[]`) for `name` to express nameless transitions.
262    /// When a non-empty `name` is provided, filename transition rules are checked first.
263    /// If no transitions apply, and the policy does not explicitly specify defaults then the
264    /// role, type and range values have defaults chosen based on the `class`:
265    /// - For "process", and socket-like classes, role, type and range are taken from the `source`.
266    /// - Otherwise role is "object_r", type is taken from `target` and range is set to the low
267    ///   level of the `source` range.
268    pub fn compute_create_context(
269        &self,
270        source: &SecurityContext,
271        target: &SecurityContext,
272        class: Option<&Class>,
273        name: &[u8],
274    ) -> SecurityContext {
275        let override_type = if !name.is_empty() {
276            class.and_then(|class| {
277                self.filename_transitions().compute_filename_transition(
278                    source.type_(),
279                    target.type_(),
280                    class.id(),
281                    name,
282                )
283            })
284        } else {
285            None
286        };
287        self.compute_create_context_internal(source, target, class, override_type)
288    }
289
290    /// Internal implementation used by [`Self::compute_create_context`] to implement the policy
291    /// transition calculations. If `override_type` is specified then the supplied value will be
292    /// applied rather than a value being calculated based on the policy; this is used by
293    /// [`Self::compute_create_context`] when a filename transition matches to shortcut the default
294    /// `type_transition` lookup.
295    fn compute_create_context_internal(
296        &self,
297        source: &SecurityContext,
298        target: &SecurityContext,
299        class: Option<&Class>,
300        override_type: Option<TypeId>,
301    ) -> SecurityContext {
302        let Some(class) = class else {
303            // If the class is not defined in the policy then there can be no transitions, nor
304            // class-defined choice of defaults, so default to the non-process-or-socket behaviour.
305            return SecurityContext::new(
306                source.user(),
307                self.object_role(),
308                target.type_(),
309                source.low_level().clone(),
310                None,
311            );
312        };
313
314        let is_process_or_socket = class.name() == b"process" || class.common_name() == b"socket";
315        let (unspecified_role, unspecified_type, unspecified_low, unspecified_high) =
316            if is_process_or_socket {
317                (source.role(), source.type_(), source.low_level(), source.high_level())
318            } else {
319                (self.object_role(), target.type_(), source.low_level(), None)
320            };
321        let defaults = class.defaults();
322
323        let user = match defaults.user() {
324            ClassDefault::Source => source.user(),
325            ClassDefault::Target => target.user(),
326            ClassDefault::Unspecified => source.user(),
327        };
328
329        let role = match self.role_transition_new_role(source.role(), target.type_(), class) {
330            Some(new_role) => new_role,
331            None => match defaults.role() {
332                ClassDefault::Source => source.role(),
333                ClassDefault::Target => target.role(),
334                ClassDefault::Unspecified => unspecified_role,
335            },
336        };
337
338        let type_ = override_type.unwrap_or_else(|| {
339            let transition = self
340                .access_vector_rules()
341                .find_type_rules(source.type_(), target.type_(), class.id())
342                .find(|rule| rule.kind() == RuleKind::TypeTransition)
343                .map(|rule| rule.new_type());
344            match transition {
345                Some(new_type) => new_type,
346                None => match defaults.type_() {
347                    ClassDefault::Source => source.type_(),
348                    ClassDefault::Target => target.type_(),
349                    ClassDefault::Unspecified => unspecified_type,
350                },
351            }
352        });
353
354        let (low_level, high_level) =
355            match self.range_transition_new_range(source.type_(), target.type_(), class) {
356                Some((low_level, high_level)) => (low_level, high_level),
357                None => match defaults.range() {
358                    ClassDefaultRange::SourceLow => (source.low_level().clone(), None),
359                    ClassDefaultRange::SourceHigh => (source.effective_high_level().clone(), None),
360                    ClassDefaultRange::SourceLowHigh => {
361                        (source.low_level().clone(), source.high_level().cloned())
362                    }
363                    ClassDefaultRange::TargetLow => (target.low_level().clone(), None),
364                    ClassDefaultRange::TargetHigh => (target.effective_high_level().clone(), None),
365                    ClassDefaultRange::TargetLowHigh => {
366                        (target.low_level().clone(), target.high_level().cloned())
367                    }
368                    ClassDefaultRange::Unspecified => {
369                        (unspecified_low.clone(), unspecified_high.cloned())
370                    }
371                    ClassDefaultRange::Glblub => glblub_range(source, target),
372                },
373            };
374
375        // TODO(http://b/334968228): Validate domain & role transitions are allowed?
376        SecurityContext::new(user, role, type_, low_level, high_level)
377    }
378
379    /// Computes the access granted to `source` on `target`, for the specified `class`. The result
380    /// is a set of access vectors with bits set for each `class` permission, describing which
381    /// permissions are allowed, and which should have access checks audit-logged when denied, or
382    /// allowed.
383    ///
384    /// An [`AccessDecision`] is accumulated, starting from no permissions to be granted,
385    /// nor audit-logged if allowed, and all permissions to be audit-logged if denied.
386    /// Permissions that are explicitly `allow`ed, but that are subject to unsatisfied
387    /// constraints, are removed from the allowed set.
388    pub fn compute_access_decision(
389        &self,
390        source: &SecurityContext,
391        target: &SecurityContext,
392        class: &Class,
393    ) -> AccessDecision {
394        let mut access_decision =
395            self.compute_explicitly_allowed(source.type_(), target.type_(), class);
396        access_decision.allow -= self.compute_denied_by_constraints(source, target, class);
397        access_decision
398    }
399
400    /// Computes the access granted to `source` on `target`, for the specified `class`. The result
401    /// is a set of access vectors with bits set for each `class` permission, describing which
402    /// permissions are explicitly allowed, and which should have access checks audit-logged when
403    /// denied, or allowed.
404    pub(super) fn compute_explicitly_allowed(
405        &self,
406        source: TypeId,
407        target: TypeId,
408        class: &Class,
409    ) -> AccessDecision {
410        let mut computed_access_vector = AccessVector::NONE;
411        let mut computed_audit_allow = AccessVector::NONE;
412        let mut computed_audit_deny = AccessVector::ALL;
413
414        let source_attributes = &self.type_attribute_maps()[source];
415        let target_attributes = &self.type_attribute_maps()[target];
416
417        for source in source_attributes.iter() {
418            for target in target_attributes.iter() {
419                for rule in self.access_vector_rules().find_av_rules(source, target, class.id()) {
420                    match rule.kind() {
421                        RuleKind::Allow => computed_access_vector |= rule.access_vector(),
422                        RuleKind::AuditAllow => computed_audit_allow |= rule.access_vector(),
423                        RuleKind::DontAudit => computed_audit_deny &= rule.access_vector(),
424                        _ => {}
425                    }
426                }
427            }
428        }
429
430        // If `source` is bounded by some `parent` then bound the allowed permissions to those
431        // available to the parent. Doing the calculation here ensures that type-bounds take into
432        // account bounding ancestors, if any.
433        if let Some(parent) = self.types().get_by_id(source).unwrap().bounded_by() {
434            // If `source == target` then this is a "self" permission check, which should be
435            // bounded to the parent domain's "self" permissions.
436            let access = if source == target {
437                self.compute_explicitly_allowed(parent, parent, class)
438            } else {
439                self.compute_explicitly_allowed(parent, target, class)
440            };
441            computed_access_vector &= access.allow;
442        }
443
444        let mut flags = 0;
445        if self.permissive_map().contains(source) {
446            flags |= SELINUX_AVD_FLAGS_PERMISSIVE;
447        }
448        AccessDecision {
449            allow: computed_access_vector,
450            auditallow: computed_audit_allow,
451            auditdeny: computed_audit_deny,
452            flags,
453            todo_bug: None,
454        }
455    }
456
457    /// Permissions are denied if they match at least one unsatisfied constraint.
458    fn compute_denied_by_constraints(
459        &self,
460        source: &SecurityContext,
461        target: &SecurityContext,
462        class: &Class,
463    ) -> AccessVector {
464        let mut denied = AccessVector::NONE;
465        for constraint in class.constraints() {
466            if !constraint.evaluate(source, target) {
467                denied |= constraint.access_vector();
468            }
469        }
470        denied
471    }
472
473    /// Computes the access decision for a set of extended permissions of a given `kind` and with a
474    /// given `prefix` byte, for a particular `source` and `target` context and `class`.
475    pub fn compute_xperms_access_decision(
476        &self,
477        kind: XpermsKind,
478        source: &SecurityContext,
479        target: &SecurityContext,
480        class: &Class,
481        prefix: u8,
482    ) -> XpermsAccessDecision {
483        let mut explicit_allow: Option<XpermsBitmap> = None;
484        let mut auditallow = XpermsBitmap::NONE;
485        let mut auditdeny = XpermsBitmap::ALL;
486
487        let xperms_types = match kind {
488            XpermsKind::Ioctl => {
489                [XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES, XPERMS_TYPE_IOCTL_PREFIXES].as_slice()
490            }
491            XpermsKind::Nlmsg => [XPERMS_TYPE_NLMSG].as_slice(),
492        };
493        let bitmap_if_prefix_matches = |prefix: u8, xperms: &ExtendedPermissions| match kind {
494            XpermsKind::Ioctl => match xperms.xperms_type() {
495                XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES => {
496                    (xperms.xperms_optional_prefix() == prefix).then_some(*xperms.xperms_bitmap())
497                }
498                XPERMS_TYPE_IOCTL_PREFIXES => {
499                    xperms.xperms_bitmap().contains(prefix).then_some(XpermsBitmap::ALL)
500                }
501                _ => None,
502            },
503            XpermsKind::Nlmsg => match xperms.xperms_type() {
504                XPERMS_TYPE_NLMSG => {
505                    (xperms.xperms_optional_prefix() == prefix).then_some(*xperms.xperms_bitmap())
506                }
507                _ => None,
508            },
509        };
510
511        let source_attributes = &self.type_attribute_maps()[source.type_()];
512        let target_attributes = &self.type_attribute_maps()[target.type_()];
513
514        for source in source_attributes.iter() {
515            for target in target_attributes.iter() {
516                for rule in self.access_vector_rules().find_xperm_rules(source, target, class.id())
517                {
518                    let xperms = rule.extended_permissions();
519                    if rule.kind() == RuleKind::AllowXperm
520                        && xperms_types.contains(&xperms.xperms_type())
521                    {
522                        explicit_allow.get_or_insert(XpermsBitmap::NONE);
523                    }
524
525                    if let Some(xperms_bitmap) = bitmap_if_prefix_matches(prefix, xperms) {
526                        match rule.kind() {
527                            RuleKind::AllowXperm => {
528                                (*explicit_allow.get_or_insert(XpermsBitmap::NONE)) |=
529                                    xperms_bitmap;
530                            }
531                            RuleKind::AuditAllowXperm => auditallow |= xperms_bitmap,
532                            RuleKind::DontAuditXperm => auditdeny -= xperms_bitmap,
533                            _ => {}
534                        }
535                    }
536                }
537            }
538        }
539        let allow = explicit_allow.unwrap_or(XpermsBitmap::ALL);
540        XpermsAccessDecision { allow, auditallow, auditdeny }
541    }
542
543    fn role_transition_new_role(
544        &self,
545        current_role: RoleId,
546        type_: TypeId,
547        class: &Class,
548    ) -> Option<RoleId> {
549        self.role_transitions()
550            .iter()
551            .find(|role_transition| {
552                role_transition.current_role() == current_role
553                    && role_transition.type_() == type_
554                    && role_transition.class() == class.id()
555            })
556            .map(|x| x.new_role())
557    }
558
559    /// Returns whether a transition from `source` to `new` is explicitly authorized by a role allow
560    /// rule (`allow <source> <new>;`).
561    pub fn role_transition_is_explicitly_allowed(&self, source: RoleId, new: RoleId) -> bool {
562        self.role_allowlist()
563            .iter()
564            .any(|role_allow| role_allow.source_role() == source && role_allow.new_role() == new)
565    }
566
567    fn range_transition_new_range(
568        &self,
569        source: TypeId,
570        target: TypeId,
571        class: &Class,
572    ) -> Option<(MlsLevel, Option<MlsLevel>)> {
573        for range_transition in self.range_transitions() {
574            if range_transition.source_type() == source
575                && range_transition.target_type() == target
576                && range_transition.target_class() == class.id()
577            {
578                let mls_range = range_transition.mls_range();
579                let low_level = mls_range.low().clone();
580                let high_level = mls_range.high().cloned();
581                return Some((low_level, high_level));
582            }
583        }
584
585        None
586    }
587}
588
589/// Returns the set of categories present in both `left` and `right`.
590fn intersect_categories(left: &MlsLevel, right: &MlsLevel) -> CategorySet {
591    let right_categories = right.categories();
592    CategorySet::from_ids(left.categories().iter().filter(|id| right_categories.contains(*id)))
593}
594
595/// Returns the intersection of the `source` and `target` ranges.
596///
597/// If the two ranges do not overlap then the intersection is empty, and the returned range is
598/// mis-ordered, i.e. its high level does not dominate its low level. Such a range is rejected
599/// when the resulting Security Context is validated.
600fn glblub_range(
601    source: &SecurityContext,
602    target: &SecurityContext,
603) -> (MlsLevel, Option<MlsLevel>) {
604    let source_low = source.low_level();
605    let source_high = source.effective_high_level();
606    let target_low = target.low_level();
607    let target_high = target.effective_high_level();
608
609    let low_level = MlsLevel::new(
610        std::cmp::max(source_low.sensitivity(), target_low.sensitivity()),
611        intersect_categories(source_low, target_low),
612    );
613    let high_level = MlsLevel::new(
614        std::cmp::min(source_high.sensitivity(), target_high.sensitivity()),
615        intersect_categories(source_high, target_high),
616    );
617    (low_level, Some(high_level))
618}