Skip to main content

selinux/new_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
5pub(super) mod access_vector;
6pub(super) mod bitmap;
7pub(super) mod booleans;
8pub(super) mod classes;
9pub(super) mod common_symbols;
10pub(super) mod constraints;
11pub(super) mod context;
12pub(super) mod error;
13pub(super) mod filename_transitions;
14pub(super) mod id_type;
15pub(super) mod indexed;
16pub(super) mod initial_sids;
17pub(super) mod metadata;
18pub(super) mod mls;
19pub(super) mod object_contexts;
20pub(super) mod parser;
21pub(super) mod permissions;
22pub(super) mod policy_cap;
23pub(super) mod roles;
24pub(super) mod rules;
25pub(super) mod traits;
26pub(super) mod types;
27pub(super) mod u24_index;
28pub(super) mod users;
29
30use selinux_policy_derive::{Parse, Serialize, Validate};
31
32pub use access_vector::AccessVector;
33pub use bitmap::IdSpan;
34pub use booleans::{ConditionalBoolean, ConditionalBooleanId};
35pub use classes::{Class, ClassDefault, ClassDefaultRange, ClassId};
36pub use common_symbols::CommonSymbol;
37pub use constraints::{
38    ConstraintOperator, ConstraintSubject, ConstraintTerm, MlsOperands, MlsOperator, NameExpression,
39};
40pub use context::{Context, MlsLevel, MlsRange};
41use error::{ParseError, SerializeError, ValidateError};
42pub use filename_transitions::FilenameTransitions;
43pub use id_type::*;
44pub use indexed::IdAndNameIndexed;
45pub use initial_sids::InitialSids;
46use metadata::{Config, Counts, Magic, Signature};
47pub use metadata::{HandleUnknown, POLICYDB_VERSION_MAX, PolicyVersion};
48pub use mls::{Category, RangeTransition, Sensitivity};
49pub use object_contexts::{FsUseType, GenfsCon, GenfsConPath, ObjectContexts};
50use parser::{Array, PolicyCursor};
51pub use parser::{PolicyWriter, SymbolArray};
52pub use permissions::PermissionId;
53pub use policy_cap::{PolicyCap, PolicyCapSet};
54pub use roles::{Role, RoleAllow, RoleId, RoleSet, RoleTransition};
55pub use rules::{
56    AccessDecision, AccessVectorRules, ConditionalNode, IndexedAccessVectorRules,
57    SELINUX_AVD_FLAGS_PERMISSIVE, XpermsBitmap,
58};
59use traits::{Serialize, Validate};
60pub use types::*;
61pub use u24_index::U24Index;
62pub use users::User;
63
64/// Tag type for type safety of policy user identifiers.
65#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
66pub struct UserTag;
67
68/// Identifies a user within a policy.
69pub type UserId = IdType<std::num::NonZeroU16, UserTag>;
70
71/// Tag type for type safety of policy sensitivity identifiers.
72#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
73pub struct SensitivityTag;
74
75/// Identifies a sensitivity level within a policy.
76pub type SensitivityId = IdType<std::num::NonZeroU16, SensitivityTag>;
77
78/// Tag type for type safety of policy category identifiers.
79#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
80pub struct CategoryTag;
81
82/// Identifies a security category within a policy.
83pub type CategoryId = IdType<std::num::NonZeroU16, CategoryTag>;
84
85/// Set of security categories.
86pub type CategorySet = bitmap::IdSet<CategoryId>;
87
88/// Builder for constructing [`CategorySet`]s dynamically.
89pub type CategorySetBuilder = bitmap::IdSetBuilder<CategoryId>;
90
91/// Top-level [`NewPolicy`] structure representing a parsed SELinux binary policy.
92#[derive(Debug, Parse, Serialize, Validate)]
93pub struct NewPolicy {
94    magic: Magic,
95    signature: Signature,
96    version: PolicyVersion,
97    config: Config,
98    counts: Counts,
99    policy_capabilities: PolicyCapSet,
100    permissive_map: PermissiveTypeSet,
101    common_symbols: IdAndNameIndexed<SymbolArray<CommonSymbol>>,
102    classes: IdAndNameIndexed<SymbolArray<Class>>,
103    roles: IdAndNameIndexed<SymbolArray<Role>>,
104    types: Types,
105    users: IdAndNameIndexed<SymbolArray<User>>,
106    conditional_booleans: IdAndNameIndexed<SymbolArray<ConditionalBoolean>>,
107    sensitivities: IdAndNameIndexed<SymbolArray<Sensitivity>>,
108    categories: IdAndNameIndexed<SymbolArray<Category>>,
109    access_vector_rules: IndexedAccessVectorRules,
110    conditional_nodes: Array<ConditionalNode>,
111    role_transitions: Array<RoleTransition>,
112    role_allowlist: Array<RoleAllow>,
113    filename_transitions: FilenameTransitions,
114    initial_sids: InitialSids,
115    object_contexts: ObjectContexts,
116    generic_fs_contexts: Array<GenfsCon>,
117    range_transitions: Array<RangeTransition>,
118    type_attribute_maps: TypeAttributeMaps,
119}
120
121impl NewPolicy {
122    /// Parses a [`NewPolicy`] from the raw binary data.
123    pub fn parse(data: &[u8]) -> Result<Self, ParseError> {
124        let mut cursor = PolicyCursor::new(data);
125        let policy = cursor.parse()?;
126        if cursor.offset() < data.len() {
127            return Err(ParseError::TrailingBytes { num_bytes: data.len() - cursor.offset() });
128        }
129        Ok(policy)
130    }
131
132    /// Validates the parsed policy.
133    pub fn validate(&self) -> Result<(), ValidateError> {
134        Validate::validate(self, self)
135    }
136
137    /// Serializes the policy to binary representation.
138    pub fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
139        let mut policy_writer = PolicyWriter::new(self.version, writer);
140        Serialize::serialize(self, &mut policy_writer)
141    }
142
143    /// Returns the policy version.
144    pub fn version(&self) -> PolicyVersion {
145        self.version
146    }
147
148    /// Returns the [`HandleUnknown`] configuration.
149    pub fn handle_unknown(&self) -> HandleUnknown {
150        self.config.handle_unknown()
151    }
152
153    /// Returns the policy capabilities set.
154    pub fn policy_capabilities(&self) -> &PolicyCapSet {
155        &self.policy_capabilities
156    }
157
158    /// Returns the permissive types set.
159    pub fn permissive_map(&self) -> &PermissiveTypeSet {
160        &self.permissive_map
161    }
162
163    /// Returns the common symbols table.
164    pub fn common_symbols(&self) -> &IdAndNameIndexed<SymbolArray<CommonSymbol>> {
165        &self.common_symbols
166    }
167
168    /// Returns the object classes table.
169    pub fn classes(&self) -> &IdAndNameIndexed<SymbolArray<Class>> {
170        &self.classes
171    }
172
173    /// Returns the roles table.
174    pub fn roles(&self) -> &IdAndNameIndexed<SymbolArray<Role>> {
175        &self.roles
176    }
177
178    /// Returns the types table.
179    pub fn types(&self) -> &Types {
180        &self.types
181    }
182
183    /// Returns the users table.
184    pub fn users(&self) -> &IdAndNameIndexed<SymbolArray<User>> {
185        &self.users
186    }
187
188    /// Returns the conditional booleans table.
189    pub fn conditional_booleans(&self) -> &IdAndNameIndexed<SymbolArray<ConditionalBoolean>> {
190        &self.conditional_booleans
191    }
192
193    /// Returns the sensitivities table.
194    pub fn sensitivities(&self) -> &IdAndNameIndexed<SymbolArray<Sensitivity>> {
195        &self.sensitivities
196    }
197
198    /// Returns the categories table.
199    pub fn categories(&self) -> &IdAndNameIndexed<SymbolArray<Category>> {
200        &self.categories
201    }
202
203    /// Returns the access vector rules table.
204    pub fn access_vector_rules(&self) -> &IndexedAccessVectorRules {
205        &self.access_vector_rules
206    }
207
208    /// Returns the list of conditional nodes.
209    #[cfg(test)]
210    pub(crate) fn conditional_nodes(&self) -> &[ConditionalNode] {
211        self.conditional_nodes.as_ref()
212    }
213
214    /// Returns the role transitions rules array.
215    pub(crate) fn role_transitions(&self) -> &[RoleTransition] {
216        self.role_transitions.as_ref()
217    }
218
219    /// Returns the role allow rules array.
220    pub(crate) fn role_allowlist(&self) -> &[RoleAllow] {
221        self.role_allowlist.as_ref()
222    }
223
224    /// Returns the filename transitions table.
225    pub fn filename_transitions(&self) -> &FilenameTransitions {
226        &self.filename_transitions
227    }
228
229    /// Returns the initial SIDs table.
230    pub fn initial_sids(&self) -> &InitialSids {
231        &self.initial_sids
232    }
233
234    /// Returns the object contexts table.
235    pub fn object_contexts(&self) -> &ObjectContexts {
236        &self.object_contexts
237    }
238
239    /// Returns generic filesystem labeling statements (`genfscon`).
240    pub(crate) fn generic_fs_contexts(&self) -> &[GenfsCon] {
241        &self.generic_fs_contexts
242    }
243
244    /// Returns the MLS range transitions array.
245    pub(crate) fn range_transitions(&self) -> &[RangeTransition] {
246        &self.range_transitions
247    }
248
249    /// Returns the type attribute maps table.
250    pub fn type_attribute_maps(&self) -> &TypeAttributeMaps {
251        &self.type_attribute_maps
252    }
253}
254
255#[cfg(test)]
256mod tests {
257    use super::*;
258    use crate::new_policy::traits::{Parse, Serialize};
259
260    #[derive(Copy, Clone, Debug, Eq, PartialEq, Parse, Serialize, Validate)]
261    #[policy(wire_type = u32)]
262    enum TestEnum {
263        ValueOne = 1,
264        ValueTwo = 2,
265    }
266
267    #[test]
268    fn test_enum_derive() {
269        let mut cursor = PolicyCursor::new(&[1, 0, 0, 0]);
270        let parsed = TestEnum::parse(&mut cursor).unwrap();
271        assert_eq!(parsed, TestEnum::ValueOne);
272
273        let mut cursor = PolicyCursor::new(&[2, 0, 0, 0]);
274        let parsed = TestEnum::parse(&mut cursor).unwrap();
275        assert_eq!(parsed, TestEnum::ValueTwo);
276
277        let mut cursor = PolicyCursor::new(&[3, 0, 0, 0]);
278        let err = TestEnum::parse(&mut cursor).unwrap_err();
279        assert!(matches!(err, ParseError::InvalidEnumValue { enum_name: "TestEnum", value: 3 }));
280
281        let mut writer = Vec::new();
282        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
283        TestEnum::ValueOne.serialize(&mut policy_writer).unwrap();
284        assert_eq!(writer, vec![1, 0, 0, 0]);
285
286        let mut writer = Vec::new();
287        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
288        TestEnum::ValueTwo.serialize(&mut policy_writer).unwrap();
289        assert_eq!(writer, vec![2, 0, 0, 0]);
290
291        let policy_bytes = include_bytes!("../../testdata/policies/selinux_testsuite");
292        let policy = NewPolicy::parse(policy_bytes).unwrap();
293        TestEnum::ValueOne.validate(&policy).unwrap();
294    }
295
296    const TEST_POLICIES: &[(&str, &[u8])] = &[
297        ("selinux_testsuite", include_bytes!("../../testdata/policies/selinux_testsuite")),
298        ("emulator", include_bytes!("../../testdata/policies/emulator")),
299        (
300            "conditional_policy",
301            include_bytes!("../../testdata/composite_policies/compiled/conditional_policy"),
302        ),
303        (
304            "minimal_policy",
305            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy"),
306        ),
307        (
308            "allow_fork_policy",
309            include_bytes!("../../testdata/composite_policies/compiled/allow_fork_policy"),
310        ),
311        (
312            "class_defaults_policy",
313            include_bytes!("../../testdata/composite_policies/compiled/class_defaults_policy"),
314        ),
315        (
316            "genfscon_policy",
317            include_bytes!("../../testdata/composite_policies/compiled/genfscon_policy"),
318        ),
319        (
320            "handle_unknown_policy-allow",
321            include_bytes!(
322                "../../testdata/composite_policies/compiled/handle_unknown_policy-allow"
323            ),
324        ),
325        (
326            "handle_unknown_policy-deny",
327            include_bytes!("../../testdata/composite_policies/compiled/handle_unknown_policy-deny"),
328        ),
329        (
330            "handle_unknown_policy-reject",
331            include_bytes!(
332                "../../testdata/composite_policies/compiled/handle_unknown_policy-reject"
333            ),
334        ),
335        (
336            "range_transition_policy",
337            include_bytes!("../../testdata/composite_policies/compiled/range_transition_policy"),
338        ),
339        (
340            "role_transition_policy",
341            include_bytes!("../../testdata/composite_policies/compiled/role_transition_policy"),
342        ),
343        (
344            "type_transition_policy",
345            include_bytes!("../../testdata/composite_policies/compiled/type_transition_policy"),
346        ),
347    ];
348
349    #[test]
350    fn test_all_compiled_policies_roundtrip() {
351        for (name, policy_bytes) in TEST_POLICIES {
352            let new_policy = NewPolicy::parse(policy_bytes)
353                .unwrap_or_else(|e| panic!("Failed to parse {name}: {e:?}"));
354            new_policy.validate().unwrap_or_else(|e| panic!("Failed to validate {name}: {e:?}"));
355
356            if new_policy.version() >= PolicyVersion::V33 {
357                let mut serialized = Vec::new();
358                new_policy
359                    .serialize(&mut serialized)
360                    .unwrap_or_else(|e| panic!("Failed to serialize {name}: {e:?}"));
361                assert_bytes_eq(&serialized, policy_bytes);
362            }
363        }
364    }
365
366    #[test]
367    fn test_initial_sids_policy_elements() {
368        let policy_bytes =
369            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy");
370        let new_policy = NewPolicy::parse(policy_bytes).expect("parse minimal policy");
371        new_policy.validate().expect("validate minimal policy");
372
373        assert!(!new_policy.initial_sids().is_empty());
374        assert!(new_policy.initial_sids().len() >= crate::InitialSid::all_variants().len() - 1);
375
376        let kernel_context =
377            new_policy.initial_sids().get_by_id(crate::InitialSid::Kernel as u32).unwrap();
378        assert!(new_policy.users().get_by_id(kernel_context.user()).is_some());
379        assert!(new_policy.roles().get_by_id(kernel_context.role()).is_some());
380
381        let unlabeled_context =
382            new_policy.initial_sids().get_by_id(crate::InitialSid::Unlabeled as u32).unwrap();
383        assert!(new_policy.users().get_by_id(unlabeled_context.user()).is_some());
384        assert!(new_policy.roles().get_by_id(unlabeled_context.role()).is_some());
385    }
386}
387
388#[cfg(test)]
389pub(crate) fn assert_bytes_eq(left: &[u8], right: &[u8]) {
390    if left != right {
391        let min_len = std::cmp::min(left.len(), right.len());
392        for i in 0..min_len {
393            if left[i] != right[i] {
394                let start = i.saturating_sub(8);
395                let end = std::cmp::min(i + 16, min_len);
396                panic!(
397                    "Byte mismatch at offset {i} (0x{i:x}): actual=0x{:02x} vs expected=0x{:02x}.\nActual   [{start}..{end}]: {:02x?}\nExpected [{start}..{end}]: {:02x?}",
398                    left[i],
399                    right[i],
400                    &left[start..end],
401                    &right[start..end]
402                );
403            }
404        }
405        panic!("Length mismatch: actual={}, expected={}", left.len(), right.len());
406    }
407}