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::{ACCESS_VECTOR_RULE_TYPE_TYPE_TRANSITION, 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::{ParsedPolicy, RoleId, TypeId};
12use crate::{ClassPermission as _, NullessByteStr, PolicyCap};
13
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    /// [`super::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 [`super::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.parsed_policy.access_vector_rules_find(
263                source.type_(),
264                target.type_(),
265                policy_class.id(),
266                ACCESS_VECTOR_RULE_TYPE_TYPE_TRANSITION,
267            ) {
268                Some(new_type_rule) => new_type_rule.new_type().unwrap(),
269                None => match class_defaults.type_() {
270                    ClassDefault::Source => source.type_(),
271                    ClassDefault::Target => target.type_(),
272                    ClassDefault::Unspecified => unspecified_type,
273                },
274            }
275        });
276
277        let (low_level, high_level) =
278            match self.range_transition_new_range(source.type_(), target.type_(), policy_class) {
279                Some((low_level, high_level)) => (low_level, high_level),
280                None => match class_defaults.range() {
281                    ClassDefaultRange::SourceLow => (source.low_level().clone(), None),
282                    ClassDefaultRange::SourceHigh => {
283                        (source.high_level().unwrap_or_else(|| source.low_level()).clone(), None)
284                    }
285                    ClassDefaultRange::SourceLowHigh => {
286                        (source.low_level().clone(), source.high_level().cloned())
287                    }
288                    ClassDefaultRange::TargetLow => (target.low_level().clone(), None),
289                    ClassDefaultRange::TargetHigh => {
290                        (target.high_level().unwrap_or_else(|| target.low_level()).clone(), None)
291                    }
292                    ClassDefaultRange::TargetLowHigh => {
293                        (target.low_level().clone(), target.high_level().cloned())
294                    }
295                    ClassDefaultRange::Unspecified => {
296                        (unspecified_low.clone(), unspecified_high.cloned())
297                    }
298                },
299            };
300
301        // TODO(http://b/334968228): Validate domain & role transitions are allowed?
302        SecurityContext::new(user, role, type_, low_level, high_level)
303    }
304
305    /// Returns the Id of the "object_r" role within the `parsed_policy`, for use when validating
306    /// Security Context fields.
307    pub(super) fn object_role(&self) -> RoleId {
308        self.cached_object_r_role
309    }
310
311    pub(super) fn parsed_policy(&self) -> &ParsedPolicy {
312        &self.parsed_policy
313    }
314
315    /// Returns the [`SecurityContext`] defined by this policy for the specified
316    /// well-known (or "initial") Id.
317    pub(super) fn initial_context(&self, id: crate::InitialSid) -> SecurityContext {
318        // All [`InitialSid`] have already been verified as resolvable, by `new()`.
319        self.resolve_initial_context(id)
320    }
321
322    /// If there is an fs_use statement for the given filesystem type, returns the associated
323    /// [`SecurityContext`] and [`FsUseType`].
324    pub(super) fn fs_use_label_and_type(
325        &self,
326        fs_type: NullessByteStr<'_>,
327    ) -> Option<FsUseLabelAndType> {
328        self.parsed_policy
329            .fs_uses()
330            .iter()
331            .find(|fs_use| fs_use.fs_type() == fs_type.as_bytes())
332            .map(|fs_use| FsUseLabelAndType {
333                context: SecurityContext::new_from_policy_context(fs_use.context()),
334                use_type: fs_use.behavior(),
335            })
336    }
337
338    /// If there is a genfscon statement for the given filesystem type, returns the associated
339    /// [`SecurityContext`], taking the `node_path` into account. `class_id` defines the type
340    /// of the file in the given `node_path`. It can only be omitted when looking up the filesystem
341    /// label.
342    pub(super) fn genfscon_label_for_fs_and_path(
343        &self,
344        fs_type: NullessByteStr<'_>,
345        node_path: NullessByteStr<'_>,
346        class: Option<crate::KernelClass>,
347    ) -> Option<SecurityContext> {
348        let node_path = if class == Some(crate::FileClass::Link.into())
349            && !self.parsed_policy.has_policycap(PolicyCap::GenfsSeclabelSymlinks)
350        {
351            // Symlinks receive the filesystem root label by default, rather than a label dependent on
352            // the `node_path`. Path based labels may be enabled with the "genfs_seclabel_symlinks"
353            // policy capability.
354            "/".into()
355        } else {
356            node_path
357        };
358
359        let class_id = class.and_then(|class| self.class(class.into())).map(|class| class.id());
360
361        // All contexts listed in the policy for the file system type.
362        let fs_contexts = self
363            .parsed_policy
364            .generic_fs_contexts()
365            .iter()
366            .find(|genfscon| genfscon.fs_type() == fs_type.as_bytes())?
367            .contexts();
368
369        // The correct match is the closest parent among the ones given in the policy file.
370        // E.g. if in the policy we have
371        //     genfscon foofs "/" label1
372        //     genfscon foofs "/abc/" label2
373        //     genfscon foofs "/abc/def" label3
374        //
375        // The correct label for a file "/abc/def/g/h/i" is label3, as "/abc/def" is the closest parent
376        // among those defined.
377        //
378        // Partial paths are prefix-matched, so that "/abc/default" would also be assigned label3.
379        //
380        // TODO(372212126): Optimize the algorithm.
381        let mut result: Option<&FsContext> = None;
382        for fs_context in fs_contexts {
383            if node_path.0.starts_with(fs_context.partial_path()) {
384                if result.is_none()
385                    || result.unwrap().partial_path().len() < fs_context.partial_path().len()
386                {
387                    if class_id.is_none()
388                        || fs_context
389                            .class()
390                            .map(|other| other == class_id.unwrap())
391                            .unwrap_or(true)
392                    {
393                        result = Some(fs_context);
394                    }
395                }
396            }
397        }
398
399        // The returned SecurityContext must be valid with respect to the policy, since otherwise
400        // we'd have rejected the policy load.
401        result.and_then(|fs_context| {
402            Some(SecurityContext::new_from_policy_context(fs_context.context()))
403        })
404    }
405
406    /// Helper used to construct and validate well-known [`SecurityContext`] values.
407    fn resolve_initial_context(&self, id: crate::InitialSid) -> SecurityContext {
408        SecurityContext::new_from_policy_context(self.parsed_policy().initial_context(id))
409    }
410
411    fn role_transition_new_role(
412        &self,
413        current_role: RoleId,
414        type_: TypeId,
415        class: &Class,
416    ) -> Option<RoleId> {
417        self.parsed_policy
418            .role_transitions()
419            .iter()
420            .find(|role_transition| {
421                role_transition.current_role() == current_role
422                    && role_transition.type_() == type_
423                    && role_transition.class() == class.id()
424            })
425            .map(|x| x.new_role())
426    }
427
428    #[allow(dead_code)]
429    // TODO(http://b/334968228): fn to be used again when checking role allow rules separately from
430    // SID calculation.
431    fn role_transition_is_explicitly_allowed(&self, source_role: RoleId, new_role: RoleId) -> bool {
432        self.parsed_policy
433            .role_allowlist()
434            .iter()
435            .find(|role_allow| {
436                role_allow.source_role() == source_role && role_allow.new_role() == new_role
437            })
438            .is_some()
439    }
440
441    fn type_transition_new_type_with_name(
442        &self,
443        source_type: TypeId,
444        target_type: TypeId,
445        class: &Class,
446        name: NullessByteStr<'_>,
447    ) -> Option<TypeId> {
448        self.parsed_policy.compute_filename_transition(source_type, target_type, class.id(), name)
449    }
450
451    fn range_transition_new_range(
452        &self,
453        source_type: TypeId,
454        target_type: TypeId,
455        class: &Class,
456    ) -> Option<(SecurityLevel, Option<SecurityLevel>)> {
457        for range_transition in self.parsed_policy.range_transitions() {
458            if range_transition.source_type() == source_type
459                && range_transition.target_type() == target_type
460                && range_transition.target_class() == class.id()
461            {
462                let mls_range = range_transition.mls_range();
463                let low_level = SecurityLevel::new_from_mls_level(mls_range.low());
464                let high_level = mls_range
465                    .high()
466                    .as_ref()
467                    .map(|high_level| SecurityLevel::new_from_mls_level(high_level));
468                return Some((low_level, high_level));
469            }
470        }
471
472        None
473    }
474}
475
476/// Permissions may be stored in their associated [`Class`], or on the class's associated
477/// [`CommonSymbol`]. This is a consequence of a limited form of inheritance supported for SELinux
478/// policy classes. Classes may inherit from zero or one `common`. For example:
479///
480/// ```config
481/// common file { ioctl read write create [...] }
482/// class file inherits file { execute_no_trans entrypoint }
483/// ```
484///
485/// In the above example, the "ioctl" permission for the "file" `class` is stored as a permission
486/// on the "file" `common`, whereas the permission "execute_no_trans" is stored as a permission on
487/// the "file" `class`.
488#[derive(Debug)]
489enum PermissionIndex {
490    /// Permission is located at `Class::permissions()[permission_index]`.
491    Class { permission_index: usize },
492    /// Permission is located at
493    /// `ParsedPolicy::common_symbols()[common_symbol_index].permissions()[permission_index]`.
494    Common { common_symbol_index: usize, permission_index: usize },
495}
496
497fn get_class_index_by_name<'a>(classes: &'a Classes, name: &str) -> Option<usize> {
498    let name_bytes = name.as_bytes();
499    classes.iter().position(|class| class.name_bytes() == name_bytes)
500}
501
502fn get_common_symbol_index_by_name_bytes<'a>(
503    common_symbols: &'a CommonSymbols,
504    name_bytes: &[u8],
505) -> Option<usize> {
506    common_symbols.iter().position(|common_symbol| common_symbol.name_bytes() == name_bytes)
507}
508
509fn get_permission_index_by_name<'a>(
510    common_symbols: &'a CommonSymbols,
511    class: &'a Class,
512    name: &str,
513) -> Option<PermissionIndex> {
514    if let Some(permission_index) = get_class_permission_index_by_name(class, name) {
515        Some(PermissionIndex::Class { permission_index })
516    } else if let Some(common_symbol_index) =
517        get_common_symbol_index_by_name_bytes(common_symbols, class.common_name_bytes())
518    {
519        let common_symbol = &common_symbols[common_symbol_index];
520        if let Some(permission_index) = get_common_permission_index_by_name(common_symbol, name) {
521            Some(PermissionIndex::Common { common_symbol_index, permission_index })
522        } else {
523            None
524        }
525    } else {
526        None
527    }
528}
529
530fn get_class_permission_index_by_name<'a>(class: &'a Class, name: &str) -> Option<usize> {
531    let name_bytes = name.as_bytes();
532    class.permissions().iter().position(|permission| permission.name_bytes() == name_bytes)
533}
534
535fn get_common_permission_index_by_name<'a>(
536    common_symbol: &'a CommonSymbol,
537    name: &str,
538) -> Option<usize> {
539    let name_bytes = name.as_bytes();
540    common_symbol.permissions().iter().position(|permission| permission.name_bytes() == name_bytes)
541}