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