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            .role_by_name("object_r".into())
107            .ok_or_else(|| anyhow::anyhow!("missing 'object_r' role"))?
108            .id();
109
110        let index = Self { classes, permissions, parsed_policy, cached_object_r_role };
111
112        // Verify that the initial Security Contexts are all defined, and valid.
113        for initial_sids in crate::InitialSid::all_variants() {
114            index.resolve_initial_context(*initial_sids);
115        }
116
117        // Validate the contexts used in fs_use statements.
118        for fs_use in index.parsed_policy.fs_uses() {
119            SecurityContext::new_from_policy_context(fs_use.context());
120        }
121
122        Ok(index)
123    }
124
125    /// Returns the policy entry for a class identified either by its well-known kernel object class
126    /// enum value, or its policy-defined Id.
127    pub(super) fn class(&self, object_class: crate::ObjectClass) -> Option<&Class> {
128        match object_class {
129            crate::ObjectClass::Kernel(kernel_class) => {
130                let &class_id = self.classes.get(&kernel_class)?;
131                self.classes().get_by_id(class_id)
132            }
133            crate::ObjectClass::ClassId(class_id) => self.classes().get_by_id(class_id),
134        }
135    }
136
137    /// Returns the policy entry for a well-known kernel object class permission.
138    pub fn kernel_permission_to_access_vector<P: Into<KernelPermission>>(
139        &self,
140        permission: P,
141    ) -> Option<AccessVector> {
142        let permission = permission.into();
143        let class_index = permission.class() as usize;
144        let permission_index = permission.id() as usize;
145        let permission_id = self.permissions[class_index][permission_index]?;
146        Some(permission_id.into())
147    }
148
149    /// Returns the security context that should be applied to a newly created SELinux
150    /// object according to `source` and `target` security contexts, as well as the new object's
151    /// `class`.
152    ///
153    /// If no filename-transition rule matches the supplied arguments then `None` is returned, and
154    /// the caller should fall-back to filename-independent labeling via
155    /// [`compute_create_context()`]
156    pub fn compute_create_context_with_name(
157        &self,
158        source: &SecurityContext,
159        target: &SecurityContext,
160        class: crate::ObjectClass,
161        name: NullessByteStr<'_>,
162    ) -> Option<SecurityContext> {
163        let policy_class = self.class(class)?;
164        let type_id = self.type_transition_new_type_with_name(
165            source.type_(),
166            target.type_(),
167            &policy_class,
168            name,
169        )?;
170        Some(self.new_security_context_internal(
171            source,
172            target,
173            class,
174            // Override the "type" with the value specified by the filename-transition rules.
175            Some(type_id),
176        ))
177    }
178
179    /// Returns the security context that should be applied to a newly created SELinux
180    /// object according to `source` and `target` security contexts, as well as the new object's
181    /// `class`.
182    ///
183    /// Computation follows the "create" algorithm for labeling newly created objects:
184    /// - user is taken from the `source`.
185    /// - role, type and range are taken from the matching transition rules, if any.
186    /// - role, type and range fall-back to the `source` or `target` values according to policy.
187    ///
188    /// If no transitions apply, and the policy does not explicitly specify defaults then the
189    /// role, type and range values have defaults chosen based on the `class`:
190    /// - For "process", and socket-like classes, role, type and range are taken from the `source`.
191    /// - Otherwise role is "object_r", type is taken from `target` and range is set to the
192    ///   low level of the `source` range.
193    pub fn compute_create_context(
194        &self,
195        source: &SecurityContext,
196        target: &SecurityContext,
197        class: crate::ObjectClass,
198    ) -> SecurityContext {
199        self.new_security_context_internal(source, target, class, None)
200    }
201
202    /// Internal implementation used by `compute_create_context_with_name()` and
203    /// `compute_create_context()` to implement the policy transition calculations.
204    /// If `override_type` is specified then the supplied value will be applied rather than a value
205    /// being calculated based on the policy; this is used by `compute_create_context_with_name()`
206    /// to shortcut the default `type_transition` lookup.
207    fn new_security_context_internal(
208        &self,
209        source: &SecurityContext,
210        target: &SecurityContext,
211        target_class: crate::ObjectClass,
212        override_type: Option<TypeId>,
213    ) -> SecurityContext {
214        let Some(policy_class) = self.class(target_class) else {
215            // If the class is not defined in the policy then there can be no transitions, nor
216            // class-defined choice of defaults, so default to the non-process-or-socket behaviour.
217            // TODO: https://fxbug.dev/361552580 - For `KernelClass`es, apply the kernel's notion
218            // of whether the class is "process", or socket-like?
219            return SecurityContext::new(
220                source.user(),
221                self.cached_object_r_role,
222                target.type_(),
223                source.low_level().clone(),
224                None,
225            );
226        };
227
228        let is_process_or_socket =
229            policy_class.name() == b"process" || policy_class.common_name() == b"socket";
230        let (unspecified_role, unspecified_type, unspecified_low, unspecified_high) =
231            if is_process_or_socket {
232                (source.role(), source.type_(), source.low_level(), source.high_level())
233            } else {
234                (self.cached_object_r_role, target.type_(), source.low_level(), None)
235            };
236        let class_defaults = policy_class.defaults();
237
238        let user = match class_defaults.user() {
239            ClassDefault::Source => source.user(),
240            ClassDefault::Target => target.user(),
241            ClassDefault::Unspecified => source.user(),
242        };
243
244        let role = match self.role_transition_new_role(source.role(), target.type_(), &policy_class)
245        {
246            Some(new_role) => new_role,
247            None => match class_defaults.role() {
248                ClassDefault::Source => source.role(),
249                ClassDefault::Target => target.role(),
250                ClassDefault::Unspecified => unspecified_role,
251            },
252        };
253
254        let type_ = override_type.unwrap_or_else(|| {
255            match self.parsed_policy.access_vector_rules_find(
256                source.type_(),
257                target.type_(),
258                policy_class.id().into(),
259                ACCESS_VECTOR_RULE_TYPE_TYPE_TRANSITION,
260            ) {
261                Some(new_type_rule) => new_type_rule.new_type().unwrap(),
262                None => match class_defaults.type_() {
263                    ClassDefault::Source => source.type_(),
264                    ClassDefault::Target => target.type_(),
265                    ClassDefault::Unspecified => unspecified_type,
266                },
267            }
268        });
269
270        let (low_level, high_level) =
271            match self.range_transition_new_range(source.type_(), target.type_(), &policy_class) {
272                Some((low_level, high_level)) => (low_level, high_level),
273                None => match class_defaults.range() {
274                    ClassDefaultRange::SourceLow => (source.low_level().clone(), None),
275                    ClassDefaultRange::SourceHigh => {
276                        (source.high_level().unwrap_or_else(|| source.low_level()).clone(), None)
277                    }
278                    ClassDefaultRange::SourceLowHigh => {
279                        (source.low_level().clone(), source.high_level().cloned())
280                    }
281                    ClassDefaultRange::TargetLow => (target.low_level().clone(), None),
282                    ClassDefaultRange::TargetHigh => {
283                        (target.high_level().unwrap_or_else(|| target.low_level()).clone(), None)
284                    }
285                    ClassDefaultRange::TargetLowHigh => {
286                        (target.low_level().clone(), target.high_level().cloned())
287                    }
288                    ClassDefaultRange::Unspecified => {
289                        (unspecified_low.clone(), unspecified_high.cloned())
290                    }
291                    ClassDefaultRange::UnknownUsedValue => {
292                        unreachable!("Invalid ClassDefaultRange in validated policy")
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    /// Returns the [`SecurityContext`] defined by this policy for the specified
308    /// well-known (or "initial") Id.
309    pub(super) fn initial_context(&self, id: crate::InitialSid) -> SecurityContext {
310        // All [`InitialSid`] have already been verified as resolvable, by `new()`.
311        self.resolve_initial_context(id)
312    }
313
314    /// If there is an fs_use statement for the given filesystem type, returns the associated
315    /// [`SecurityContext`] and [`FsUseType`].
316    pub(super) fn fs_use_label_and_type(
317        &self,
318        fs_type: NullessByteStr<'_>,
319    ) -> Option<FsUseLabelAndType> {
320        self.parsed_policy
321            .fs_uses()
322            .iter()
323            .find(|fs_use| fs_use.fs_type() == fs_type.as_bytes())
324            .map(|fs_use| FsUseLabelAndType {
325                context: SecurityContext::new_from_policy_context(fs_use.context()),
326                use_type: fs_use.behavior(),
327            })
328    }
329
330    /// If there is a genfscon statement for the given filesystem type, returns the associated
331    /// [`SecurityContext`], taking the `node_path` into account. `class_id` defines the type
332    /// of the file in the given `node_path`. It can only be omitted when looking up the filesystem
333    /// label.
334    pub(super) fn genfscon_label_for_fs_and_path(
335        &self,
336        fs_type: NullessByteStr<'_>,
337        node_path: NullessByteStr<'_>,
338        class: Option<crate::KernelClass>,
339    ) -> Option<SecurityContext> {
340        let node_path = if class == Some(crate::FileClass::LnkFile.into())
341            && !self.parsed_policy.has_policycap(PolicyCap::GenfsSeclabelSymlinks)
342        {
343            // Symlinks receive the filesystem root label by default, rather than a label dependent on
344            // the `node_path`. Path based labels may be enabled with the "genfs_seclabel_symlinks"
345            // policy capability.
346            "/".into()
347        } else {
348            node_path
349        };
350
351        let class_id = class.and_then(|class| self.class(class.into())).map(|class| class.id());
352
353        // All contexts listed in the policy for the file system type.
354        let fs_contexts = self
355            .parsed_policy
356            .genfscon_find_all(std::str::from_utf8(fs_type.as_bytes()).expect("fs type is valid"));
357
358        #[derive(PartialEq)]
359        enum OrderType {
360            Alphabetic,
361            ByLength,
362            Unknown,
363        }
364        // The correct match is the closest parent among the ones given in the policy file.
365        // E.g. if in the policy we have
366        //     genfscon foofs "/" label1
367        //     genfscon foofs "/abc/" label2
368        //     genfscon foofs "/abc/def" label3
369        //
370        // The correct label for a file "/abc/def/g/h/i" is label3, as "/abc/def" is the closest parent
371        // among those defined.
372        //
373        // Partial paths are prefix-matched, so that "/abc/default" would also be assigned label3.
374        //
375        // TODO(372212126): Optimize the algorithm.
376        let mut result: Option<FsContext> = None;
377        let mut order_type = OrderType::Unknown;
378        let mut prev_path_bytes: Option<Vec<u8>> = None;
379        for fs_context in fs_contexts {
380            // Determine the order type based on the first entries.
381            let path = fs_context.partial_path();
382            if order_type == OrderType::Unknown {
383                if let Some(prev) = &prev_path_bytes {
384                    if path.len() > prev.len() {
385                        order_type = OrderType::Alphabetic;
386                    } else if path < prev.as_slice() {
387                        order_type = OrderType::ByLength;
388                    }
389                }
390                prev_path_bytes = Some(path.to_vec());
391            }
392
393            // Check if the class matches.
394            let class_matches = class_id.is_none()
395                || fs_context
396                    .class()
397                    .map(|other| other == class_id.unwrap().into())
398                    .unwrap_or(true);
399            if !class_matches {
400                continue;
401            }
402
403            if order_type == OrderType::Alphabetic && fs_context.partial_path() > node_path.0 {
404                // We know that:
405                // - We have alphabetic order,
406                // - The current path is lexicographically greater than our target path.
407                // We can infer that we have passed any potential prefixes in alphabetical order.
408                break;
409            }
410
411            if node_path.0.starts_with(fs_context.partial_path()) {
412                if result
413                    .as_ref()
414                    .map_or(true, |c| c.partial_path().len() < fs_context.partial_path().len())
415                {
416                    // The path matches, and it's the closest parent so far.
417                    result = Some(fs_context);
418                    if order_type == OrderType::ByLength {
419                        break;
420                    }
421                }
422            }
423        }
424
425        // The returned SecurityContext must be valid with respect to the policy, since otherwise
426        // we'd have rejected the policy load.
427        result.and_then(|fs_context| {
428            Some(SecurityContext::new_from_policy_context(fs_context.context()))
429        })
430    }
431
432    /// Helper used to construct and validate well-known [`SecurityContext`] values.
433    fn resolve_initial_context(&self, id: crate::InitialSid) -> SecurityContext {
434        SecurityContext::new_from_policy_context(self.parsed_policy.initial_context(id))
435    }
436
437    fn role_transition_new_role(
438        &self,
439        current_role: RoleId,
440        type_: TypeId,
441        class: &Class,
442    ) -> Option<RoleId> {
443        self.parsed_policy
444            .role_transitions()
445            .iter()
446            .find(|role_transition| {
447                role_transition.current_role() == current_role
448                    && role_transition.type_() == type_
449                    && role_transition.class() == class.id().into()
450            })
451            .map(|x| x.new_role())
452    }
453
454    #[allow(dead_code)]
455    // TODO(http://b/334968228): fn to be used again when checking role allow rules separately from
456    // SID calculation.
457    fn role_transition_is_explicitly_allowed(&self, source_role: RoleId, new_role: RoleId) -> bool {
458        self.parsed_policy
459            .role_allowlist()
460            .iter()
461            .find(|role_allow| {
462                role_allow.source_role() == source_role && role_allow.new_role() == new_role
463            })
464            .is_some()
465    }
466
467    fn type_transition_new_type_with_name(
468        &self,
469        source_type: TypeId,
470        target_type: TypeId,
471        class: &Class,
472        name: NullessByteStr<'_>,
473    ) -> Option<TypeId> {
474        self.parsed_policy.compute_filename_transition(
475            source_type,
476            target_type,
477            class.id().into(),
478            name,
479        )
480    }
481
482    fn range_transition_new_range(
483        &self,
484        source_type: TypeId,
485        target_type: TypeId,
486        class: &Class,
487    ) -> Option<(MlsLevel, Option<MlsLevel>)> {
488        for range_transition in self.parsed_policy.range_transitions() {
489            if range_transition.source_type() == source_type
490                && range_transition.target_type() == target_type
491                && range_transition.target_class() == class.id().into()
492            {
493                let mls_range = range_transition.mls_range();
494                let low_level = mls_range.low().clone();
495                let high_level = mls_range.high().clone();
496                return Some((low_level, high_level));
497            }
498        }
499
500        None
501    }
502}
503
504/// Returns the bit index of the specified permission for the specified security `class`, looking
505/// up the permission in the class' common symbol, if any.
506fn get_permission_id_by_name(
507    common_symbols: &IdAndNameIndexed<SymbolArray<CommonSymbol>>,
508    class: &Class,
509    name: &str,
510) -> Option<PermissionId> {
511    let name = name.as_bytes();
512    if let Some(permission) = class.permissions().iter().find(|p| p.name_bytes() == name) {
513        return Some(permission.id());
514    }
515    let common_name = class.common_name();
516    if !common_name.is_empty() {
517        let common_symbol = common_symbols.get_by_name(common_name)?;
518        let permission = common_symbol.permissions().iter().find(|p| p.name_bytes() == name)?;
519        return Some(permission.id());
520    }
521    None
522}
523
524impl Deref for PolicyIndex {
525    type Target = ParsedPolicy;
526
527    fn deref(&self) -> &Self::Target {
528        &self.parsed_policy
529    }
530}