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(super) 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::LnkFile.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            .genfscon_find_all(std::str::from_utf8(fs_type.as_bytes()).expect("fs type is valid"));
353
354        #[derive(PartialEq)]
355        enum OrderType {
356            Alphabetic,
357            ByLength,
358            Unknown,
359        }
360        // The correct match is the closest parent among the ones given in the policy file.
361        // E.g. if in the policy we have
362        //     genfscon foofs "/" label1
363        //     genfscon foofs "/abc/" label2
364        //     genfscon foofs "/abc/def" label3
365        //
366        // The correct label for a file "/abc/def/g/h/i" is label3, as "/abc/def" is the closest parent
367        // among those defined.
368        //
369        // Partial paths are prefix-matched, so that "/abc/default" would also be assigned label3.
370        //
371        // TODO(372212126): Optimize the algorithm.
372        let mut result: Option<FsContext> = None;
373        let mut order_type = OrderType::Unknown;
374        let mut prev_path_bytes: Option<Vec<u8>> = None;
375        for fs_context in fs_contexts {
376            // Determine the order type based on the first entries.
377            let path = fs_context.partial_path();
378            if order_type == OrderType::Unknown {
379                if let Some(prev) = &prev_path_bytes {
380                    if path.len() > prev.len() {
381                        order_type = OrderType::Alphabetic;
382                    } else if path < prev.as_slice() {
383                        order_type = OrderType::ByLength;
384                    }
385                }
386                prev_path_bytes = Some(path.to_vec());
387            }
388
389            // Check if the class matches.
390            let class_matches = class_id.is_none()
391                || fs_context.class().map(|other| other == class_id.unwrap()).unwrap_or(true);
392            if !class_matches {
393                continue;
394            }
395
396            if order_type == OrderType::Alphabetic && fs_context.partial_path() > node_path.0 {
397                // We know that:
398                // - We have alphabetic order,
399                // - The current path is lexicographically greater than our target path.
400                // We can infer that we have passed any potential prefixes in alphabetical order.
401                break;
402            }
403
404            if node_path.0.starts_with(fs_context.partial_path()) {
405                if result
406                    .as_ref()
407                    .map_or(true, |c| c.partial_path().len() < fs_context.partial_path().len())
408                {
409                    // The path matches, and it's the closest parent so far.
410                    result = Some(fs_context);
411                    if order_type == OrderType::ByLength {
412                        break;
413                    }
414                }
415            }
416        }
417
418        // The returned SecurityContext must be valid with respect to the policy, since otherwise
419        // we'd have rejected the policy load.
420        result.and_then(|fs_context| {
421            Some(SecurityContext::new_from_policy_context(fs_context.context()))
422        })
423    }
424
425    /// Helper used to construct and validate well-known [`SecurityContext`] values.
426    fn resolve_initial_context(&self, id: crate::InitialSid) -> SecurityContext {
427        SecurityContext::new_from_policy_context(self.parsed_policy().initial_context(id))
428    }
429
430    fn role_transition_new_role(
431        &self,
432        current_role: RoleId,
433        type_: TypeId,
434        class: &Class,
435    ) -> Option<RoleId> {
436        self.parsed_policy
437            .role_transitions()
438            .iter()
439            .find(|role_transition| {
440                role_transition.current_role() == current_role
441                    && role_transition.type_() == type_
442                    && role_transition.class() == class.id()
443            })
444            .map(|x| x.new_role())
445    }
446
447    #[allow(dead_code)]
448    // TODO(http://b/334968228): fn to be used again when checking role allow rules separately from
449    // SID calculation.
450    fn role_transition_is_explicitly_allowed(&self, source_role: RoleId, new_role: RoleId) -> bool {
451        self.parsed_policy
452            .role_allowlist()
453            .iter()
454            .find(|role_allow| {
455                role_allow.source_role() == source_role && role_allow.new_role() == new_role
456            })
457            .is_some()
458    }
459
460    fn type_transition_new_type_with_name(
461        &self,
462        source_type: TypeId,
463        target_type: TypeId,
464        class: &Class,
465        name: NullessByteStr<'_>,
466    ) -> Option<TypeId> {
467        self.parsed_policy.compute_filename_transition(source_type, target_type, class.id(), name)
468    }
469
470    fn range_transition_new_range(
471        &self,
472        source_type: TypeId,
473        target_type: TypeId,
474        class: &Class,
475    ) -> Option<(SecurityLevel, Option<SecurityLevel>)> {
476        for range_transition in self.parsed_policy.range_transitions() {
477            if range_transition.source_type() == source_type
478                && range_transition.target_type() == target_type
479                && range_transition.target_class() == class.id()
480            {
481                let mls_range = range_transition.mls_range();
482                let low_level = SecurityLevel::new_from_mls_level(mls_range.low());
483                let high_level = mls_range
484                    .high()
485                    .as_ref()
486                    .map(|high_level| SecurityLevel::new_from_mls_level(high_level));
487                return Some((low_level, high_level));
488            }
489        }
490
491        None
492    }
493}
494
495/// Returns the bit index of the specified permission for the specified security `class`, looking
496/// up the permission in the class' common symbol, if any.
497fn get_permission_id_by_name(
498    common_symbols: &CommonSymbols,
499    class: &Class,
500    name: &str,
501) -> Option<ClassPermissionId> {
502    let name = name.as_bytes();
503    if let Some(permission) = class.permissions().iter().find(|p| p.name_bytes() == name) {
504        return Some(permission.id());
505    }
506    let common_name = class.common_name_bytes();
507    if !common_name.is_empty() {
508        let common_symbol = common_symbols.iter().find(|cs| cs.name_bytes() == common_name)?;
509        let permission = common_symbol.permissions().iter().find(|p| p.name_bytes() == name)?;
510        return Some(permission.id());
511    }
512    None
513}