Skip to main content

selinux/policy/
mod.rs

1// Copyright 2026 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
5mod access_vector;
6mod bitmap;
7mod booleans;
8mod classes;
9mod common_symbols;
10mod compute;
11mod constraints;
12mod error;
13mod filename_transitions;
14mod id_type;
15mod indexed;
16mod initial_sids;
17mod metadata;
18mod mls;
19mod object_contexts;
20mod parser;
21mod permissions;
22mod policy_cap;
23mod roles;
24mod rules;
25mod security_context;
26#[cfg(test)]
27mod tests;
28mod traits;
29mod types;
30mod u24_index;
31mod users;
32
33use std::num::NonZeroU16;
34
35use selinux_policy_derive::{Parse, Serialize, Validate};
36
37use crate::InitialSid;
38pub use access_vector::AccessVector;
39use bitmap::IdSpan;
40use booleans::{ConditionalBoolean, ConditionalBooleanId};
41pub(crate) use classes::Class;
42pub use classes::ClassId;
43use classes::{ClassDefault, ClassDefaultRange};
44use common_symbols::CommonSymbol;
45pub use compute::XpermsKind;
46pub(crate) use compute::{FsUseLabelAndType, XpermsAccessDecision};
47use error::{ParseError, SerializeError, ValidateError};
48use filename_transitions::FilenameTransitions;
49use indexed::IdAndNameIndexed;
50use initial_sids::InitialSids;
51pub(crate) use metadata::HandleUnknown;
52pub use metadata::POLICYDB_VERSION_MAX;
53use metadata::{Config, Counts, Magic, PolicyVersion, Signature};
54use mls::{
55    Category, CategoryId, CategorySet, CategorySetBuilder, RangeTransition, Sensitivity,
56    SensitivityId,
57};
58pub use object_contexts::FsUseType;
59use object_contexts::{GenfsCon, GenfsConPath, ObjectContexts};
60pub use parser::PolicyData;
61use parser::{Array, PolicyCursor, PolicyWriter, SymbolArray};
62pub use permissions::PermissionId;
63pub use policy_cap::PolicyCap;
64use policy_cap::PolicyCapSet;
65pub(crate) use roles::RoleId;
66use roles::{Role, RoleAllow, RoleSet, RoleTransition};
67pub use rules::AccessDecision;
68use rules::{ConditionalNode, IndexedAccessVectorRules};
69pub(crate) use rules::{SELINUX_AVD_FLAGS_PERMISSIVE, XpermsBitmap};
70use security_context::{MlsLevel, MlsRange};
71pub(crate) use security_context::{SecurityContext, SecurityContextError};
72pub use traits::PolicyId;
73pub(crate) use traits::{HasName, HasPolicyId};
74use traits::{Serialize, Validate};
75pub(crate) use types::TypeId;
76use types::{PermissiveTypeSet, TypeAttributeMaps, TypeSet, Types};
77use u24_index::U24Index;
78use users::{User, UserId};
79
80/// Maximum supported binary policy size in bytes (`U24Index` limit).
81///
82/// As of 2026-01-30, this is more than five times larger than any policy seen in production or
83/// tests.
84const MAXIMUM_POLICY_SIZE: usize = (1 << 24) - 1;
85
86/// Mandatory role name and ID assigned to non-process/non-socket SELinux objects by default.
87const OBJECT_R_ROLE_NAME: &[u8] = b"object_r";
88const OBJECT_R_ROLE_ID: RoleId = RoleId::new(NonZeroU16::MIN);
89
90/// Top-level parsed SELinux binary policy.
91#[derive(Debug, Parse, Serialize, Validate)]
92pub(crate) struct Policy {
93    magic: Magic,
94    signature: Signature,
95    version: PolicyVersion,
96    config: Config,
97    counts: Counts,
98    policy_capabilities: PolicyCapSet,
99    permissive_map: PermissiveTypeSet,
100    common_symbols: IdAndNameIndexed<SymbolArray<CommonSymbol>>,
101    classes: IdAndNameIndexed<SymbolArray<Class>>,
102    roles: IdAndNameIndexed<SymbolArray<Role>>,
103    types: Types,
104    users: IdAndNameIndexed<SymbolArray<User>>,
105    conditional_booleans: IdAndNameIndexed<SymbolArray<ConditionalBoolean>>,
106    sensitivities: IdAndNameIndexed<SymbolArray<Sensitivity>>,
107    categories: IdAndNameIndexed<SymbolArray<Category>>,
108    access_vector_rules: IndexedAccessVectorRules,
109    conditional_nodes: Array<ConditionalNode>,
110    role_transitions: Array<RoleTransition>,
111    role_allowlist: Array<RoleAllow>,
112    filename_transitions: FilenameTransitions,
113    initial_sids: InitialSids,
114    object_contexts: ObjectContexts,
115    generic_fs_contexts: Array<GenfsCon>,
116    range_transitions: Array<RangeTransition>,
117    type_attribute_maps: TypeAttributeMaps,
118}
119
120impl Policy {
121    /// Parses a [`Policy`] from the raw binary data.
122    pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
123        let policy_size = data.len();
124        if policy_size > MAXIMUM_POLICY_SIZE {
125            return Err(ParseError::UnsupportedlyLarge {
126                observed: policy_size,
127                limit: MAXIMUM_POLICY_SIZE,
128            });
129        }
130        let mut cursor = PolicyCursor::new(data);
131        let policy = cursor.parse()?;
132        if cursor.offset() < data.len() {
133            return Err(ParseError::TrailingBytes { num_bytes: data.len() - cursor.offset() });
134        }
135        Ok(policy)
136    }
137
138    /// Validates the parsed policy.
139    pub fn validate(&self) -> Result<(), ValidateError> {
140        Validate::validate(self, self)?;
141
142        // Validate that all kernel-required initial SIDs are present in the policy.
143        let need_init_sid = self.has_policycap(PolicyCap::UserspaceInitialContext);
144        for initial_sid in InitialSid::all_variants() {
145            if *initial_sid == InitialSid::Init && !need_init_sid {
146                continue;
147            }
148            self.initial_sids()
149                .get_by_id(*initial_sid as u32)
150                .ok_or(ValidateError::MissingInitialSid { initial_sid: *initial_sid })?;
151        }
152
153        // Validate that the well-known "object_r" role is defined at role ID 1.
154        if self.roles().get_by_id(OBJECT_R_ROLE_ID).map(|role| role.name())
155            != Some(OBJECT_R_ROLE_NAME)
156        {
157            return Err(ValidateError::MissingObjectRRole);
158        }
159
160        // TODO: https://fxbug.dev/575728936 - Validate that `typebounds` hierarchies do not
161        // contain cycles, exceed maximum depth, or reference attributes.
162
163        Ok(())
164    }
165
166    /// Parses and validates a [`Policy`] from raw binary data for tests.
167    #[cfg(test)]
168    pub(crate) fn parse_and_validate(data: &[u8]) -> Self {
169        let policy = Self::parse(data).expect("parse policy");
170        policy.validate().expect("validate policy");
171        policy
172    }
173
174    /// Serializes the policy to binary representation.
175    pub fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
176        let mut policy_writer = PolicyWriter::new(self.version, writer);
177        Serialize::serialize(self, &mut policy_writer)
178    }
179
180    /// Returns the policy version.
181    #[cfg(test)]
182    fn version(&self) -> PolicyVersion {
183        self.version
184    }
185
186    /// Returns the [`HandleUnknown`] configuration.
187    pub fn handle_unknown(&self) -> HandleUnknown {
188        self.config.handle_unknown()
189    }
190
191    /// Returns the policy capabilities set.
192    fn policy_capabilities(&self) -> &PolicyCapSet {
193        &self.policy_capabilities
194    }
195
196    /// Returns the permissive types set.
197    fn permissive_map(&self) -> &PermissiveTypeSet {
198        &self.permissive_map
199    }
200
201    /// Returns the common symbols table.
202    fn common_symbols(&self) -> &IdAndNameIndexed<SymbolArray<CommonSymbol>> {
203        &self.common_symbols
204    }
205
206    /// Returns the object classes table.
207    pub fn classes(&self) -> &IdAndNameIndexed<SymbolArray<Class>> {
208        &self.classes
209    }
210
211    /// Returns the roles table.
212    fn roles(&self) -> &IdAndNameIndexed<SymbolArray<Role>> {
213        &self.roles
214    }
215
216    /// Returns the types table.
217    pub fn types(&self) -> &Types {
218        &self.types
219    }
220
221    /// Returns the users table.
222    fn users(&self) -> &IdAndNameIndexed<SymbolArray<User>> {
223        &self.users
224    }
225
226    /// Returns the conditional booleans table.
227    pub fn conditional_booleans(&self) -> &IdAndNameIndexed<SymbolArray<ConditionalBoolean>> {
228        &self.conditional_booleans
229    }
230
231    /// Returns the sensitivities table.
232    fn sensitivities(&self) -> &IdAndNameIndexed<SymbolArray<Sensitivity>> {
233        &self.sensitivities
234    }
235
236    /// Returns the categories table.
237    fn categories(&self) -> &IdAndNameIndexed<SymbolArray<Category>> {
238        &self.categories
239    }
240
241    /// Returns the access vector rules table.
242    fn access_vector_rules(&self) -> &IndexedAccessVectorRules {
243        &self.access_vector_rules
244    }
245
246    /// Returns the role transitions rules array.
247    fn role_transitions(&self) -> &[RoleTransition] {
248        self.role_transitions.as_ref()
249    }
250
251    /// Returns the role allow rules array.
252    fn role_allowlist(&self) -> &[RoleAllow] {
253        self.role_allowlist.as_ref()
254    }
255
256    /// Returns the filename transitions table.
257    fn filename_transitions(&self) -> &FilenameTransitions {
258        &self.filename_transitions
259    }
260
261    /// Returns the initial SIDs table.
262    fn initial_sids(&self) -> &InitialSids {
263        &self.initial_sids
264    }
265
266    /// Returns the object contexts table.
267    pub fn object_contexts(&self) -> &ObjectContexts {
268        &self.object_contexts
269    }
270
271    /// Returns generic filesystem labeling statements (`genfscon`).
272    fn generic_fs_contexts(&self) -> &[GenfsCon] {
273        &self.generic_fs_contexts
274    }
275
276    /// Returns the MLS range transitions array.
277    fn range_transitions(&self) -> &[RangeTransition] {
278        &self.range_transitions
279    }
280
281    /// Returns the type attribute maps table.
282    fn type_attribute_maps(&self) -> &TypeAttributeMaps {
283        &self.type_attribute_maps
284    }
285}
286
287#[cfg(test)]
288mod parse_tests {
289    use super::*;
290    use crate::policy::traits::{Parse, Serialize};
291
292    #[derive(Copy, Clone, Debug, Eq, PartialEq, Parse, Serialize, Validate)]
293    #[policy(wire_type = u32)]
294    enum TestEnum {
295        ValueOne = 1,
296        ValueTwo = 2,
297    }
298
299    #[test]
300    fn test_enum_derive() {
301        let mut cursor = PolicyCursor::new(&[1, 0, 0, 0]);
302        let parsed = TestEnum::parse(&mut cursor).unwrap();
303        assert_eq!(parsed, TestEnum::ValueOne);
304
305        let mut cursor = PolicyCursor::new(&[2, 0, 0, 0]);
306        let parsed = TestEnum::parse(&mut cursor).unwrap();
307        assert_eq!(parsed, TestEnum::ValueTwo);
308
309        let mut cursor = PolicyCursor::new(&[3, 0, 0, 0]);
310        let err = TestEnum::parse(&mut cursor).unwrap_err();
311        assert!(matches!(err, ParseError::InvalidEnumValue { enum_name: "TestEnum", value: 3 }));
312
313        let mut writer = Vec::new();
314        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
315        TestEnum::ValueOne.serialize(&mut policy_writer).unwrap();
316        assert_eq!(writer, vec![1, 0, 0, 0]);
317
318        let mut writer = Vec::new();
319        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
320        TestEnum::ValueTwo.serialize(&mut policy_writer).unwrap();
321        assert_eq!(writer, vec![2, 0, 0, 0]);
322
323        let policy_bytes = include_bytes!("../../testdata/policies/selinux_testsuite");
324        let policy = Policy::parse(policy_bytes).unwrap();
325        TestEnum::ValueOne.validate(&policy).unwrap();
326    }
327
328    const TEST_POLICIES: &[(&str, &[u8])] = &[
329        ("selinux_testsuite", include_bytes!("../../testdata/policies/selinux_testsuite")),
330        ("emulator", include_bytes!("../../testdata/policies/emulator")),
331        (
332            "conditional_policy",
333            include_bytes!("../../testdata/composite_policies/compiled/conditional_policy"),
334        ),
335        (
336            "minimal_policy",
337            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy"),
338        ),
339        (
340            "allow_fork_policy",
341            include_bytes!("../../testdata/composite_policies/compiled/allow_fork_policy"),
342        ),
343        (
344            "class_defaults_policy",
345            include_bytes!("../../testdata/composite_policies/compiled/class_defaults_policy"),
346        ),
347        (
348            "genfscon_policy",
349            include_bytes!("../../testdata/composite_policies/compiled/genfscon_policy"),
350        ),
351        (
352            "handle_unknown_policy-allow",
353            include_bytes!(
354                "../../testdata/composite_policies/compiled/handle_unknown_policy-allow"
355            ),
356        ),
357        (
358            "handle_unknown_policy-deny",
359            include_bytes!("../../testdata/composite_policies/compiled/handle_unknown_policy-deny"),
360        ),
361        (
362            "handle_unknown_policy-reject",
363            include_bytes!(
364                "../../testdata/composite_policies/compiled/handle_unknown_policy-reject"
365            ),
366        ),
367        (
368            "range_transition_policy",
369            include_bytes!("../../testdata/composite_policies/compiled/range_transition_policy"),
370        ),
371        (
372            "role_transition_policy",
373            include_bytes!("../../testdata/composite_policies/compiled/role_transition_policy"),
374        ),
375        (
376            "type_transition_policy",
377            include_bytes!("../../testdata/composite_policies/compiled/type_transition_policy"),
378        ),
379    ];
380
381    #[test]
382    fn test_all_compiled_policies_roundtrip() {
383        for (name, policy_bytes) in TEST_POLICIES {
384            let policy = Policy::parse(policy_bytes)
385                .unwrap_or_else(|e| panic!("Failed to parse {name}: {e:?}"));
386            policy.validate().unwrap_or_else(|e| panic!("Failed to validate {name}: {e:?}"));
387
388            if policy.version() >= PolicyVersion::V33 {
389                let mut serialized = Vec::new();
390                policy
391                    .serialize(&mut serialized)
392                    .unwrap_or_else(|e| panic!("Failed to serialize {name}: {e:?}"));
393                assert_bytes_eq(&serialized, policy_bytes);
394            }
395        }
396    }
397
398    #[test]
399    fn test_initial_sids_policy_elements() {
400        let policy_bytes =
401            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy");
402        let policy = Policy::parse(policy_bytes).expect("parse minimal policy");
403        policy.validate().expect("validate minimal policy");
404
405        assert!(!policy.initial_sids().is_empty());
406        assert!(policy.initial_sids().len() >= crate::InitialSid::all_variants().len() - 1);
407
408        let kernel_context =
409            policy.initial_sids().get_by_id(crate::InitialSid::Kernel as u32).unwrap();
410        assert!(policy.users().get_by_id(kernel_context.user()).is_some());
411        assert!(policy.roles().get_by_id(kernel_context.role()).is_some());
412
413        let unlabeled_context =
414            policy.initial_sids().get_by_id(crate::InitialSid::Unlabeled as u32).unwrap();
415        assert!(policy.users().get_by_id(unlabeled_context.user()).is_some());
416        assert!(policy.roles().get_by_id(unlabeled_context.role()).is_some());
417    }
418
419    #[test]
420    fn test_object_r_role_id_validation() {
421        let policy_bytes =
422            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy");
423        let mut policy = Policy::parse_and_validate(policy_bytes);
424
425        // Swap the role ID of "object_r" (ID 1) with another role so that role ID 1 still exists
426        // in the policy, but is no longer assigned to "object_r".
427        let roles = policy.roles.as_mut_slice();
428        let object_r_index =
429            roles.iter().position(|role| role.name() == OBJECT_R_ROLE_NAME).unwrap();
430        let other_index = roles.iter().position(|role| role.name() != OBJECT_R_ROLE_NAME).unwrap();
431        let object_r_id = roles[object_r_index].id();
432        let other_id = roles[other_index].id();
433        roles[object_r_index].set_id(other_id);
434        roles[other_index].set_id(object_r_id);
435
436        let mut serialized = Vec::new();
437        policy.serialize(&mut serialized).expect("serialize modified policy");
438
439        let reparsed = Policy::parse(&serialized).expect("re-parse modified policy");
440        assert_eq!(reparsed.validate(), Err(ValidateError::MissingObjectRRole));
441    }
442}
443
444#[cfg(test)]
445fn assert_bytes_eq(left: &[u8], right: &[u8]) {
446    if left != right {
447        let min_len = std::cmp::min(left.len(), right.len());
448        for i in 0..min_len {
449            if left[i] != right[i] {
450                let start = i.saturating_sub(8);
451                let end = std::cmp::min(i + 16, min_len);
452                panic!(
453                    "Byte mismatch at offset {i} (0x{i:x}): actual=0x{:02x} vs expected=0x{:02x}.\nActual   [{start}..{end}]: {:02x?}\nExpected [{start}..{end}]: {:02x?}",
454                    left[i],
455                    right[i],
456                    &left[start..end],
457                    &right[start..end]
458                );
459            }
460        }
461        panic!("Length mismatch: actual={}, expected={}", left.len(), right.len());
462    }
463}