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