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