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/356569876): Determine which "bounds" should be verified for
161        // correctness here.
162
163        Ok(())
164    }
165
166    /// Serializes the policy to binary representation.
167    pub fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
168        let mut policy_writer = PolicyWriter::new(self.version, writer);
169        Serialize::serialize(self, &mut policy_writer)
170    }
171
172    /// Returns the policy version.
173    #[cfg(test)]
174    fn version(&self) -> PolicyVersion {
175        self.version
176    }
177
178    /// Returns the [`HandleUnknown`] configuration.
179    pub fn handle_unknown(&self) -> HandleUnknown {
180        self.config.handle_unknown()
181    }
182
183    /// Returns the policy capabilities set.
184    fn policy_capabilities(&self) -> &PolicyCapSet {
185        &self.policy_capabilities
186    }
187
188    /// Returns the permissive types set.
189    fn permissive_map(&self) -> &PermissiveTypeSet {
190        &self.permissive_map
191    }
192
193    /// Returns the common symbols table.
194    fn common_symbols(&self) -> &IdAndNameIndexed<SymbolArray<CommonSymbol>> {
195        &self.common_symbols
196    }
197
198    /// Returns the object classes table.
199    pub fn classes(&self) -> &IdAndNameIndexed<SymbolArray<Class>> {
200        &self.classes
201    }
202
203    /// Returns the roles table.
204    fn roles(&self) -> &IdAndNameIndexed<SymbolArray<Role>> {
205        &self.roles
206    }
207
208    /// Returns the types table.
209    pub fn types(&self) -> &Types {
210        &self.types
211    }
212
213    /// Returns the users table.
214    fn users(&self) -> &IdAndNameIndexed<SymbolArray<User>> {
215        &self.users
216    }
217
218    /// Returns the conditional booleans table.
219    pub fn conditional_booleans(&self) -> &IdAndNameIndexed<SymbolArray<ConditionalBoolean>> {
220        &self.conditional_booleans
221    }
222
223    /// Returns the sensitivities table.
224    fn sensitivities(&self) -> &IdAndNameIndexed<SymbolArray<Sensitivity>> {
225        &self.sensitivities
226    }
227
228    /// Returns the categories table.
229    fn categories(&self) -> &IdAndNameIndexed<SymbolArray<Category>> {
230        &self.categories
231    }
232
233    /// Returns the access vector rules table.
234    fn access_vector_rules(&self) -> &IndexedAccessVectorRules {
235        &self.access_vector_rules
236    }
237
238    /// Returns the list of conditional nodes.
239    #[cfg(test)]
240    fn conditional_nodes(&self) -> &[ConditionalNode] {
241        self.conditional_nodes.as_ref()
242    }
243
244    /// Returns the role transitions rules array.
245    fn role_transitions(&self) -> &[RoleTransition] {
246        self.role_transitions.as_ref()
247    }
248
249    /// Returns the role allow rules array.
250    fn role_allowlist(&self) -> &[RoleAllow] {
251        self.role_allowlist.as_ref()
252    }
253
254    /// Returns the filename transitions table.
255    fn filename_transitions(&self) -> &FilenameTransitions {
256        &self.filename_transitions
257    }
258
259    /// Returns the initial SIDs table.
260    fn initial_sids(&self) -> &InitialSids {
261        &self.initial_sids
262    }
263
264    /// Returns the object contexts table.
265    pub fn object_contexts(&self) -> &ObjectContexts {
266        &self.object_contexts
267    }
268
269    /// Returns generic filesystem labeling statements (`genfscon`).
270    fn generic_fs_contexts(&self) -> &[GenfsCon] {
271        &self.generic_fs_contexts
272    }
273
274    /// Returns the MLS range transitions array.
275    fn range_transitions(&self) -> &[RangeTransition] {
276        &self.range_transitions
277    }
278
279    /// Returns the type attribute maps table.
280    fn type_attribute_maps(&self) -> &TypeAttributeMaps {
281        &self.type_attribute_maps
282    }
283}
284
285#[cfg(test)]
286mod parse_tests {
287    use super::*;
288    use crate::policy::traits::{Parse, Serialize};
289
290    #[derive(Copy, Clone, Debug, Eq, PartialEq, Parse, Serialize, Validate)]
291    #[policy(wire_type = u32)]
292    enum TestEnum {
293        ValueOne = 1,
294        ValueTwo = 2,
295    }
296
297    #[test]
298    fn test_enum_derive() {
299        let mut cursor = PolicyCursor::new(&[1, 0, 0, 0]);
300        let parsed = TestEnum::parse(&mut cursor).unwrap();
301        assert_eq!(parsed, TestEnum::ValueOne);
302
303        let mut cursor = PolicyCursor::new(&[2, 0, 0, 0]);
304        let parsed = TestEnum::parse(&mut cursor).unwrap();
305        assert_eq!(parsed, TestEnum::ValueTwo);
306
307        let mut cursor = PolicyCursor::new(&[3, 0, 0, 0]);
308        let err = TestEnum::parse(&mut cursor).unwrap_err();
309        assert!(matches!(err, ParseError::InvalidEnumValue { enum_name: "TestEnum", value: 3 }));
310
311        let mut writer = Vec::new();
312        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
313        TestEnum::ValueOne.serialize(&mut policy_writer).unwrap();
314        assert_eq!(writer, vec![1, 0, 0, 0]);
315
316        let mut writer = Vec::new();
317        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
318        TestEnum::ValueTwo.serialize(&mut policy_writer).unwrap();
319        assert_eq!(writer, vec![2, 0, 0, 0]);
320
321        let policy_bytes = include_bytes!("../../testdata/policies/selinux_testsuite");
322        let policy = Policy::parse(policy_bytes).unwrap();
323        TestEnum::ValueOne.validate(&policy).unwrap();
324    }
325
326    const TEST_POLICIES: &[(&str, &[u8])] = &[
327        ("selinux_testsuite", include_bytes!("../../testdata/policies/selinux_testsuite")),
328        ("emulator", include_bytes!("../../testdata/policies/emulator")),
329        (
330            "conditional_policy",
331            include_bytes!("../../testdata/composite_policies/compiled/conditional_policy"),
332        ),
333        (
334            "minimal_policy",
335            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy"),
336        ),
337        (
338            "allow_fork_policy",
339            include_bytes!("../../testdata/composite_policies/compiled/allow_fork_policy"),
340        ),
341        (
342            "class_defaults_policy",
343            include_bytes!("../../testdata/composite_policies/compiled/class_defaults_policy"),
344        ),
345        (
346            "genfscon_policy",
347            include_bytes!("../../testdata/composite_policies/compiled/genfscon_policy"),
348        ),
349        (
350            "handle_unknown_policy-allow",
351            include_bytes!(
352                "../../testdata/composite_policies/compiled/handle_unknown_policy-allow"
353            ),
354        ),
355        (
356            "handle_unknown_policy-deny",
357            include_bytes!("../../testdata/composite_policies/compiled/handle_unknown_policy-deny"),
358        ),
359        (
360            "handle_unknown_policy-reject",
361            include_bytes!(
362                "../../testdata/composite_policies/compiled/handle_unknown_policy-reject"
363            ),
364        ),
365        (
366            "range_transition_policy",
367            include_bytes!("../../testdata/composite_policies/compiled/range_transition_policy"),
368        ),
369        (
370            "role_transition_policy",
371            include_bytes!("../../testdata/composite_policies/compiled/role_transition_policy"),
372        ),
373        (
374            "type_transition_policy",
375            include_bytes!("../../testdata/composite_policies/compiled/type_transition_policy"),
376        ),
377    ];
378
379    #[test]
380    fn test_all_compiled_policies_roundtrip() {
381        for (name, policy_bytes) in TEST_POLICIES {
382            let policy = Policy::parse(policy_bytes)
383                .unwrap_or_else(|e| panic!("Failed to parse {name}: {e:?}"));
384            policy.validate().unwrap_or_else(|e| panic!("Failed to validate {name}: {e:?}"));
385
386            if policy.version() >= PolicyVersion::V33 {
387                let mut serialized = Vec::new();
388                policy
389                    .serialize(&mut serialized)
390                    .unwrap_or_else(|e| panic!("Failed to serialize {name}: {e:?}"));
391                assert_bytes_eq(&serialized, policy_bytes);
392            }
393        }
394    }
395
396    #[test]
397    fn test_initial_sids_policy_elements() {
398        let policy_bytes =
399            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy");
400        let policy = Policy::parse(policy_bytes).expect("parse minimal policy");
401        policy.validate().expect("validate minimal policy");
402
403        assert!(!policy.initial_sids().is_empty());
404        assert!(policy.initial_sids().len() >= crate::InitialSid::all_variants().len() - 1);
405
406        let kernel_context =
407            policy.initial_sids().get_by_id(crate::InitialSid::Kernel as u32).unwrap();
408        assert!(policy.users().get_by_id(kernel_context.user()).is_some());
409        assert!(policy.roles().get_by_id(kernel_context.role()).is_some());
410
411        let unlabeled_context =
412            policy.initial_sids().get_by_id(crate::InitialSid::Unlabeled as u32).unwrap();
413        assert!(policy.users().get_by_id(unlabeled_context.user()).is_some());
414        assert!(policy.roles().get_by_id(unlabeled_context.role()).is_some());
415    }
416
417    #[test]
418    fn test_object_r_role_id_validation() {
419        let policy_bytes =
420            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy");
421        let mut policy = Policy::parse(policy_bytes).expect("parse minimal policy");
422        policy.validate().expect("validate minimal policy");
423
424        // Swap the role ID of "object_r" (ID 1) with another role so that role ID 1 still exists
425        // in the policy, but is no longer assigned to "object_r".
426        let object_r_index =
427            policy.roles.iter().position(|role| role.name() == OBJECT_R_ROLE_NAME).unwrap();
428        let other_index =
429            policy.roles.iter().position(|role| role.name() != OBJECT_R_ROLE_NAME).unwrap();
430        let object_r_id = policy.roles[object_r_index].id();
431        let other_id = policy.roles[other_index].id();
432        policy.roles[object_r_index].set_id(other_id);
433        policy.roles[other_index].set_id(object_r_id);
434
435        let mut serialized = Vec::new();
436        policy.serialize(&mut serialized).expect("serialize modified policy");
437
438        let reparsed = Policy::parse(&serialized).expect("re-parse modified policy");
439        assert_eq!(reparsed.validate(), Err(ValidateError::MissingObjectRRole));
440    }
441}
442
443#[cfg(test)]
444fn assert_bytes_eq(left: &[u8], right: &[u8]) {
445    if left != right {
446        let min_len = std::cmp::min(left.len(), right.len());
447        for i in 0..min_len {
448            if left[i] != right[i] {
449                let start = i.saturating_sub(8);
450                let end = std::cmp::min(i + 16, min_len);
451                panic!(
452                    "Byte mismatch at offset {i} (0x{i:x}): actual=0x{:02x} vs expected=0x{:02x}.\nActual   [{start}..{end}]: {:02x?}\nExpected [{start}..{end}]: {:02x?}",
453                    left[i],
454                    right[i],
455                    &left[start..end],
456                    &right[start..end]
457                );
458            }
459        }
460        panic!("Length mismatch: actual={}, expected={}", left.len(), right.len());
461    }
462}