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