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