Skip to main content

selinux/policy/
parsed_policy.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::constraints::evaluate_constraint;
6use super::error::{ParseError, ValidateError};
7use super::parser::PolicyData;
8use super::security_context::SecurityContext;
9use super::{
10    AccessDecision, AccessVector, ClassId, SELINUX_AVD_FLAGS_PERMISSIVE, TypeId,
11    XpermsAccessDecision, XpermsKind,
12};
13use crate::PolicyCap;
14use crate::new_policy::rules::{
15    ExtendedPermissions, HasRuleKey, RuleKind, XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES,
16    XPERMS_TYPE_IOCTL_PREFIXES, XPERMS_TYPE_NLMSG, XpermsBitmap,
17};
18use crate::new_policy::traits::HasPolicyId;
19use crate::new_policy::{Class, GenfsConPath, NewPolicy};
20use std::ops::Deref;
21use std::sync::Arc;
22
23use anyhow::Context as _;
24use std::iter::Iterator;
25
26// As of 2026-01-30, more than five times larger than any policy seen in production or tests.
27const MAXIMUM_POLICY_SIZE: usize = 1 << 24;
28
29/// Parsed binary policy.
30#[derive(Debug)]
31pub struct ParsedPolicy {
32    /// [`NewPolicy`] that handles the policy tables and verification.
33    new_policy: Arc<NewPolicy>,
34}
35
36impl Deref for ParsedPolicy {
37    type Target = NewPolicy;
38    fn deref(&self) -> &Self::Target {
39        &self.new_policy
40    }
41}
42
43impl ParsedPolicy {
44    /// Returns true if the specified capability is in the policy's enabled capabilities set.
45    pub fn has_policycap(&self, policy_cap: PolicyCap) -> bool {
46        self.new_policy.policy_capabilities().contains(policy_cap)
47    }
48
49    /// Computes the access granted to `source_type` on `target_type`, for the specified
50    /// `target_class`. The result is a set of access vectors with bits set for each
51    /// `target_class` permission, describing which permissions are allowed, and
52    /// which should have access checks audit-logged when denied, or allowed.
53    ///
54    /// An [`AccessDecision`] is accumulated, starting from no permissions to be granted,
55    /// nor audit-logged if allowed, and all permissions to be audit-logged if denied.
56    /// Permissions that are explicitly `allow`ed, but that are subject to unsatisfied
57    /// constraints, are removed from the allowed set. Matching policy statements then
58    /// add permissions to the granted & audit-allow sets, or remove them from the
59    /// audit-deny set.
60    pub(super) fn compute_access_decision(
61        &self,
62        source_context: &SecurityContext,
63        target_context: &SecurityContext,
64        target_class: &Class,
65    ) -> AccessDecision {
66        let mut access_decision = self.compute_explicitly_allowed(
67            source_context.type_(),
68            target_context.type_(),
69            target_class,
70        );
71        access_decision.allow -=
72            self.compute_denied_by_constraints(source_context, target_context, target_class);
73        access_decision
74    }
75
76    /// Computes the access granted to `source_type` on `target_type`, for the specified
77    /// `target_class`. The result is a set of access vectors with bits set for each
78    /// `target_class` permission, describing which permissions are explicitly allowed,
79    /// and which should have access checks audit-logged when denied, or allowed.
80    pub(super) fn compute_explicitly_allowed(
81        &self,
82        source_type: TypeId,
83        target_type: TypeId,
84        target_class: &Class,
85    ) -> AccessDecision {
86        let target_class_id = target_class.id();
87
88        let mut computed_access_vector = AccessVector::NONE;
89        let mut computed_audit_allow = AccessVector::NONE;
90        let mut computed_audit_deny = AccessVector::ALL;
91
92        let source_attribute_set = &self.type_attribute_maps()[source_type];
93        let target_attribute_set = &self.type_attribute_maps()[target_type];
94
95        for source_id in source_attribute_set.iter() {
96            for target_id in target_attribute_set.iter() {
97                for rule in self.new_policy.access_vector_rules().find_av_rules(
98                    source_id,
99                    target_id,
100                    target_class_id,
101                ) {
102                    match rule.kind() {
103                        RuleKind::Allow => computed_access_vector |= rule.access_vector(),
104                        RuleKind::AuditAllow => computed_audit_allow |= rule.access_vector(),
105                        RuleKind::DontAudit => computed_audit_deny &= rule.access_vector(),
106                        _ => {}
107                    }
108                }
109            }
110        }
111
112        // If the `source_type` is bounded by some `parent_type` then bound the allowed permissions
113        // to those available to the parent. Doing the calculation here ensures that type-bounds
114        // take into account bounding ancestors, if any.
115        if let Some(parent) = self.types().get_by_id(source_type).unwrap().bounded_by() {
116            // If `source_type`==`target_type` then this is a "self" permission check, which should
117            // be bounded to the parent domain's "self" permissions.
118            let access = if source_type == target_type {
119                self.compute_explicitly_allowed(parent, parent, target_class)
120            } else {
121                self.compute_explicitly_allowed(parent, target_type, target_class)
122            };
123            computed_access_vector &= access.allow;
124        }
125
126        let mut flags = 0;
127        if self.permissive_map().contains(source_type) {
128            flags |= SELINUX_AVD_FLAGS_PERMISSIVE;
129        }
130        AccessDecision {
131            allow: computed_access_vector,
132            auditallow: computed_audit_allow,
133            auditdeny: computed_audit_deny,
134            flags,
135            todo_bug: None,
136        }
137    }
138
139    /// A permission is denied if it matches at least one unsatisfied constraint.
140    fn compute_denied_by_constraints(
141        &self,
142        source_context: &SecurityContext,
143        target_context: &SecurityContext,
144        target_class: &Class,
145    ) -> AccessVector {
146        let mut denied = AccessVector::NONE;
147        for constraint in target_class.constraints() {
148            if !evaluate_constraint(constraint.constraint_expr(), source_context, target_context) {
149                denied |= constraint.access_vector();
150            }
151        }
152        denied
153    }
154
155    /// Computes the access decision for set of extended permissions of a given kind and with a
156    /// given prefix byte, for a particular source and target context and target class.
157    pub(super) fn compute_xperms_access_decision(
158        &self,
159        xperms_kind: XpermsKind,
160        source_context: &SecurityContext,
161        target_context: &SecurityContext,
162        target_class: &Class,
163        xperms_prefix: u8,
164    ) -> XpermsAccessDecision {
165        let target_class_id = target_class.id();
166
167        let mut explicit_allow: Option<XpermsBitmap> = None;
168        let mut auditallow = XpermsBitmap::NONE;
169        let mut auditdeny = XpermsBitmap::ALL;
170
171        let xperms_types = match xperms_kind {
172            XpermsKind::Ioctl => {
173                [XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES, XPERMS_TYPE_IOCTL_PREFIXES].as_slice()
174            }
175            XpermsKind::Nlmsg => [XPERMS_TYPE_NLMSG].as_slice(),
176        };
177        let bitmap_if_prefix_matches =
178            |xperms_prefix: u8, xperms: &ExtendedPermissions| match xperms_kind {
179                XpermsKind::Ioctl => match xperms.xperms_type() {
180                    XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES => (xperms.xperms_optional_prefix()
181                        == xperms_prefix)
182                        .then_some(*xperms.xperms_bitmap()),
183                    XPERMS_TYPE_IOCTL_PREFIXES => {
184                        xperms.xperms_bitmap().contains(xperms_prefix).then_some(XpermsBitmap::ALL)
185                    }
186                    _ => None,
187                },
188                XpermsKind::Nlmsg => match xperms.xperms_type() {
189                    XPERMS_TYPE_NLMSG => (xperms.xperms_optional_prefix() == xperms_prefix)
190                        .then_some(*xperms.xperms_bitmap()),
191                    _ => None,
192                },
193            };
194
195        let source_attribute_set = &self.type_attribute_maps()[source_context.type_()];
196        let target_attribute_set = &self.type_attribute_maps()[target_context.type_()];
197
198        for source_id in source_attribute_set.iter() {
199            for target_id in target_attribute_set.iter() {
200                for rule in self.new_policy.access_vector_rules().find_xperm_rules(
201                    source_id,
202                    target_id,
203                    target_class_id,
204                ) {
205                    let xperms = rule.extended_permissions();
206                    if rule.kind() == RuleKind::AllowXperm
207                        && xperms_types.contains(&xperms.xperms_type())
208                    {
209                        explicit_allow.get_or_insert(XpermsBitmap::NONE);
210                    }
211
212                    if let Some(xperms_bitmap) = bitmap_if_prefix_matches(xperms_prefix, xperms) {
213                        match rule.kind() {
214                            RuleKind::AllowXperm => {
215                                (*explicit_allow.get_or_insert(XpermsBitmap::NONE)) |=
216                                    xperms_bitmap;
217                            }
218                            RuleKind::AuditAllowXperm => auditallow |= xperms_bitmap,
219                            RuleKind::DontAuditXperm => auditdeny -= xperms_bitmap,
220                            _ => {}
221                        }
222                    }
223                }
224            }
225        }
226        let allow = explicit_allow.unwrap_or(XpermsBitmap::ALL);
227        XpermsAccessDecision { allow, auditallow, auditdeny }
228    }
229
230    pub(super) fn genfscon_find_all<'a>(
231        &'a self,
232        fs_type: &'a [u8],
233    ) -> impl Iterator<Item = &'a GenfsConPath> {
234        self.generic_fs_contexts()
235            .iter()
236            .filter(move |entry| entry.fs_type() == fs_type)
237            .flat_map(|entry| entry.paths().iter())
238    }
239
240    pub(super) fn compute_filename_transition(
241        &self,
242        source_type: TypeId,
243        target_type: TypeId,
244        class: ClassId,
245        name: &[u8],
246    ) -> Option<TypeId> {
247        self.new_policy.filename_transitions().compute_filename_transition(
248            source_type,
249            target_type,
250            class,
251            name,
252        )
253    }
254
255    pub(super) fn initial_context(&self, mut id: crate::InitialSid) -> &crate::new_policy::Context {
256        let need_init_sid = self.has_policycap(PolicyCap::UserspaceInitialContext);
257        if id == crate::InitialSid::Init && !need_init_sid {
258            id = crate::InitialSid::Kernel;
259        }
260        self.new_policy
261            .initial_sids()
262            .get_by_id(id as u32)
263            .expect("initial SID must be present in validated policy")
264    }
265}
266
267impl ParsedPolicy {
268    /// Parses the binary policy stored in `bytes`. It is an error for `bytes` to have trailing
269    /// bytes after policy parsing completes.
270    pub(super) fn parse(data: PolicyData) -> Result<Self, anyhow::Error> {
271        let policy_size = data.len();
272        if MAXIMUM_POLICY_SIZE <= policy_size {
273            return Err(anyhow::Error::from(ParseError::UnsupportedlyLarge {
274                observed: policy_size,
275                limit: MAXIMUM_POLICY_SIZE,
276            }));
277        }
278        let new_policy =
279            NewPolicy::parse(&data).map_err(|e| anyhow::anyhow!("new parser failed: {:?}", e))?;
280        new_policy.validate().context("validating new policy structure")?;
281
282        Ok(ParsedPolicy { new_policy: Arc::new(new_policy) })
283    }
284}
285
286impl ParsedPolicy {
287    pub fn validate(&self) -> Result<(), anyhow::Error> {
288        // Validate that all kernel-required initial SIDs are present in the policy.
289        let need_init_sid = self.has_policycap(PolicyCap::UserspaceInitialContext);
290        for initial_sid in crate::InitialSid::all_variants() {
291            if *initial_sid == crate::InitialSid::Init && !need_init_sid {
292                continue;
293            }
294            self.new_policy
295                .initial_sids()
296                .get_by_id(*initial_sid as u32)
297                .ok_or(ValidateError::MissingInitialSid { initial_sid: *initial_sid })?;
298        }
299
300        // To-do comments for cross-policy validations yet to be implemented go here.
301        // TODO(b/356569876): Determine which "bounds" should be verified for correctness here.
302
303        Ok(())
304    }
305}
306
307#[cfg(test)]
308mod tests {
309    use super::*;
310    use crate::new_policy::TypeSet;
311    use crate::new_policy::traits::PolicyId;
312    use crate::policy::Parse;
313    use crate::policy::parser::PolicyCursor;
314    use std::sync::Arc;
315
316    #[test]
317    fn test_id_set_parse_compatibility() {
318        let bytes = [
319            64, 0, 0, 0, // map_item_size_bits = 64
320            128, 0, 0, 0, // high_bit = 128
321            2, 0, 0, 0, // count = 2
322            // Item 1
323            0, 0, 0, 0, // start_bit = 0
324            5, 0, 0, 0, 0, 0, 0, 0, // map = 5 (bits 0 and 2 set)
325            // Item 2
326            64, 0, 0, 0, // start_bit = 64
327            2, 0, 0, 0, 0, 0, 0, 0, // map = 2 (bit 65 set)
328        ];
329        let data: PolicyData = Arc::from(bytes);
330        let cursor = PolicyCursor::new(&data);
331        let (id_set, tail) = TypeSet::parse(cursor).unwrap();
332        assert_eq!(tail.offset(), bytes.len() as u32);
333        assert!(id_set.contains(TypeId::from_u32(1).unwrap()));
334        assert!(!id_set.contains(TypeId::from_u32(2).unwrap()));
335        assert!(id_set.contains(TypeId::from_u32(3).unwrap()));
336        assert!(id_set.contains(TypeId::from_u32(66).unwrap()));
337    }
338}