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::arrays::{
6    Context, DeprecatedFilenameTransition, FilenameTransition, FilenameTransitionList, FsUse,
7    GenericFsContext, IPv6Node, InfinitiBandEndPort, InfinitiBandPartitionKey, InitialSid,
8    MIN_POLICY_VERSION_FOR_INFINITIBAND_PARTITION_KEY, NamedContextPair, Node, Port,
9    RangeTransition, SimpleArray,
10};
11use super::error::{ParseError, ValidateError};
12use crate::new_policy::TypeSet;
13use crate::new_policy::bitmap::IdSet;
14
15use super::constraints::evaluate_constraint;
16use super::parser::{PolicyCursor, PolicyData};
17use super::security_context::SecurityContext;
18use super::view::Hashable;
19use super::{
20    AccessDecision, AccessVector, CategoryId, ClassId, MlsLevel, Parse, PolicyValidationContext,
21    RoleId, SELINUX_AVD_FLAGS_PERMISSIVE, SensitivityId, TypeId, UserId, Validate,
22    XpermsAccessDecision, XpermsKind,
23};
24
25use crate::new_policy::rules::{
26    ExtendedPermissions, HasRuleKey, RuleKind, XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES,
27    XPERMS_TYPE_IOCTL_PREFIXES, XPERMS_TYPE_NLMSG, XpermsBitmap,
28};
29use crate::new_policy::traits::{HasPolicyId, PolicyId};
30use crate::new_policy::{Class, NewPolicy};
31use crate::policy::arrays::FsContext;
32use crate::policy::view::CustomKeyHashedView;
33use crate::{NullessByteStr, PolicyCap};
34use std::ops::Deref;
35use std::sync::Arc;
36
37use anyhow::Context as _;
38use std::collections::HashSet;
39use std::fmt::Debug;
40use std::hash::Hash;
41use std::iter::Iterator;
42use zerocopy::little_endian as le;
43
44// As of 2026-01-30, more than five times larger than any policy seen in production or tests.
45const MAXIMUM_POLICY_SIZE: usize = 1 << 24;
46
47/// Parsed binary policy.
48#[derive(Debug)]
49pub struct ParsedPolicy {
50    /// Raw policy data (remaining).
51    data: PolicyData,
52
53    /// [`NewPolicy`] that handles the header and base tables.
54    new_policy: Arc<NewPolicy>,
55
56    filename_transition_list: FilenameTransitionList,
57    initial_sids: SimpleArray<InitialSid>,
58    filesystems: SimpleArray<NamedContextPair>,
59    ports: SimpleArray<Port>,
60    network_interfaces: SimpleArray<NamedContextPair>,
61    nodes: SimpleArray<Node>,
62    fs_uses: SimpleArray<FsUse>,
63    ipv6_nodes: SimpleArray<IPv6Node>,
64    infinitiband_partition_keys: Option<SimpleArray<InfinitiBandPartitionKey>>,
65    infinitiband_end_ports: Option<SimpleArray<InfinitiBandEndPort>>,
66    /// A set of labeling statements to apply to given filesystems and/or their subdirectories.
67    /// Corresponds to the `genfscon` labeling statement in the policy.
68    generic_fs_contexts: CustomKeyHashedView<GenericFsContext>,
69    range_transitions: SimpleArray<RangeTransition>,
70    /// Extensible bitmaps that encode associations between types and attributes.
71    attribute_maps: Vec<TypeSet>,
72}
73
74impl Deref for ParsedPolicy {
75    type Target = NewPolicy;
76    fn deref(&self) -> &Self::Target {
77        &self.new_policy
78    }
79}
80
81impl ParsedPolicy {
82    /// Returns true if the specified capability is in the policy's enabled capabilities set.
83    pub fn has_policycap(&self, policy_cap: PolicyCap) -> bool {
84        self.new_policy.policy_capabilities().contains(policy_cap)
85    }
86
87    /// Computes the access granted to `source_type` on `target_type`, for the specified
88    /// `target_class`. The result is a set of access vectors with bits set for each
89    /// `target_class` permission, describing which permissions are allowed, and
90    /// which should have access checks audit-logged when denied, or allowed.
91    ///
92    /// An [`AccessDecision`] is accumulated, starting from no permissions to be granted,
93    /// nor audit-logged if allowed, and all permissions to be audit-logged if denied.
94    /// Permissions that are explicitly `allow`ed, but that are subject to unsatisfied
95    /// constraints, are removed from the allowed set. Matching policy statements then
96    /// add permissions to the granted & audit-allow sets, or remove them from the
97    /// audit-deny set.
98    pub(super) fn compute_access_decision(
99        &self,
100        source_context: &SecurityContext,
101        target_context: &SecurityContext,
102        target_class: &Class,
103    ) -> AccessDecision {
104        let mut access_decision = self.compute_explicitly_allowed(
105            source_context.type_(),
106            target_context.type_(),
107            target_class,
108        );
109        access_decision.allow -=
110            self.compute_denied_by_constraints(source_context, target_context, target_class);
111        access_decision
112    }
113
114    /// Computes the access granted to `source_type` on `target_type`, for the specified
115    /// `target_class`. The result is a set of access vectors with bits set for each
116    /// `target_class` permission, describing which permissions are explicitly allowed,
117    /// and which should have access checks audit-logged when denied, or allowed.
118    pub(super) fn compute_explicitly_allowed(
119        &self,
120        source_type: TypeId,
121        target_type: TypeId,
122        target_class: &Class,
123    ) -> AccessDecision {
124        let target_class_id = target_class.id();
125
126        let mut computed_access_vector = AccessVector::NONE;
127        let mut computed_audit_allow = AccessVector::NONE;
128        let mut computed_audit_deny = AccessVector::ALL;
129
130        let source_attribute_set: &TypeSet =
131            &self.attribute_maps[(source_type.as_u32() - 1) as usize];
132        let target_attribute_set: &TypeSet =
133            &self.attribute_maps[(target_type.as_u32() - 1) as usize];
134
135        for source_id in source_attribute_set.iter() {
136            for target_id in target_attribute_set.iter() {
137                for rule in self.new_policy.access_vector_rules().find_av_rules(
138                    source_id,
139                    target_id,
140                    target_class_id,
141                ) {
142                    match rule.kind() {
143                        RuleKind::Allow => computed_access_vector |= rule.access_vector(),
144                        RuleKind::AuditAllow => computed_audit_allow |= rule.access_vector(),
145                        RuleKind::DontAudit => computed_audit_deny &= rule.access_vector(),
146                        _ => {}
147                    }
148                }
149            }
150        }
151
152        // If the `source_type` is bounded by some `parent_type` then bound the allowed permissions
153        // to those available to the parent. Doing the calculation here ensures that type-bounds
154        // take into account bounding ancestors, if any.
155        if let Some(parent) = self.types().get_by_id(source_type).unwrap().bounded_by() {
156            // If `source_type`==`target_type` then this is a "self" permission check, which should
157            // be bounded to the parent domain's "self" permissions.
158            let access = if source_type == target_type {
159                self.compute_explicitly_allowed(parent, parent, target_class)
160            } else {
161                self.compute_explicitly_allowed(parent, target_type, target_class)
162            };
163            computed_access_vector &= access.allow;
164        }
165
166        let mut flags = 0;
167        if self.permissive_map().contains(source_type) {
168            flags |= SELINUX_AVD_FLAGS_PERMISSIVE;
169        }
170        AccessDecision {
171            allow: computed_access_vector,
172            auditallow: computed_audit_allow,
173            auditdeny: computed_audit_deny,
174            flags,
175            todo_bug: None,
176        }
177    }
178
179    /// A permission is denied if it matches at least one unsatisfied constraint.
180    fn compute_denied_by_constraints(
181        &self,
182        source_context: &SecurityContext,
183        target_context: &SecurityContext,
184        target_class: &Class,
185    ) -> AccessVector {
186        let mut denied = AccessVector::NONE;
187        for constraint in target_class.constraints() {
188            if !evaluate_constraint(constraint.constraint_expr(), source_context, target_context) {
189                denied |= constraint.access_vector();
190            }
191        }
192        denied
193    }
194
195    /// Computes the access decision for set of extended permissions of a given kind and with a
196    /// given prefix byte, for a particular source and target context and target class.
197    pub(super) fn compute_xperms_access_decision(
198        &self,
199        xperms_kind: XpermsKind,
200        source_context: &SecurityContext,
201        target_context: &SecurityContext,
202        target_class: &Class,
203        xperms_prefix: u8,
204    ) -> XpermsAccessDecision {
205        let target_class_id = target_class.id();
206
207        let mut explicit_allow: Option<XpermsBitmap> = None;
208        let mut auditallow = XpermsBitmap::NONE;
209        let mut auditdeny = XpermsBitmap::ALL;
210
211        let xperms_types = match xperms_kind {
212            XpermsKind::Ioctl => {
213                [XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES, XPERMS_TYPE_IOCTL_PREFIXES].as_slice()
214            }
215            XpermsKind::Nlmsg => [XPERMS_TYPE_NLMSG].as_slice(),
216        };
217        let bitmap_if_prefix_matches =
218            |xperms_prefix: u8, xperms: &ExtendedPermissions| match xperms_kind {
219                XpermsKind::Ioctl => match xperms.xperms_type() {
220                    XPERMS_TYPE_IOCTL_PREFIX_AND_POSTFIXES => (xperms.xperms_optional_prefix()
221                        == xperms_prefix)
222                        .then_some(*xperms.xperms_bitmap()),
223                    XPERMS_TYPE_IOCTL_PREFIXES => {
224                        xperms.xperms_bitmap().contains(xperms_prefix).then_some(XpermsBitmap::ALL)
225                    }
226                    _ => None,
227                },
228                XpermsKind::Nlmsg => match xperms.xperms_type() {
229                    XPERMS_TYPE_NLMSG => (xperms.xperms_optional_prefix() == xperms_prefix)
230                        .then_some(*xperms.xperms_bitmap()),
231                    _ => None,
232                },
233            };
234
235        let source_attribute_set: &TypeSet =
236            &self.attribute_maps[(source_context.type_().as_u32() - 1) as usize];
237        let target_attribute_set: &TypeSet =
238            &self.attribute_maps[(target_context.type_().as_u32() - 1) as usize];
239
240        for source_id in source_attribute_set.iter() {
241            for target_id in target_attribute_set.iter() {
242                for rule in self.new_policy.access_vector_rules().find_xperm_rules(
243                    source_id,
244                    target_id,
245                    target_class_id,
246                ) {
247                    let xperms = rule.extended_permissions();
248                    if rule.kind() == RuleKind::AllowXperm
249                        && xperms_types.contains(&xperms.xperms_type())
250                    {
251                        explicit_allow.get_or_insert(XpermsBitmap::NONE);
252                    }
253
254                    if let Some(xperms_bitmap) = bitmap_if_prefix_matches(xperms_prefix, xperms) {
255                        match rule.kind() {
256                            RuleKind::AllowXperm => {
257                                (*explicit_allow.get_or_insert(XpermsBitmap::NONE)) |=
258                                    xperms_bitmap;
259                            }
260                            RuleKind::AuditAllowXperm => auditallow |= xperms_bitmap,
261                            RuleKind::DontAuditXperm => auditdeny -= xperms_bitmap,
262                            _ => {}
263                        }
264                    }
265                }
266            }
267        }
268        let allow = explicit_allow.unwrap_or(XpermsBitmap::ALL);
269        XpermsAccessDecision { allow, auditallow, auditdeny }
270    }
271
272    /// Returns the policy entry for the specified initial Security Context.
273    pub(super) fn initial_context(&self, mut id: crate::InitialSid) -> &Context {
274        // If "userspace_initial_context" is not set then the "init" SID is treated as "kernel".
275        if id == crate::InitialSid::Init && !self.has_policycap(PolicyCap::UserspaceInitialContext)
276        {
277            id = crate::InitialSid::Kernel
278        }
279
280        // [`InitialSids`] validates that all `InitialSid` values are defined by the policy.
281        let id = le::U32::from(id as u32);
282        &self.initial_sids.data.iter().find(|initial| initial.id() == id).unwrap().context()
283    }
284
285    pub(super) fn fs_uses(&self) -> &[FsUse] {
286        &self.fs_uses.data
287    }
288
289    pub(super) fn genfscon_find_all(&self, fs_type: &str) -> impl Iterator<Item = FsContext> {
290        let query = GenericFsContext::for_query(fs_type);
291        self.generic_fs_contexts.find_all(query, &self.data)
292    }
293
294    pub(super) fn range_transitions(&self) -> &[RangeTransition] {
295        &self.range_transitions.data
296    }
297
298    pub(super) fn compute_filename_transition(
299        &self,
300        source_type: TypeId,
301        target_type: TypeId,
302        class: ClassId,
303        name: NullessByteStr<'_>,
304    ) -> Option<TypeId> {
305        match &self.filename_transition_list {
306            FilenameTransitionList::PolicyVersionGeq33(list) => {
307                let entry = list.data.iter().find(|transition| {
308                    transition.target_type() == target_type
309                        && transition.target_class() == class
310                        && transition.name_bytes() == name.as_bytes()
311                })?;
312                entry
313                    .outputs()
314                    .iter()
315                    .find(|entry| entry.has_source_type(source_type))
316                    .map(|x| x.out_type())
317            }
318            FilenameTransitionList::PolicyVersionLeq32(list) => list
319                .data
320                .iter()
321                .find(|transition| {
322                    transition.target_class() == class
323                        && transition.target_type() == target_type
324                        && transition.source_type() == source_type
325                        && transition.name_bytes() == name.as_bytes()
326                })
327                .map(|x| x.out_type()),
328        }
329    }
330
331    // Validate that all sensitivity and category IDs referenced in the MLS level are
332    // defined.
333    fn validate_mls_level(
334        &self,
335        level: &MlsLevel,
336        sensitivity_ids: &HashSet<SensitivityId>,
337        category_ids: &HashSet<CategoryId>,
338    ) -> Result<(), anyhow::Error> {
339        validate_id(sensitivity_ids, level.sensitivity(), "sensitivity")?;
340        for id in level.category_ids() {
341            validate_id(category_ids, id, "category")?;
342        }
343        Ok(())
344    }
345
346    // Validate an MLS range statement against sets of defined sensitivity and category
347    // IDs:
348    // - Verify that all sensitivity and category IDs referenced in the MLS levels are
349    //   defined.
350    // - Verify that the range is internally consistent; i.e., the high level (if any)
351    //   dominates the low level.
352    fn validate_mls_range(
353        &self,
354        low_level: &MlsLevel,
355        high_level: &Option<MlsLevel>,
356        sensitivity_ids: &HashSet<SensitivityId>,
357        category_ids: &HashSet<CategoryId>,
358    ) -> Result<(), anyhow::Error> {
359        self.validate_mls_level(low_level, sensitivity_ids, category_ids)?;
360        if let Some(high) = high_level {
361            self.validate_mls_level(high, sensitivity_ids, category_ids)?;
362            if !high.dominates(low_level) {
363                return Err(ValidateError::InvalidMlsRange {
364                    low: low_level.to_string(self).into(),
365                    high: high.to_string(self).into(),
366                }
367                .into());
368            }
369        }
370        Ok(())
371    }
372
373    fn validate_context(
374        &self,
375        context: &Context,
376        user_ids: &HashSet<UserId>,
377        role_ids: &HashSet<RoleId>,
378        type_ids: &HashSet<TypeId>,
379        sensitivity_ids: &HashSet<SensitivityId>,
380        category_ids: &HashSet<CategoryId>,
381    ) -> Result<(), anyhow::Error> {
382        validate_id(user_ids, context.user_id(), "user")?;
383        validate_id(role_ids, context.role_id(), "role")?;
384        validate_id(type_ids, context.type_id(), "type")?;
385        self.validate_mls_range(
386            context.low_level(),
387            context.high_level(),
388            sensitivity_ids,
389            category_ids,
390        )?;
391        Ok(())
392    }
393}
394
395impl ParsedPolicy {
396    /// Parses the binary policy stored in `bytes`. It is an error for `bytes` to have trailing
397    /// bytes after policy parsing completes.
398    pub(super) fn parse(data: PolicyData) -> Result<Self, anyhow::Error> {
399        let policy_size = data.len();
400        if MAXIMUM_POLICY_SIZE <= policy_size {
401            return Err(anyhow::Error::from(ParseError::UnsupportedlyLarge {
402                observed: policy_size,
403                limit: MAXIMUM_POLICY_SIZE,
404            }));
405        }
406        let new_policy =
407            NewPolicy::parse(&data).map_err(|e| anyhow::anyhow!("new parser failed: {:?}", e))?;
408        new_policy.validate().context("validating new policy structure")?;
409
410        let rest_data = new_policy.rest_bytes();
411        let (policy, excess_bytes) = parse_policy_remaining(new_policy, rest_data)?;
412        if excess_bytes > 0 {
413            return Err(anyhow::Error::from(ParseError::TrailingBytes { num_bytes: excess_bytes }));
414        }
415        Ok(policy)
416    }
417}
418
419/// Parses the remaining parts of the policy from `rest_data` to construct a [`ParsedPolicy`].
420fn parse_policy_remaining(
421    new_policy: NewPolicy,
422    rest_data: PolicyData,
423) -> Result<(ParsedPolicy, usize), anyhow::Error> {
424    let tail = PolicyCursor::new(&rest_data);
425
426    let (filename_transition_list, tail) = if new_policy.policy_version() >= 33 {
427        let (filename_transition_list, tail) = SimpleArray::<FilenameTransition>::parse(tail)
428            .map_err(Into::<anyhow::Error>::into)
429            .context("parsing standard filename transitions")?;
430        (FilenameTransitionList::PolicyVersionGeq33(filename_transition_list), tail)
431    } else {
432        let (filename_transition_list, tail) =
433            SimpleArray::<DeprecatedFilenameTransition>::parse(tail)
434                .map_err(Into::<anyhow::Error>::into)
435                .context("parsing deprecated filename transitions")?;
436        (FilenameTransitionList::PolicyVersionLeq32(filename_transition_list), tail)
437    };
438
439    let (initial_sids, tail) = SimpleArray::<InitialSid>::parse(tail)
440        .map_err(Into::<anyhow::Error>::into)
441        .context("parsing initial sids")?;
442
443    let (filesystems, tail) = SimpleArray::<NamedContextPair>::parse(tail)
444        .map_err(Into::<anyhow::Error>::into)
445        .context("parsing filesystem contexts")?;
446
447    let (ports, tail) = SimpleArray::<Port>::parse(tail)
448        .map_err(Into::<anyhow::Error>::into)
449        .context("parsing ports")?;
450
451    let (network_interfaces, tail) = SimpleArray::<NamedContextPair>::parse(tail)
452        .map_err(Into::<anyhow::Error>::into)
453        .context("parsing network interfaces")?;
454
455    let (nodes, tail) = SimpleArray::<Node>::parse(tail)
456        .map_err(Into::<anyhow::Error>::into)
457        .context("parsing nodes")?;
458
459    let (fs_uses, tail) = SimpleArray::<FsUse>::parse(tail)
460        .map_err(Into::<anyhow::Error>::into)
461        .context("parsing fs uses")?;
462
463    let (ipv6_nodes, tail) = SimpleArray::<IPv6Node>::parse(tail)
464        .map_err(Into::<anyhow::Error>::into)
465        .context("parsing ipv6 nodes")?;
466
467    let (infinitiband_partition_keys, infinitiband_end_ports, tail) =
468        if new_policy.policy_version() >= MIN_POLICY_VERSION_FOR_INFINITIBAND_PARTITION_KEY {
469            let (infinity_band_partition_keys, tail) =
470                SimpleArray::<InfinitiBandPartitionKey>::parse(tail)
471                    .map_err(Into::<anyhow::Error>::into)
472                    .context("parsing infiniti band partition keys")?;
473            let (infinitiband_end_ports, tail) = SimpleArray::<InfinitiBandEndPort>::parse(tail)
474                .map_err(Into::<anyhow::Error>::into)
475                .context("parsing infiniti band end ports")?;
476            (Some(infinity_band_partition_keys), Some(infinitiband_end_ports), tail)
477        } else {
478            (None, None, tail)
479        };
480
481    let (generic_fs_contexts, tail) = CustomKeyHashedView::<GenericFsContext>::parse(tail)
482        .map_err(Into::<anyhow::Error>::into)
483        .context("parsing generic filesystem contexts")?;
484
485    let (range_transitions, tail) = SimpleArray::<RangeTransition>::parse(tail)
486        .map_err(Into::<anyhow::Error>::into)
487        .context("parsing range transitions")?;
488
489    let primary_names_count = new_policy.types().primary_names_count();
490    let mut attribute_maps = Vec::with_capacity(primary_names_count as usize);
491    let mut tail = tail;
492
493    for i in 0..primary_names_count {
494        let (item, next_tail) = TypeSet::parse(tail)
495            .map_err(Into::<anyhow::Error>::into)
496            .with_context(|| format!("parsing {}th attribute map", i))?;
497        attribute_maps.push(item);
498        tail = next_tail;
499    }
500    let tail = tail;
501    let attribute_maps = attribute_maps;
502
503    let excess_bytes = rest_data.len() - tail.offset() as usize;
504
505    Ok((
506        ParsedPolicy {
507            data: rest_data,
508            new_policy: Arc::new(new_policy),
509
510            filename_transition_list,
511            initial_sids,
512            filesystems,
513            ports,
514            network_interfaces,
515            nodes,
516            fs_uses,
517            ipv6_nodes,
518            infinitiband_partition_keys,
519            infinitiband_end_ports,
520            generic_fs_contexts,
521            range_transitions,
522            attribute_maps,
523        },
524        excess_bytes,
525    ))
526}
527
528impl ParsedPolicy {
529    pub fn validate(&self) -> Result<(), anyhow::Error> {
530        let need_init_sid = self.has_policycap(PolicyCap::UserspaceInitialContext);
531        let context = PolicyValidationContext {
532            data: self.data.clone(),
533            need_init_sid,
534            new_policy: self.new_policy.clone(),
535        };
536
537        self.filename_transition_list
538            .validate(&context)
539            .map_err(Into::<anyhow::Error>::into)
540            .context("validating filename_transition_list")?;
541        self.initial_sids
542            .validate(&context)
543            .map_err(Into::<anyhow::Error>::into)
544            .context("validating initial_sids")?;
545        self.filesystems
546            .validate(&context)
547            .map_err(Into::<anyhow::Error>::into)
548            .context("validating filesystems")?;
549        self.ports
550            .validate(&context)
551            .map_err(Into::<anyhow::Error>::into)
552            .context("validating ports")?;
553        self.network_interfaces
554            .validate(&context)
555            .map_err(Into::<anyhow::Error>::into)
556            .context("validating network_interfaces")?;
557        self.nodes
558            .validate(&context)
559            .map_err(Into::<anyhow::Error>::into)
560            .context("validating nodes")?;
561        self.fs_uses
562            .validate(&context)
563            .map_err(Into::<anyhow::Error>::into)
564            .context("validating fs_uses")?;
565        self.ipv6_nodes
566            .validate(&context)
567            .map_err(Into::<anyhow::Error>::into)
568            .context("validating ipv6 nodes")?;
569        self.infinitiband_partition_keys
570            .validate(&context)
571            .map_err(Into::<anyhow::Error>::into)
572            .context("validating infinitiband_partition_keys")?;
573        self.infinitiband_end_ports
574            .validate(&context)
575            .map_err(Into::<anyhow::Error>::into)
576            .context("validating infinitiband_end_ports")?;
577        self.generic_fs_contexts
578            .validate(&context)
579            .map_err(Into::<anyhow::Error>::into)
580            .context("validating generic_fs_contexts")?;
581        self.range_transitions
582            .validate(&context)
583            .map_err(Into::<anyhow::Error>::into)
584            .context("validating range_transitions")?;
585        self.attribute_maps
586            .validate(&context)
587            .map_err(Into::<anyhow::Error>::into)
588            .context("validating attribute_maps")?;
589
590        // Collate the sets of user, role, type, sensitivity and category Ids.
591        let user_ids: HashSet<UserId> = self.new_policy.users().iter().map(|x| x.id()).collect();
592        let role_ids: HashSet<RoleId> = self.roles().iter().map(|x| x.id()).collect();
593        let type_ids: HashSet<TypeId> = self.new_policy.types().iter().map(|t| t.id()).collect();
594        let sensitivity_ids: HashSet<SensitivityId> =
595            self.new_policy.sensitivities().iter().map(|x| x.id()).collect();
596        let category_ids: HashSet<CategoryId> =
597            self.new_policy.categories().iter().map(|x| x.id()).collect();
598
599        // Validate that initial contexts use only defined user, role, type, etc Ids.
600        // Check that all sensitivity and category IDs are defined and that MLS levels
601        // are internally consistent.
602        for initial_sid in &self.initial_sids.data {
603            self.validate_context(
604                initial_sid.context(),
605                &user_ids,
606                &role_ids,
607                &type_ids,
608                &sensitivity_ids,
609                &category_ids,
610            )?;
611        }
612
613        // Validate that contexts specified in filesystem labeling rules only use
614        // policy-defined Ids for their fields. Check that MLS levels are internally
615        // consistent.
616        for fs_use in &self.fs_uses.data {
617            self.validate_context(
618                fs_use.context(),
619                &user_ids,
620                &role_ids,
621                &type_ids,
622                &sensitivity_ids,
623                &category_ids,
624            )?;
625        }
626
627        // Validate that contexts specified in genfscon rules only use
628        // policy-defined Ids for their fields. Check that MLS levels are internally
629        // consistent.
630        for entry in self.generic_fs_contexts.iter(&self.data) {
631            let entry = entry?;
632            for fs_context_view in entry.values().data().iter(&self.data) {
633                let fs_context = fs_context_view.parse(&self.data);
634                self.validate_context(
635                    fs_context.context(),
636                    &user_ids,
637                    &role_ids,
638                    &type_ids,
639                    &sensitivity_ids,
640                    &category_ids,
641                )?;
642            }
643        }
644
645        // To-do comments for cross-policy validations yet to be implemented go here.
646        // TODO(b/356569876): Determine which "bounds" should be verified for correctness here.
647
648        Ok(())
649    }
650}
651
652fn validate_id<IdType: Debug + Eq + Hash>(
653    id_set: &HashSet<IdType>,
654    id: IdType,
655    debug_kind: &'static str,
656) -> Result<(), anyhow::Error> {
657    if !id_set.contains(&id) {
658        return Err(ValidateError::UnknownId { kind: debug_kind, id: format!("{:?}", id) }.into());
659    }
660    Ok(())
661}
662
663impl<T: PolicyId, const WITH_ID_ZERO: bool> Parse for IdSet<T, WITH_ID_ZERO> {
664    type Error = anyhow::Error;
665
666    fn parse<'a>(cursor: PolicyCursor<'a>) -> Result<(Self, PolicyCursor<'a>), Self::Error> {
667        let offset = cursor.offset() as usize;
668        let slice = &cursor.data().as_ref()[offset..];
669        let mut new_cursor = crate::new_policy::parser::PolicyCursor::new(slice);
670        let id_set = <Self as crate::new_policy::traits::Parse>::parse(&mut new_cursor)
671            .map_err(|e| anyhow::anyhow!("Parse error: {:?}", e))?;
672        let bytes_parsed = new_cursor.offset();
673        let new_offset = cursor.offset() + bytes_parsed as u32;
674        Ok((id_set, PolicyCursor::new_at(cursor.data(), new_offset)))
675    }
676}
677
678impl<T: PolicyId + crate::new_policy::traits::Validate, const WITH_ID_ZERO: bool> Validate
679    for IdSet<T, WITH_ID_ZERO>
680{
681    type Error = anyhow::Error;
682
683    fn validate(&self, context: &PolicyValidationContext) -> Result<(), Self::Error> {
684        crate::new_policy::traits::Validate::validate(self, &context.new_policy).map_err(Into::into)
685    }
686}
687
688#[cfg(test)]
689mod tests {
690    use super::*;
691    use std::sync::Arc;
692
693    #[test]
694    fn test_id_set_parse_compatibility() {
695        let bytes = [
696            64, 0, 0, 0, // map_item_size_bits = 64
697            128, 0, 0, 0, // high_bit = 128
698            2, 0, 0, 0, // count = 2
699            // Item 1
700            0, 0, 0, 0, // start_bit = 0
701            5, 0, 0, 0, 0, 0, 0, 0, // map = 5 (bits 0 and 2 set)
702            // Item 2
703            64, 0, 0, 0, // start_bit = 64
704            2, 0, 0, 0, 0, 0, 0, 0, // map = 2 (bit 65 set)
705        ];
706        let data: PolicyData = Arc::from(bytes);
707        let cursor = PolicyCursor::new(&data);
708        let (id_set, tail) = TypeSet::parse(cursor).unwrap();
709        assert_eq!(tail.offset(), bytes.len() as u32);
710        assert!(id_set.contains(TypeId::from_u32(1).unwrap()));
711        assert!(!id_set.contains(TypeId::from_u32(2).unwrap()));
712        assert!(id_set.contains(TypeId::from_u32(3).unwrap()));
713        assert!(id_set.contains(TypeId::from_u32(66).unwrap()));
714    }
715}