selinux/policy/
index.rs

1// Copyright 2024 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use super::arrays::{FsContext, FsUseType};
6use super::metadata::HandleUnknown;
7use super::security_context::{SecurityContext, SecurityLevel};
8use super::symbols::{
9    Class, ClassDefault, ClassDefaultRange, Classes, CommonSymbol, CommonSymbols, Permission,
10};
11use super::{ParsedPolicy, RoleId, TypeId};
12
13use crate::policy::arrays::ACCESS_VECTOR_RULE_TYPE_TYPE_TRANSITION;
14use crate::{ClassPermission as _, NullessByteStr, PolicyCap};
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 {
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,
43    /// The "object_r" role used as a fallback for new file context transitions.
44    cached_object_r_role: RoleId,
45}
46
47impl PolicyIndex {
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) -> 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() {
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 mut permissions =
92            HashMap::with_capacity(crate::KernelPermission::all_variants().count());
93        for known_permission in crate::KernelPermission::all_variants() {
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 initial_sids in crate::InitialSid::all_variants() {
122            index.resolve_initial_context(*initial_sids);
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> {
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> {
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 SELinux
158    /// object according to `source` and `target` security contexts, as well as the new object's
159    /// `class`.
160    ///
161    /// If no filename-transition rule matches the supplied arguments then `None` is returned, and
162    /// the caller should fall-back to filename-independent labeling via
163    /// [`compute_create_context()`]
164    pub fn compute_create_context_with_name(
165        &self,
166        source: &SecurityContext,
167        target: &SecurityContext,
168        class: crate::ObjectClass,
169        name: NullessByteStr<'_>,
170    ) -> Option<SecurityContext> {
171        let policy_class = self.class(class)?;
172        let type_id = self.type_transition_new_type_with_name(
173            source.type_(),
174            target.type_(),
175            policy_class,
176            name,
177        )?;
178        Some(self.new_security_context_internal(
179            source,
180            target,
181            class,
182            // Override the "type" with the value specified by the filename-transition rules.
183            Some(type_id),
184        ))
185    }
186
187    /// Returns the security context that should be applied to a newly created SELinux
188    /// object according to `source` and `target` security contexts, as well as the new object's
189    /// `class`.
190    ///
191    /// Computation follows the "create" algorithm for labeling newly created objects:
192    /// - user is taken from the `source`.
193    /// - role, type and range are taken from the matching transition rules, if any.
194    /// - role, type and range fall-back to the `source` or `target` values according to policy.
195    ///
196    /// If no transitions apply, and the policy does not explicitly specify defaults then the
197    /// role, type and range values have defaults chosen based on the `class`:
198    /// - For "process", and socket-like classes, role, type and range are taken from the `source`.
199    /// - Otherwise role is "object_r", type is taken from `target` and range is set to the
200    ///   low level of the `source` range.
201    pub fn compute_create_context(
202        &self,
203        source: &SecurityContext,
204        target: &SecurityContext,
205        class: crate::ObjectClass,
206    ) -> SecurityContext {
207        self.new_security_context_internal(source, target, class, None)
208    }
209
210    /// Internal implementation used by `compute_create_context_with_name()` and
211    /// `compute_create_context()` to implement the policy transition calculations.
212    /// If `override_type` is specified then the supplied value will be applied rather than a value
213    /// being calculated based on the policy; this is used by `compute_create_context_with_name()`
214    /// to shortcut the default `type_transition` lookup.
215    fn new_security_context_internal(
216        &self,
217        source: &SecurityContext,
218        target: &SecurityContext,
219        target_class: crate::ObjectClass,
220        override_type: Option<TypeId>,
221    ) -> SecurityContext {
222        let Some(policy_class) = self.class(target_class) else {
223            // If the class is not defined in the policy then there can be no transitions, nor
224            // class-defined choice of defaults, so default to the non-process-or-socket behaviour.
225            // TODO: https://fxbug.dev/361552580 - For `KernelClass`es, apply the kernel's notion
226            // of whether the class is "process", or socket-like?
227            return SecurityContext::new(
228                source.user(),
229                self.cached_object_r_role,
230                target.type_(),
231                source.low_level().clone(),
232                None,
233            );
234        };
235
236        let is_process_or_socket = policy_class.name_bytes() == b"process"
237            || policy_class.common_name_bytes() == b"socket";
238        let (unspecified_role, unspecified_type, unspecified_low, unspecified_high) =
239            if is_process_or_socket {
240                (source.role(), source.type_(), source.low_level(), source.high_level())
241            } else {
242                (self.cached_object_r_role, target.type_(), source.low_level(), None)
243            };
244        let class_defaults = policy_class.defaults();
245
246        let user = match class_defaults.user() {
247            ClassDefault::Source => source.user(),
248            ClassDefault::Target => target.user(),
249            ClassDefault::Unspecified => source.user(),
250        };
251
252        let role = match self.role_transition_new_role(source.role(), target.type_(), policy_class)
253        {
254            Some(new_role) => new_role,
255            None => match class_defaults.role() {
256                ClassDefault::Source => source.role(),
257                ClassDefault::Target => target.role(),
258                ClassDefault::Unspecified => unspecified_role,
259            },
260        };
261
262        let type_ = override_type.unwrap_or_else(|| {
263            match self.type_transition_new_type(source.type_(), target.type_(), policy_class) {
264                Some(new_type) => new_type,
265                None => match class_defaults.type_() {
266                    ClassDefault::Source => source.type_(),
267                    ClassDefault::Target => target.type_(),
268                    ClassDefault::Unspecified => unspecified_type,
269                },
270            }
271        });
272
273        let (low_level, high_level) =
274            match self.range_transition_new_range(source.type_(), target.type_(), policy_class) {
275                Some((low_level, high_level)) => (low_level, high_level),
276                None => match class_defaults.range() {
277                    ClassDefaultRange::SourceLow => (source.low_level().clone(), None),
278                    ClassDefaultRange::SourceHigh => {
279                        (source.high_level().unwrap_or_else(|| source.low_level()).clone(), None)
280                    }
281                    ClassDefaultRange::SourceLowHigh => {
282                        (source.low_level().clone(), source.high_level().cloned())
283                    }
284                    ClassDefaultRange::TargetLow => (target.low_level().clone(), None),
285                    ClassDefaultRange::TargetHigh => {
286                        (target.high_level().unwrap_or_else(|| target.low_level()).clone(), None)
287                    }
288                    ClassDefaultRange::TargetLowHigh => {
289                        (target.low_level().clone(), target.high_level().cloned())
290                    }
291                    ClassDefaultRange::Unspecified => {
292                        (unspecified_low.clone(), unspecified_high.cloned())
293                    }
294                },
295            };
296
297        // TODO(http://b/334968228): Validate domain & role transitions are allowed?
298        SecurityContext::new(user, role, type_, low_level, high_level)
299    }
300
301    /// Returns the Id of the "object_r" role within the `parsed_policy`, for use when validating
302    /// Security Context fields.
303    pub(super) fn object_role(&self) -> RoleId {
304        self.cached_object_r_role
305    }
306
307    pub(super) fn parsed_policy(&self) -> &ParsedPolicy {
308        &self.parsed_policy
309    }
310
311    /// Returns the [`SecurityContext`] defined by this policy for the specified
312    /// well-known (or "initial") Id.
313    pub(super) fn initial_context(&self, id: crate::InitialSid) -> SecurityContext {
314        // All [`InitialSid`] have already been verified as resolvable, by `new()`.
315        self.resolve_initial_context(id)
316    }
317
318    /// If there is an fs_use statement for the given filesystem type, returns the associated
319    /// [`SecurityContext`] and [`FsUseType`].
320    pub(super) fn fs_use_label_and_type(
321        &self,
322        fs_type: NullessByteStr<'_>,
323    ) -> Option<FsUseLabelAndType> {
324        self.parsed_policy
325            .fs_uses()
326            .iter()
327            .find(|fs_use| fs_use.fs_type() == fs_type.as_bytes())
328            .map(|fs_use| FsUseLabelAndType {
329                context: SecurityContext::new_from_policy_context(fs_use.context()),
330                use_type: fs_use.behavior(),
331            })
332    }
333
334    /// If there is a genfscon statement for the given filesystem type, returns the associated
335    /// [`SecurityContext`], taking the `node_path` into account. `class_id` defines the type
336    /// of the file in the given `node_path`. It can only be omitted when looking up the filesystem
337    /// label.
338    pub(super) fn genfscon_label_for_fs_and_path(
339        &self,
340        fs_type: NullessByteStr<'_>,
341        node_path: NullessByteStr<'_>,
342        class: Option<crate::KernelClass>,
343    ) -> Option<SecurityContext> {
344        let node_path = if class == Some(crate::FileClass::Link.into())
345            && !self.parsed_policy.has_policycap(PolicyCap::GenfsSeclabelSymlinks)
346        {
347            // Symlinks receive the filesystem root label by default, rather than a label dependent on
348            // the `node_path`. Path based labels may be enabled with the "genfs_seclabel_symlinks"
349            // policy capability.
350            "/".into()
351        } else {
352            node_path
353        };
354
355        let class_id = class.and_then(|class| self.class(class.into())).map(|class| class.id());
356
357        // All contexts listed in the policy for the file system type.
358        let fs_contexts = self
359            .parsed_policy
360            .generic_fs_contexts()
361            .iter()
362            .find(|genfscon| genfscon.fs_type() == fs_type.as_bytes())?
363            .contexts();
364
365        // The correct match is the closest parent among the ones given in the policy file.
366        // E.g. if in the policy we have
367        //     genfscon foofs "/" label1
368        //     genfscon foofs "/abc/" label2
369        //     genfscon foofs "/abc/def" label3
370        //
371        // The correct label for a file "/abc/def/g/h/i" is label3, as "/abc/def" is the closest parent
372        // among those defined.
373        //
374        // Partial paths are prefix-matched, so that "/abc/default" would also be assigned label3.
375        //
376        // TODO(372212126): Optimize the algorithm.
377        let mut result: Option<&FsContext> = None;
378        for fs_context in fs_contexts {
379            if node_path.0.starts_with(fs_context.partial_path()) {
380                if result.is_none()
381                    || result.unwrap().partial_path().len() < fs_context.partial_path().len()
382                {
383                    if class_id.is_none()
384                        || fs_context
385                            .class()
386                            .map(|other| other == class_id.unwrap())
387                            .unwrap_or(true)
388                    {
389                        result = Some(fs_context);
390                    }
391                }
392            }
393        }
394
395        // The returned SecurityContext must be valid with respect to the policy, since otherwise
396        // we'd have rejected the policy load.
397        result.and_then(|fs_context| {
398            Some(SecurityContext::new_from_policy_context(fs_context.context()))
399        })
400    }
401
402    /// Helper used to construct and validate well-known [`SecurityContext`] values.
403    fn resolve_initial_context(&self, id: crate::InitialSid) -> SecurityContext {
404        SecurityContext::new_from_policy_context(self.parsed_policy().initial_context(id))
405    }
406
407    fn role_transition_new_role(
408        &self,
409        current_role: RoleId,
410        type_: TypeId,
411        class: &Class,
412    ) -> Option<RoleId> {
413        self.parsed_policy
414            .role_transitions()
415            .iter()
416            .find(|role_transition| {
417                role_transition.current_role() == current_role
418                    && role_transition.type_() == type_
419                    && role_transition.class() == class.id()
420            })
421            .map(|x| x.new_role())
422    }
423
424    #[allow(dead_code)]
425    // TODO(http://b/334968228): fn to be used again when checking role allow rules separately from
426    // SID calculation.
427    fn role_transition_is_explicitly_allowed(&self, source_role: RoleId, new_role: RoleId) -> bool {
428        self.parsed_policy
429            .role_allowlist()
430            .iter()
431            .find(|role_allow| {
432                role_allow.source_role() == source_role && role_allow.new_role() == new_role
433            })
434            .is_some()
435    }
436
437    fn type_transition_new_type(
438        &self,
439        source_type: TypeId,
440        target_type: TypeId,
441        class: &Class,
442    ) -> Option<TypeId> {
443        // Return first match. The `checkpolicy` tool will not compile a policy that has
444        // multiple matches, so behavior on multiple matches is undefined.
445        let rule = self.parsed_policy.find_access_vector_rule(
446            source_type,
447            target_type,
448            class.id(),
449            ACCESS_VECTOR_RULE_TYPE_TYPE_TRANSITION,
450        )?;
451        Some(rule.new_type().unwrap())
452    }
453
454    fn type_transition_new_type_with_name(
455        &self,
456        source_type: TypeId,
457        target_type: TypeId,
458        class: &Class,
459        name: NullessByteStr<'_>,
460    ) -> Option<TypeId> {
461        self.parsed_policy.compute_filename_transition(source_type, target_type, class.id(), name)
462    }
463
464    fn range_transition_new_range(
465        &self,
466        source_type: TypeId,
467        target_type: TypeId,
468        class: &Class,
469    ) -> Option<(SecurityLevel, Option<SecurityLevel>)> {
470        for range_transition in self.parsed_policy.range_transitions() {
471            if range_transition.source_type() == source_type
472                && range_transition.target_type() == target_type
473                && range_transition.target_class() == class.id()
474            {
475                let mls_range = range_transition.mls_range();
476                let low_level = SecurityLevel::new_from_mls_level(mls_range.low());
477                let high_level = mls_range
478                    .high()
479                    .as_ref()
480                    .map(|high_level| SecurityLevel::new_from_mls_level(high_level));
481                return Some((low_level, high_level));
482            }
483        }
484
485        None
486    }
487}
488
489/// Permissions may be stored in their associated [`Class`], or on the class's associated
490/// [`CommonSymbol`]. This is a consequence of a limited form of inheritance supported for SELinux
491/// policy classes. Classes may inherit from zero or one `common`. For example:
492///
493/// ```config
494/// common file { ioctl read write create [...] }
495/// class file inherits file { execute_no_trans entrypoint }
496/// ```
497///
498/// In the above example, the "ioctl" permission for the "file" `class` is stored as a permission
499/// on the "file" `common`, whereas the permission "execute_no_trans" is stored as a permission on
500/// the "file" `class`.
501#[derive(Debug)]
502enum PermissionIndex {
503    /// Permission is located at `Class::permissions()[permission_index]`.
504    Class { permission_index: usize },
505    /// Permission is located at
506    /// `ParsedPolicy::common_symbols()[common_symbol_index].permissions()[permission_index]`.
507    Common { common_symbol_index: usize, permission_index: usize },
508}
509
510fn get_class_index_by_name<'a>(classes: &'a Classes, name: &str) -> Option<usize> {
511    let name_bytes = name.as_bytes();
512    for i in 0..classes.len() {
513        if classes[i].name_bytes() == name_bytes {
514            return Some(i);
515        }
516    }
517
518    None
519}
520
521fn get_common_symbol_index_by_name_bytes<'a>(
522    common_symbols: &'a CommonSymbols,
523    name_bytes: &[u8],
524) -> Option<usize> {
525    for i in 0..common_symbols.len() {
526        if common_symbols[i].name_bytes() == name_bytes {
527            return Some(i);
528        }
529    }
530
531    None
532}
533
534fn get_permission_index_by_name<'a>(
535    common_symbols: &'a CommonSymbols,
536    class: &'a Class,
537    name: &str,
538) -> Option<PermissionIndex> {
539    if let Some(permission_index) = get_class_permission_index_by_name(class, name) {
540        Some(PermissionIndex::Class { permission_index })
541    } else if let Some(common_symbol_index) =
542        get_common_symbol_index_by_name_bytes(common_symbols, class.common_name_bytes())
543    {
544        let common_symbol = &common_symbols[common_symbol_index];
545        if let Some(permission_index) = get_common_permission_index_by_name(common_symbol, name) {
546            Some(PermissionIndex::Common { common_symbol_index, permission_index })
547        } else {
548            None
549        }
550    } else {
551        None
552    }
553}
554
555fn get_class_permission_index_by_name<'a>(class: &'a Class, name: &str) -> Option<usize> {
556    let name_bytes = name.as_bytes();
557    let permissions = class.permissions();
558    for i in 0..permissions.len() {
559        if permissions[i].name_bytes() == name_bytes {
560            return Some(i);
561        }
562    }
563
564    None
565}
566
567fn get_common_permission_index_by_name<'a>(
568    common_symbol: &'a CommonSymbol,
569    name: &str,
570) -> Option<usize> {
571    let name_bytes = name.as_bytes();
572    let permissions = common_symbol.permissions();
573    for i in 0..permissions.len() {
574        if permissions[i].name_bytes() == name_bytes {
575            return Some(i);
576        }
577    }
578
579    None
580}