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