Skip to main content

selinux/policy/
index.rs

1// Copyright 2024 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::arrays::{FsContext, FsUseType};
6use super::security_context::SecurityContext;
7use super::{
8    AccessDecision, AccessVector, ClassId, MlsLevel, ParsedPolicy, PermissionId, RoleId, TypeId,
9};
10use crate::new_policy::rules::{HasRuleKey, RuleKind};
11use crate::new_policy::traits::{HasName, HasPolicyId};
12use crate::new_policy::{
13    Class, ClassDefault, ClassDefaultRange, CommonSymbol, HandleUnknown, IdAndNameIndexed,
14    SymbolArray,
15};
16use crate::{
17    ClassPermission as _, KernelClass, KernelPermission, NullessByteStr, PolicyCap,
18    ProcessPermission,
19};
20
21use std::collections::HashMap;
22use std::ops::Deref;
23
24use strum::VariantArray as _;
25
26/// The [`SecurityContext`] and [`FsUseType`] derived from some `fs_use_*` line of the policy.
27pub struct FsUseLabelAndType {
28    pub context: SecurityContext,
29    pub use_type: FsUseType,
30}
31
32/// Array of `PermissionId` values each of a kernel security class' permissions.
33type KernelPermissionIdsArray = [Option<PermissionId>; 32];
34
35/// An index for facilitating fast lookup of common abstractions inside parsed binary policy data
36/// structures. Typically, data is indexed by an enum that describes a well-known value and the
37/// index stores the offset of the data in the binary policy to avoid scanning a collection to find
38/// an element that contains a matching string. For example, the policy contains a collection of
39/// classes that are identified by string names included in each collection entry. However,
40/// `policy_index.classes(KernelClass::Process).unwrap()` yields the offset in the policy's
41/// collection of classes where the "process" class resides.
42#[derive(Debug)]
43pub struct PolicyIndex {
44    /// Map from [`KernelClass`]es to their corresponding [`ClassId`]s in the associated policy's
45    /// [`super::symbols::Classes`] collection.
46    classes: HashMap<KernelClass, ClassId>,
47    /// Index mapping kernel class permissions to their policy-specific `AccessVector` bit index.
48    permissions: [KernelPermissionIdsArray; KernelClass::VARIANTS.len()],
49    /// The parsed binary policy.
50    parsed_policy: ParsedPolicy,
51    /// The "object_r" role used as a fallback for new file context transitions.
52    cached_object_r_role: RoleId,
53    /// The cached `ClassId` for the "process" class, if defined by the policy.
54    cached_process_class: Option<ClassId>,
55}
56
57impl PolicyIndex {
58    /// Constructs a [`PolicyIndex`] that indexes over well-known policy elements.
59    ///
60    /// [`Class`]es and [`Permission`]s used by the kernel are amongst the indexed elements.
61    /// The policy's `handle_unknown()` configuration determines whether the policy can be loaded even
62    /// if it omits classes or permissions expected by the kernel, and whether to allow or deny those
63    /// permissions if so.
64    pub fn new(parsed_policy: ParsedPolicy) -> Result<Self, anyhow::Error> {
65        let policy_classes = parsed_policy.classes();
66        let common_symbols = parsed_policy.common_symbols();
67
68        let mut classes = HashMap::with_capacity(crate::KernelClass::VARIANTS.len());
69
70        // Insert elements for each kernel object class. If the policy defines that unknown
71        // kernel classes should cause rejection then return an error describing the missing
72        // element.
73        for known_class in crate::KernelClass::VARIANTS {
74            match policy_classes.get_by_name(known_class.name().as_bytes()) {
75                Some(class) => {
76                    classes.insert(*known_class, class.id());
77                }
78                None => {
79                    if parsed_policy.handle_unknown() == HandleUnknown::Reject {
80                        return Err(anyhow::anyhow!("missing object class {:?}", known_class,));
81                    }
82                }
83            }
84        }
85
86        // Allow unused space in the classes map to be released.
87        classes.shrink_to_fit();
88
89        // Accumulate permissions indexed by kernel permission enum. If the policy defines that
90        // unknown permissions or classes should cause rejection then return an error describing the
91        // missing element.
92        let mut permissions = [KernelPermissionIdsArray::default(); _];
93        for kernel_permission in crate::KernelPermission::all_variants() {
94            let kernel_class_name = kernel_permission.class().name();
95            if let Some(class) = policy_classes.get_by_name(kernel_class_name.as_bytes()) {
96                if let Some(permission_id) =
97                    get_permission_id_by_name(common_symbols, class, kernel_permission.name())
98                {
99                    let kernel_class_id = kernel_permission.class() as usize;
100                    let kernel_permission_id = kernel_permission.id() as usize;
101                    permissions[kernel_class_id][kernel_permission_id] = Some(permission_id);
102                } else if parsed_policy.handle_unknown() == HandleUnknown::Reject {
103                    return Err(anyhow::anyhow!(
104                        "missing permission {:?}:{:?}",
105                        kernel_class_name,
106                        kernel_permission.name(),
107                    ));
108                }
109            }
110        }
111
112        // Locate the "object_r" role.
113        let cached_object_r_role = parsed_policy
114            .roles()
115            .get_by_name(b"object_r")
116            .ok_or_else(|| anyhow::anyhow!("missing 'object_r' role"))?
117            .id();
118
119        let cached_process_class = classes.get(&KernelClass::Process).copied();
120
121        let index = Self {
122            classes,
123            permissions,
124            parsed_policy,
125            cached_object_r_role,
126            cached_process_class,
127        };
128
129        // Verify that the initial Security Contexts are all defined, and valid.
130        for initial_sids in crate::InitialSid::all_variants() {
131            index.resolve_initial_context(*initial_sids);
132        }
133
134        // Validate the contexts used in fs_use statements.
135        for fs_use in index.parsed_policy.fs_uses() {
136            SecurityContext::new_from_policy_context(fs_use.context());
137        }
138
139        Ok(index)
140    }
141
142    /// Returns the policy entry for a class identified either by its well-known kernel object class
143    /// enum value, or its policy-defined Id.
144    pub(super) fn class(&self, object_class: crate::ObjectClass) -> Option<&Class> {
145        match object_class {
146            crate::ObjectClass::Kernel(kernel_class) => {
147                let &class_id = self.classes.get(&kernel_class)?;
148                self.classes().get_by_id(class_id)
149            }
150            crate::ObjectClass::ClassId(class_id) => self.classes().get_by_id(class_id),
151        }
152    }
153
154    /// Returns the policy entry for a well-known kernel object class permission.
155    pub fn kernel_permission_to_access_vector<P: Into<KernelPermission>>(
156        &self,
157        permission: P,
158    ) -> Option<AccessVector> {
159        let permission = permission.into();
160        let class_index = permission.class() as usize;
161        let permission_index = permission.id() as usize;
162        let permission_id = self.permissions[class_index][permission_index]?;
163        Some(permission_id.into())
164    }
165
166    /// Returns the security context that should be applied to a newly created SELinux
167    /// object according to `source` and `target` security contexts, as well as the new object's
168    /// `class`.
169    ///
170    /// If no filename-transition rule matches the supplied arguments then `None` is returned, and
171    /// the caller should fall-back to filename-independent labeling via
172    /// [`compute_create_context()`]
173    pub fn compute_create_context_with_name(
174        &self,
175        source: &SecurityContext,
176        target: &SecurityContext,
177        class: crate::ObjectClass,
178        name: NullessByteStr<'_>,
179    ) -> Option<SecurityContext> {
180        let policy_class = self.class(class)?;
181        let type_id = self.type_transition_new_type_with_name(
182            source.type_(),
183            target.type_(),
184            &policy_class,
185            name,
186        )?;
187        Some(self.new_security_context_internal(
188            source,
189            target,
190            class,
191            // Override the "type" with the value specified by the filename-transition rules.
192            Some(type_id),
193        ))
194    }
195
196    /// Returns the security context that should be applied to a newly created SELinux
197    /// object according to `source` and `target` security contexts, as well as the new object's
198    /// `class`.
199    ///
200    /// Computation follows the "create" algorithm for labeling newly created objects:
201    /// - user is taken from the `source`.
202    /// - role, type and range are taken from the matching transition rules, if any.
203    /// - role, type and range fall-back to the `source` or `target` values according to policy.
204    ///
205    /// If no transitions apply, and the policy does not explicitly specify defaults then the
206    /// role, type and range values have defaults chosen based on the `class`:
207    /// - For "process", and socket-like classes, role, type and range are taken from the `source`.
208    /// - Otherwise role is "object_r", type is taken from `target` and range is set to the
209    ///   low level of the `source` range.
210    pub fn compute_create_context(
211        &self,
212        source: &SecurityContext,
213        target: &SecurityContext,
214        class: crate::ObjectClass,
215    ) -> SecurityContext {
216        self.new_security_context_internal(source, target, class, None)
217    }
218
219    /// Internal implementation used by `compute_create_context_with_name()` and
220    /// `compute_create_context()` to implement the policy transition calculations.
221    /// If `override_type` is specified then the supplied value will be applied rather than a value
222    /// being calculated based on the policy; this is used by `compute_create_context_with_name()`
223    /// to shortcut the default `type_transition` lookup.
224    fn new_security_context_internal(
225        &self,
226        source: &SecurityContext,
227        target: &SecurityContext,
228        target_class: crate::ObjectClass,
229        override_type: Option<TypeId>,
230    ) -> SecurityContext {
231        let Some(policy_class) = self.class(target_class) else {
232            // If the class is not defined in the policy then there can be no transitions, nor
233            // class-defined choice of defaults, so default to the non-process-or-socket behaviour.
234            // TODO: https://fxbug.dev/361552580 - For `KernelClass`es, apply the kernel's notion
235            // of whether the class is "process", or socket-like?
236            return SecurityContext::new(
237                source.user(),
238                self.cached_object_r_role,
239                target.type_(),
240                source.low_level().clone(),
241                None,
242            );
243        };
244
245        let is_process_or_socket =
246            policy_class.name() == b"process" || policy_class.common_name() == b"socket";
247        let (unspecified_role, unspecified_type, unspecified_low, unspecified_high) =
248            if is_process_or_socket {
249                (source.role(), source.type_(), source.low_level(), source.high_level())
250            } else {
251                (self.cached_object_r_role, target.type_(), source.low_level(), None)
252            };
253        let class_defaults = policy_class.defaults();
254
255        let user = match class_defaults.user() {
256            ClassDefault::Source => source.user(),
257            ClassDefault::Target => target.user(),
258            ClassDefault::Unspecified => source.user(),
259        };
260
261        let role = match self.role_transition_new_role(source.role(), target.type_(), &policy_class)
262        {
263            Some(new_role) => new_role,
264            None => match class_defaults.role() {
265                ClassDefault::Source => source.role(),
266                ClassDefault::Target => target.role(),
267                ClassDefault::Unspecified => unspecified_role,
268            },
269        };
270
271        let type_ = override_type.unwrap_or_else(|| {
272            let transition = self
273                .parsed_policy
274                .access_vector_rules()
275                .find_type_rules(source.type_(), target.type_(), policy_class.id())
276                .find(|rule| rule.kind() == RuleKind::TypeTransition)
277                .map(|rule| rule.new_type());
278            match transition {
279                Some(new_type) => new_type,
280                None => match class_defaults.type_() {
281                    ClassDefault::Source => source.type_(),
282                    ClassDefault::Target => target.type_(),
283                    ClassDefault::Unspecified => unspecified_type,
284                },
285            }
286        });
287
288        let (low_level, high_level) =
289            match self.range_transition_new_range(source.type_(), target.type_(), &policy_class) {
290                Some((low_level, high_level)) => (low_level, high_level),
291                None => match class_defaults.range() {
292                    ClassDefaultRange::SourceLow => (source.low_level().clone(), None),
293                    ClassDefaultRange::SourceHigh => {
294                        (source.high_level().unwrap_or_else(|| source.low_level()).clone(), None)
295                    }
296                    ClassDefaultRange::SourceLowHigh => {
297                        (source.low_level().clone(), source.high_level().cloned())
298                    }
299                    ClassDefaultRange::TargetLow => (target.low_level().clone(), None),
300                    ClassDefaultRange::TargetHigh => {
301                        (target.high_level().unwrap_or_else(|| target.low_level()).clone(), None)
302                    }
303                    ClassDefaultRange::TargetLowHigh => {
304                        (target.low_level().clone(), target.high_level().cloned())
305                    }
306                    ClassDefaultRange::Unspecified => {
307                        (unspecified_low.clone(), unspecified_high.cloned())
308                    }
309                    ClassDefaultRange::UnknownUsedValue => {
310                        unreachable!("Invalid ClassDefaultRange in validated policy")
311                    }
312                },
313            };
314
315        // TODO(http://b/334968228): Validate domain & role transitions are allowed?
316        SecurityContext::new(user, role, type_, low_level, high_level)
317    }
318
319    /// Evaluates the access rights allowed, and whether an audit should be emitted for any allowed
320    /// or denied permissions, by `source_context` acting on `target_context` as `target_class`.
321    pub(super) fn compute_access_decision(
322        &self,
323        source_context: &SecurityContext,
324        target_context: &SecurityContext,
325        target_class: &Class,
326    ) -> AccessDecision {
327        let mut access_decision = self.parsed_policy.compute_access_decision(
328            source_context,
329            target_context,
330            target_class,
331        );
332
333        // Process domain transitions ("transition" and "dyntransition") across different roles
334        // require explicit authorization in policy via a role allow rule ("allow old_role new_role;").
335        if source_context.role() != target_context.role()
336            && Some(target_class.id()) == self.cached_process_class
337        {
338            let process_trans_perms = self.process_trans_perms();
339            if (access_decision.allow & process_trans_perms) != AccessVector::NONE
340                && !self.role_transition_is_explicitly_allowed(
341                    source_context.role(),
342                    target_context.role(),
343                )
344            {
345                // The source is granted one or both of the "transition" permissions, but the role
346                // transition is not explicitly allowed, so remove those permissions from the
347                // returned decision.
348                access_decision.allow -= process_trans_perms;
349            }
350        }
351
352        access_decision
353    }
354
355    /// Returns the combined permissions mask for process `transition` and `dyntransition`.
356    fn process_trans_perms(&self) -> AccessVector {
357        let mut perms = self
358            .kernel_permission_to_access_vector(ProcessPermission::Transition)
359            .unwrap_or(AccessVector::NONE);
360        perms |= self
361            .kernel_permission_to_access_vector(ProcessPermission::DynTransition)
362            .unwrap_or(AccessVector::NONE);
363        perms
364    }
365
366    /// Returns the Id of the "object_r" role within the `parsed_policy`, for use when validating
367    /// Security Context fields.
368    pub(super) fn object_role(&self) -> RoleId {
369        self.cached_object_r_role
370    }
371
372    /// Returns the [`SecurityContext`] defined by this policy for the specified
373    /// well-known (or "initial") Id.
374    pub(super) fn initial_context(&self, id: crate::InitialSid) -> SecurityContext {
375        // All [`InitialSid`] have already been verified as resolvable, by `new()`.
376        self.resolve_initial_context(id)
377    }
378
379    /// If there is an fs_use statement for the given filesystem type, returns the associated
380    /// [`SecurityContext`] and [`FsUseType`].
381    pub(super) fn fs_use_label_and_type(
382        &self,
383        fs_type: NullessByteStr<'_>,
384    ) -> Option<FsUseLabelAndType> {
385        self.parsed_policy
386            .fs_uses()
387            .iter()
388            .find(|fs_use| fs_use.fs_type() == fs_type.as_bytes())
389            .map(|fs_use| FsUseLabelAndType {
390                context: SecurityContext::new_from_policy_context(fs_use.context()),
391                use_type: fs_use.behavior(),
392            })
393    }
394
395    /// If there is a genfscon statement for the given filesystem type, returns the associated
396    /// [`SecurityContext`], taking the `node_path` into account. `class_id` defines the type
397    /// of the file in the given `node_path`. It can only be omitted when looking up the filesystem
398    /// label.
399    pub(super) fn genfscon_label_for_fs_and_path(
400        &self,
401        fs_type: NullessByteStr<'_>,
402        node_path: NullessByteStr<'_>,
403        class: Option<crate::KernelClass>,
404    ) -> Option<SecurityContext> {
405        let node_path = if class == Some(crate::FileClass::LnkFile.into())
406            && !self.parsed_policy.has_policycap(PolicyCap::GenfsSeclabelSymlinks)
407        {
408            // Symlinks receive the filesystem root label by default, rather than a label dependent on
409            // the `node_path`. Path based labels may be enabled with the "genfs_seclabel_symlinks"
410            // policy capability.
411            "/".into()
412        } else {
413            node_path
414        };
415
416        let class_id = class.and_then(|class| self.class(class.into())).map(|class| class.id());
417
418        // All contexts listed in the policy for the file system type.
419        let fs_contexts = self
420            .parsed_policy
421            .genfscon_find_all(std::str::from_utf8(fs_type.as_bytes()).expect("fs type is valid"));
422
423        #[derive(PartialEq)]
424        enum OrderType {
425            Alphabetic,
426            ByLength,
427            Unknown,
428        }
429        // The correct match is the closest parent among the ones given in the policy file.
430        // E.g. if in the policy we have
431        //     genfscon foofs "/" label1
432        //     genfscon foofs "/abc/" label2
433        //     genfscon foofs "/abc/def" label3
434        //
435        // The correct label for a file "/abc/def/g/h/i" is label3, as "/abc/def" is the closest parent
436        // among those defined.
437        //
438        // Partial paths are prefix-matched, so that "/abc/default" would also be assigned label3.
439        //
440        // TODO(372212126): Optimize the algorithm.
441        let mut result: Option<FsContext> = None;
442        let mut order_type = OrderType::Unknown;
443        let mut prev_path_bytes: Option<Vec<u8>> = None;
444        for fs_context in fs_contexts {
445            // Determine the order type based on the first entries.
446            let path = fs_context.partial_path();
447            if order_type == OrderType::Unknown {
448                if let Some(prev) = &prev_path_bytes {
449                    if path.len() > prev.len() {
450                        order_type = OrderType::Alphabetic;
451                    } else if path < prev.as_slice() {
452                        order_type = OrderType::ByLength;
453                    }
454                }
455                prev_path_bytes = Some(path.to_vec());
456            }
457
458            // Check if the class matches.
459            let class_matches = class_id.is_none()
460                || fs_context
461                    .class()
462                    .map(|other| other == class_id.unwrap().into())
463                    .unwrap_or(true);
464            if !class_matches {
465                continue;
466            }
467
468            if order_type == OrderType::Alphabetic && fs_context.partial_path() > node_path.0 {
469                // We know that:
470                // - We have alphabetic order,
471                // - The current path is lexicographically greater than our target path.
472                // We can infer that we have passed any potential prefixes in alphabetical order.
473                break;
474            }
475
476            if node_path.0.starts_with(fs_context.partial_path()) {
477                if result
478                    .as_ref()
479                    .map_or(true, |c| c.partial_path().len() < fs_context.partial_path().len())
480                {
481                    // The path matches, and it's the closest parent so far.
482                    result = Some(fs_context);
483                    if order_type == OrderType::ByLength {
484                        break;
485                    }
486                }
487            }
488        }
489
490        // The returned SecurityContext must be valid with respect to the policy, since otherwise
491        // we'd have rejected the policy load.
492        result.and_then(|fs_context| {
493            Some(SecurityContext::new_from_policy_context(fs_context.context()))
494        })
495    }
496
497    /// Helper used to construct and validate well-known [`SecurityContext`] values.
498    fn resolve_initial_context(&self, id: crate::InitialSid) -> SecurityContext {
499        SecurityContext::new_from_policy_context(self.parsed_policy.initial_context(id))
500    }
501
502    fn role_transition_new_role(
503        &self,
504        current_role: RoleId,
505        type_: TypeId,
506        class: &Class,
507    ) -> Option<RoleId> {
508        self.parsed_policy
509            .role_transitions()
510            .iter()
511            .find(|role_transition| {
512                role_transition.current_role() == current_role
513                    && role_transition.type_() == type_
514                    && role_transition.class() == class.id().into()
515            })
516            .map(|x| x.new_role())
517    }
518
519    fn role_transition_is_explicitly_allowed(&self, source_role: RoleId, new_role: RoleId) -> bool {
520        self.parsed_policy.role_allowlist().iter().any(|role_allow| {
521            role_allow.source_role() == source_role && role_allow.new_role() == new_role
522        })
523    }
524
525    fn type_transition_new_type_with_name(
526        &self,
527        source_type: TypeId,
528        target_type: TypeId,
529        class: &Class,
530        name: NullessByteStr<'_>,
531    ) -> Option<TypeId> {
532        self.parsed_policy.compute_filename_transition(
533            source_type,
534            target_type,
535            class.id().into(),
536            name,
537        )
538    }
539
540    fn range_transition_new_range(
541        &self,
542        source_type: TypeId,
543        target_type: TypeId,
544        class: &Class,
545    ) -> Option<(MlsLevel, Option<MlsLevel>)> {
546        for range_transition in self.parsed_policy.range_transitions() {
547            if range_transition.source_type() == source_type
548                && range_transition.target_type() == target_type
549                && range_transition.target_class() == class.id().into()
550            {
551                let mls_range = range_transition.mls_range();
552                let low_level = mls_range.low().clone();
553                let high_level = mls_range.high().clone();
554                return Some((low_level, high_level));
555            }
556        }
557
558        None
559    }
560}
561
562/// Returns the bit index of the specified permission for the specified security `class`, looking
563/// up the permission in the class' common symbol, if any.
564fn get_permission_id_by_name(
565    common_symbols: &IdAndNameIndexed<SymbolArray<CommonSymbol>>,
566    class: &Class,
567    name: &str,
568) -> Option<PermissionId> {
569    let name = name.as_bytes();
570    if let Some(permission) = class.permissions().iter().find(|p| p.name() == name) {
571        return Some(permission.id());
572    }
573    let common_name = class.common_name();
574    if !common_name.is_empty() {
575        let common_symbol = common_symbols.get_by_name(common_name)?;
576        let permission = common_symbol.permissions().iter().find(|p| p.name() == name)?;
577        return Some(permission.id());
578    }
579    None
580}
581
582impl Deref for PolicyIndex {
583    type Target = ParsedPolicy;
584
585    fn deref(&self) -> &Self::Target {
586        &self.parsed_policy
587    }
588}