Skip to main content

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::security_context::SecurityContext;
7use super::{AccessVector, ClassId, MlsLevel, ParsedPolicy, PermissionId, RoleId, TypeId};
8use crate::new_policy::traits::{HasName, HasPolicyId};
9use crate::new_policy::{
10    Class, ClassDefault, ClassDefaultRange, CommonSymbol, HandleUnknown, IdAndNameIndexed,
11    SymbolArray,
12};
13use crate::{ClassPermission as _, KernelClass, KernelPermission, NullessByteStr, PolicyCap};
14
15use std::collections::HashMap;
16use std::ops::Deref;
17
18use strum::VariantArray as _;
19
20/// The [`SecurityContext`] and [`FsUseType`] derived from some `fs_use_*` line of the policy.
21pub struct FsUseLabelAndType {
22    pub context: SecurityContext,
23    pub use_type: FsUseType,
24}
25
26/// Array of `PermissionId` values each of a kernel security class' permissions.
27type KernelPermissionIdsArray = [Option<PermissionId>; 32];
28
29/// An index for facilitating fast lookup of common abstractions inside parsed binary policy data
30/// structures. Typically, data is indexed by an enum that describes a well-known value and the
31/// index stores the offset of the data in the binary policy to avoid scanning a collection to find
32/// an element that contains a matching string. For example, the policy contains a collection of
33/// classes that are identified by string names included in each collection entry. However,
34/// `policy_index.classes(KernelClass::Process).unwrap()` yields the offset in the policy's
35/// collection of classes where the "process" class resides.
36#[derive(Debug)]
37pub struct PolicyIndex {
38    /// Map from [`KernelClass`]es to their corresponding [`ClassId`]s in the associated policy's
39    /// [`super::symbols::Classes`] collection.
40    classes: HashMap<KernelClass, ClassId>,
41    /// Index mapping kernel class permissions to their policy-specific `AccessVector` bit index.
42    permissions: [KernelPermissionIdsArray; KernelClass::VARIANTS.len()],
43    /// The parsed binary policy.
44    parsed_policy: ParsedPolicy,
45    /// The "object_r" role used as a fallback for new file context transitions.
46    cached_object_r_role: RoleId,
47}
48
49impl PolicyIndex {
50    /// Constructs a [`PolicyIndex`] that indexes over well-known policy elements.
51    ///
52    /// [`Class`]es and [`Permission`]s used by the kernel are amongst the indexed elements.
53    /// The policy's `handle_unknown()` configuration determines whether the policy can be loaded even
54    /// if it omits classes or permissions expected by the kernel, and whether to allow or deny those
55    /// permissions if so.
56    pub fn new(parsed_policy: ParsedPolicy) -> Result<Self, anyhow::Error> {
57        let policy_classes = parsed_policy.classes();
58        let common_symbols = parsed_policy.common_symbols();
59
60        let mut classes = HashMap::with_capacity(crate::KernelClass::VARIANTS.len());
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::VARIANTS {
66            match policy_classes.get_by_name(known_class.name().as_bytes()) {
67                Some(class) => {
68                    classes.insert(*known_class, class.id());
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        // Allow unused space in the classes map to be released.
79        classes.shrink_to_fit();
80
81        // Accumulate permissions indexed by kernel permission enum. If the policy defines that
82        // unknown permissions or classes should cause rejection then return an error describing the
83        // missing element.
84        let mut permissions = [KernelPermissionIdsArray::default(); _];
85        for kernel_permission in crate::KernelPermission::all_variants() {
86            let kernel_class_name = kernel_permission.class().name();
87            if let Some(class) = policy_classes.get_by_name(kernel_class_name.as_bytes()) {
88                if let Some(permission_id) =
89                    get_permission_id_by_name(common_symbols, class, kernel_permission.name())
90                {
91                    let kernel_class_id = kernel_permission.class() as usize;
92                    let kernel_permission_id = kernel_permission.id() as usize;
93                    permissions[kernel_class_id][kernel_permission_id] = Some(permission_id);
94                } else if parsed_policy.handle_unknown() == HandleUnknown::Reject {
95                    return Err(anyhow::anyhow!(
96                        "missing permission {:?}:{:?}",
97                        kernel_class_name,
98                        kernel_permission.name(),
99                    ));
100                }
101            }
102        }
103
104        // Locate the "object_r" role.
105        let cached_object_r_role = parsed_policy
106            .roles()
107            .get_by_name(b"object_r")
108            .ok_or_else(|| anyhow::anyhow!("missing 'object_r' role"))?
109            .id();
110
111        let index = Self { classes, permissions, parsed_policy, cached_object_r_role };
112
113        // Verify that the initial Security Contexts are all defined, and valid.
114        for initial_sids in crate::InitialSid::all_variants() {
115            index.resolve_initial_context(*initial_sids);
116        }
117
118        // Validate the contexts used in fs_use statements.
119        for fs_use in index.parsed_policy.fs_uses() {
120            SecurityContext::new_from_policy_context(fs_use.context());
121        }
122
123        Ok(index)
124    }
125
126    /// Returns the policy entry for a class identified either by its well-known kernel object class
127    /// enum value, or its policy-defined Id.
128    pub(super) fn class(&self, object_class: crate::ObjectClass) -> Option<&Class> {
129        match object_class {
130            crate::ObjectClass::Kernel(kernel_class) => {
131                let &class_id = self.classes.get(&kernel_class)?;
132                self.classes().get_by_id(class_id)
133            }
134            crate::ObjectClass::ClassId(class_id) => self.classes().get_by_id(class_id),
135        }
136    }
137
138    /// Returns the policy entry for a well-known kernel object class permission.
139    pub fn kernel_permission_to_access_vector<P: Into<KernelPermission>>(
140        &self,
141        permission: P,
142    ) -> Option<AccessVector> {
143        let permission = permission.into();
144        let class_index = permission.class() as usize;
145        let permission_index = permission.id() as usize;
146        let permission_id = self.permissions[class_index][permission_index]?;
147        Some(permission_id.into())
148    }
149
150    /// Returns the security context that should be applied to a newly created SELinux
151    /// object according to `source` and `target` security contexts, as well as the new object's
152    /// `class`.
153    ///
154    /// If no filename-transition rule matches the supplied arguments then `None` is returned, and
155    /// the caller should fall-back to filename-independent labeling via
156    /// [`compute_create_context()`]
157    pub fn compute_create_context_with_name(
158        &self,
159        source: &SecurityContext,
160        target: &SecurityContext,
161        class: crate::ObjectClass,
162        name: NullessByteStr<'_>,
163    ) -> Option<SecurityContext> {
164        let policy_class = self.class(class)?;
165        let type_id = self.type_transition_new_type_with_name(
166            source.type_(),
167            target.type_(),
168            &policy_class,
169            name,
170        )?;
171        Some(self.new_security_context_internal(
172            source,
173            target,
174            class,
175            // Override the "type" with the value specified by the filename-transition rules.
176            Some(type_id),
177        ))
178    }
179
180    /// Returns the security context that should be applied to a newly created SELinux
181    /// object according to `source` and `target` security contexts, as well as the new object's
182    /// `class`.
183    ///
184    /// Computation follows the "create" algorithm for labeling newly created objects:
185    /// - user is taken from the `source`.
186    /// - role, type and range are taken from the matching transition rules, if any.
187    /// - role, type and range fall-back to the `source` or `target` values according to policy.
188    ///
189    /// If no transitions apply, and the policy does not explicitly specify defaults then the
190    /// role, type and range values have defaults chosen based on the `class`:
191    /// - For "process", and socket-like classes, role, type and range are taken from the `source`.
192    /// - Otherwise role is "object_r", type is taken from `target` and range is set to the
193    ///   low level of the `source` range.
194    pub fn compute_create_context(
195        &self,
196        source: &SecurityContext,
197        target: &SecurityContext,
198        class: crate::ObjectClass,
199    ) -> SecurityContext {
200        self.new_security_context_internal(source, target, class, None)
201    }
202
203    /// Internal implementation used by `compute_create_context_with_name()` and
204    /// `compute_create_context()` to implement the policy transition calculations.
205    /// If `override_type` is specified then the supplied value will be applied rather than a value
206    /// being calculated based on the policy; this is used by `compute_create_context_with_name()`
207    /// to shortcut the default `type_transition` lookup.
208    fn new_security_context_internal(
209        &self,
210        source: &SecurityContext,
211        target: &SecurityContext,
212        target_class: crate::ObjectClass,
213        override_type: Option<TypeId>,
214    ) -> SecurityContext {
215        let Some(policy_class) = self.class(target_class) else {
216            // If the class is not defined in the policy then there can be no transitions, nor
217            // class-defined choice of defaults, so default to the non-process-or-socket behaviour.
218            // TODO: https://fxbug.dev/361552580 - For `KernelClass`es, apply the kernel's notion
219            // of whether the class is "process", or socket-like?
220            return SecurityContext::new(
221                source.user(),
222                self.cached_object_r_role,
223                target.type_(),
224                source.low_level().clone(),
225                None,
226            );
227        };
228
229        let is_process_or_socket =
230            policy_class.name() == b"process" || policy_class.common_name() == b"socket";
231        let (unspecified_role, unspecified_type, unspecified_low, unspecified_high) =
232            if is_process_or_socket {
233                (source.role(), source.type_(), source.low_level(), source.high_level())
234            } else {
235                (self.cached_object_r_role, target.type_(), source.low_level(), None)
236            };
237        let class_defaults = policy_class.defaults();
238
239        let user = match class_defaults.user() {
240            ClassDefault::Source => source.user(),
241            ClassDefault::Target => target.user(),
242            ClassDefault::Unspecified => source.user(),
243        };
244
245        let role = match self.role_transition_new_role(source.role(), target.type_(), &policy_class)
246        {
247            Some(new_role) => new_role,
248            None => match class_defaults.role() {
249                ClassDefault::Source => source.role(),
250                ClassDefault::Target => target.role(),
251                ClassDefault::Unspecified => unspecified_role,
252            },
253        };
254
255        let type_ = override_type.unwrap_or_else(|| {
256            match self.parsed_policy.access_vector_rules_find(
257                source.type_(),
258                target.type_(),
259                policy_class.id().into(),
260                ACCESS_VECTOR_RULE_TYPE_TYPE_TRANSITION,
261            ) {
262                Some(new_type_rule) => new_type_rule.new_type().unwrap(),
263                None => match class_defaults.type_() {
264                    ClassDefault::Source => source.type_(),
265                    ClassDefault::Target => target.type_(),
266                    ClassDefault::Unspecified => unspecified_type,
267                },
268            }
269        });
270
271        let (low_level, high_level) =
272            match self.range_transition_new_range(source.type_(), target.type_(), &policy_class) {
273                Some((low_level, high_level)) => (low_level, high_level),
274                None => match class_defaults.range() {
275                    ClassDefaultRange::SourceLow => (source.low_level().clone(), None),
276                    ClassDefaultRange::SourceHigh => {
277                        (source.high_level().unwrap_or_else(|| source.low_level()).clone(), None)
278                    }
279                    ClassDefaultRange::SourceLowHigh => {
280                        (source.low_level().clone(), source.high_level().cloned())
281                    }
282                    ClassDefaultRange::TargetLow => (target.low_level().clone(), None),
283                    ClassDefaultRange::TargetHigh => {
284                        (target.high_level().unwrap_or_else(|| target.low_level()).clone(), None)
285                    }
286                    ClassDefaultRange::TargetLowHigh => {
287                        (target.low_level().clone(), target.high_level().cloned())
288                    }
289                    ClassDefaultRange::Unspecified => {
290                        (unspecified_low.clone(), unspecified_high.cloned())
291                    }
292                    ClassDefaultRange::UnknownUsedValue => {
293                        unreachable!("Invalid ClassDefaultRange in validated policy")
294                    }
295                },
296            };
297
298        // TODO(http://b/334968228): Validate domain & role transitions are allowed?
299        SecurityContext::new(user, role, type_, low_level, high_level)
300    }
301
302    /// Returns the Id of the "object_r" role within the `parsed_policy`, for use when validating
303    /// Security Context fields.
304    pub(super) fn object_role(&self) -> RoleId {
305        self.cached_object_r_role
306    }
307
308    /// Returns the [`SecurityContext`] defined by this policy for the specified
309    /// well-known (or "initial") Id.
310    pub(super) fn initial_context(&self, id: crate::InitialSid) -> SecurityContext {
311        // All [`InitialSid`] have already been verified as resolvable, by `new()`.
312        self.resolve_initial_context(id)
313    }
314
315    /// If there is an fs_use statement for the given filesystem type, returns the associated
316    /// [`SecurityContext`] and [`FsUseType`].
317    pub(super) fn fs_use_label_and_type(
318        &self,
319        fs_type: NullessByteStr<'_>,
320    ) -> Option<FsUseLabelAndType> {
321        self.parsed_policy
322            .fs_uses()
323            .iter()
324            .find(|fs_use| fs_use.fs_type() == fs_type.as_bytes())
325            .map(|fs_use| FsUseLabelAndType {
326                context: SecurityContext::new_from_policy_context(fs_use.context()),
327                use_type: fs_use.behavior(),
328            })
329    }
330
331    /// If there is a genfscon statement for the given filesystem type, returns the associated
332    /// [`SecurityContext`], taking the `node_path` into account. `class_id` defines the type
333    /// of the file in the given `node_path`. It can only be omitted when looking up the filesystem
334    /// label.
335    pub(super) fn genfscon_label_for_fs_and_path(
336        &self,
337        fs_type: NullessByteStr<'_>,
338        node_path: NullessByteStr<'_>,
339        class: Option<crate::KernelClass>,
340    ) -> Option<SecurityContext> {
341        let node_path = if class == Some(crate::FileClass::LnkFile.into())
342            && !self.parsed_policy.has_policycap(PolicyCap::GenfsSeclabelSymlinks)
343        {
344            // Symlinks receive the filesystem root label by default, rather than a label dependent on
345            // the `node_path`. Path based labels may be enabled with the "genfs_seclabel_symlinks"
346            // policy capability.
347            "/".into()
348        } else {
349            node_path
350        };
351
352        let class_id = class.and_then(|class| self.class(class.into())).map(|class| class.id());
353
354        // All contexts listed in the policy for the file system type.
355        let fs_contexts = self
356            .parsed_policy
357            .genfscon_find_all(std::str::from_utf8(fs_type.as_bytes()).expect("fs type is valid"));
358
359        #[derive(PartialEq)]
360        enum OrderType {
361            Alphabetic,
362            ByLength,
363            Unknown,
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        let mut order_type = OrderType::Unknown;
379        let mut prev_path_bytes: Option<Vec<u8>> = None;
380        for fs_context in fs_contexts {
381            // Determine the order type based on the first entries.
382            let path = fs_context.partial_path();
383            if order_type == OrderType::Unknown {
384                if let Some(prev) = &prev_path_bytes {
385                    if path.len() > prev.len() {
386                        order_type = OrderType::Alphabetic;
387                    } else if path < prev.as_slice() {
388                        order_type = OrderType::ByLength;
389                    }
390                }
391                prev_path_bytes = Some(path.to_vec());
392            }
393
394            // Check if the class matches.
395            let class_matches = class_id.is_none()
396                || fs_context
397                    .class()
398                    .map(|other| other == class_id.unwrap().into())
399                    .unwrap_or(true);
400            if !class_matches {
401                continue;
402            }
403
404            if order_type == OrderType::Alphabetic && fs_context.partial_path() > node_path.0 {
405                // We know that:
406                // - We have alphabetic order,
407                // - The current path is lexicographically greater than our target path.
408                // We can infer that we have passed any potential prefixes in alphabetical order.
409                break;
410            }
411
412            if node_path.0.starts_with(fs_context.partial_path()) {
413                if result
414                    .as_ref()
415                    .map_or(true, |c| c.partial_path().len() < fs_context.partial_path().len())
416                {
417                    // The path matches, and it's the closest parent so far.
418                    result = Some(fs_context);
419                    if order_type == OrderType::ByLength {
420                        break;
421                    }
422                }
423            }
424        }
425
426        // The returned SecurityContext must be valid with respect to the policy, since otherwise
427        // we'd have rejected the policy load.
428        result.and_then(|fs_context| {
429            Some(SecurityContext::new_from_policy_context(fs_context.context()))
430        })
431    }
432
433    /// Helper used to construct and validate well-known [`SecurityContext`] values.
434    fn resolve_initial_context(&self, id: crate::InitialSid) -> SecurityContext {
435        SecurityContext::new_from_policy_context(self.parsed_policy.initial_context(id))
436    }
437
438    fn role_transition_new_role(
439        &self,
440        current_role: RoleId,
441        type_: TypeId,
442        class: &Class,
443    ) -> Option<RoleId> {
444        self.parsed_policy
445            .role_transitions()
446            .iter()
447            .find(|role_transition| {
448                role_transition.current_role() == current_role
449                    && role_transition.type_() == type_
450                    && role_transition.class() == class.id().into()
451            })
452            .map(|x| x.new_role())
453    }
454
455    #[allow(dead_code)]
456    // TODO(http://b/334968228): fn to be used again when checking role allow rules separately from
457    // SID calculation.
458    fn role_transition_is_explicitly_allowed(&self, source_role: RoleId, new_role: RoleId) -> bool {
459        self.parsed_policy
460            .role_allowlist()
461            .iter()
462            .find(|role_allow| {
463                role_allow.source_role() == source_role && role_allow.new_role() == new_role
464            })
465            .is_some()
466    }
467
468    fn type_transition_new_type_with_name(
469        &self,
470        source_type: TypeId,
471        target_type: TypeId,
472        class: &Class,
473        name: NullessByteStr<'_>,
474    ) -> Option<TypeId> {
475        self.parsed_policy.compute_filename_transition(
476            source_type,
477            target_type,
478            class.id().into(),
479            name,
480        )
481    }
482
483    fn range_transition_new_range(
484        &self,
485        source_type: TypeId,
486        target_type: TypeId,
487        class: &Class,
488    ) -> Option<(MlsLevel, Option<MlsLevel>)> {
489        for range_transition in self.parsed_policy.range_transitions() {
490            if range_transition.source_type() == source_type
491                && range_transition.target_type() == target_type
492                && range_transition.target_class() == class.id().into()
493            {
494                let mls_range = range_transition.mls_range();
495                let low_level = mls_range.low().clone();
496                let high_level = mls_range.high().clone();
497                return Some((low_level, high_level));
498            }
499        }
500
501        None
502    }
503}
504
505/// Returns the bit index of the specified permission for the specified security `class`, looking
506/// up the permission in the class' common symbol, if any.
507fn get_permission_id_by_name(
508    common_symbols: &IdAndNameIndexed<SymbolArray<CommonSymbol>>,
509    class: &Class,
510    name: &str,
511) -> Option<PermissionId> {
512    let name = name.as_bytes();
513    if let Some(permission) = class.permissions().iter().find(|p| p.name() == name) {
514        return Some(permission.id());
515    }
516    let common_name = class.common_name();
517    if !common_name.is_empty() {
518        let common_symbol = common_symbols.get_by_name(common_name)?;
519        let permission = common_symbol.permissions().iter().find(|p| p.name() == name)?;
520        return Some(permission.id());
521    }
522    None
523}
524
525impl Deref for PolicyIndex {
526    type Target = ParsedPolicy;
527
528    fn deref(&self) -> &Self::Target {
529        &self.parsed_policy
530    }
531}