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