Skip to main content

selinux/new_policy/
classes.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
5use std::num::NonZeroU16;
6
7use super::NewPolicy;
8use super::constraints::{Constraint, ConstraintTerm};
9use super::error::{ParseError, SerializeError, ValidateError};
10use super::id_type::IdType;
11use super::indexed::IdAndNameIndexed;
12use super::parser::{Array, PolicyCursor};
13use super::permissions::Permission;
14use super::traits::{Parse, PolicyId, Serialize, Validate};
15
16use selinux_policy_derive::{HasName, HasPolicyId, Parse, Serialize, Validate};
17
18/// Tag type for type safety of policy class identifiers.
19#[derive(Copy, Clone, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)]
20pub struct ClassTag;
21
22/// Identifies a class within a policy.
23pub type ClassId = IdType<NonZeroU16, ClassTag>;
24
25#[derive(Parse, Serialize)]
26struct BinaryClassMetadata {
27    key_length: u32,
28    common_key_length: u32,
29    id: u32,
30    /// Included in the policy to allow allocation of index structures to be optimized.
31    permission_primary_names_count: u32,
32    permission_count: u32,
33    constraint_count: u32,
34}
35
36/// Rule for computing default user, role, or type when creating an object of a class.
37#[derive(Debug, Clone, Copy, PartialEq, Eq, Parse, Serialize, Validate)]
38#[policy(wire_type = u32)]
39pub enum ClassDefault {
40    Unspecified = 0,
41    Source = 1,
42    Target = 2,
43}
44
45/// Rule for computing default MLS range when creating an object of a class.
46#[derive(Debug, Clone, Copy, PartialEq, Eq, Parse, Serialize, Validate)]
47#[policy(wire_type = u32)]
48pub enum ClassDefaultRange {
49    Unspecified = 0,
50    SourceLow = 1,
51    SourceHigh = 2,
52    SourceLowHigh = 3,
53    TargetLow = 4,
54    TargetHigh = 5,
55    TargetLowHigh = 6,
56    UnknownUsedValue = 7,
57}
58
59/// Set of rules for computing default security context fields for a class.
60#[derive(Debug, Clone, PartialEq, Eq, Parse, Serialize, Validate)]
61pub struct ClassDefaults {
62    default_user: ClassDefault,
63    default_role: ClassDefault,
64    default_range: ClassDefaultRange,
65    default_type: ClassDefault,
66}
67
68impl ClassDefaults {
69    pub fn user(&self) -> ClassDefault {
70        self.default_user
71    }
72
73    pub fn role(&self) -> ClassDefault {
74        self.default_role
75    }
76
77    pub fn range(&self) -> ClassDefaultRange {
78        self.default_range
79    }
80
81    pub fn type_(&self) -> ClassDefault {
82        self.default_type
83    }
84}
85
86/// Parsed SELinux object class definition, including permissions and constraints.
87#[derive(Debug, Clone, PartialEq, Eq, HasName, HasPolicyId)]
88pub struct Class {
89    id: ClassId,
90    name: Box<[u8]>,
91    common_name: Box<[u8]>,
92    /// Included in the policy to allow allocation of index structures to be optimized.
93    permission_primary_names_count: u32,
94    permissions: IdAndNameIndexed<Box<[Permission]>>,
95    constraints: Box<[Constraint]>,
96    validate_transitions: Array<ConstraintTerm>,
97    defaults: ClassDefaults,
98}
99
100impl Class {
101    /// Name of the `common` from which this class inherits.
102    ///
103    /// For example, `common file { common_file_perm }` and
104    /// `class file inherits file { file_perm }` yields a `Class` object
105    /// for `file` with `self.common_name() == b"file"`.
106    pub fn common_name(&self) -> &[u8] {
107        &self.common_name
108    }
109
110    pub fn permissions(&self) -> &IdAndNameIndexed<Box<[Permission]>> {
111        &self.permissions
112    }
113
114    pub fn constraints(&self) -> &[Constraint] {
115        &self.constraints
116    }
117
118    pub fn validate_transitions(&self) -> &[ConstraintTerm] {
119        &self.validate_transitions
120    }
121
122    pub fn defaults(&self) -> &ClassDefaults {
123        &self.defaults
124    }
125}
126
127impl Parse for Class {
128    fn parse(cursor: &mut PolicyCursor<'_>) -> Result<Self, ParseError> {
129        let metadata = BinaryClassMetadata::parse(cursor)?;
130
131        let id_val = metadata.id;
132        let id = ClassId::from_u32(id_val).ok_or(ParseError::InvalidId { value: id_val })?;
133
134        let name_len = metadata.key_length as usize;
135        let name = Box::from(cursor.read_bytes(name_len)?);
136
137        let common_name_len = metadata.common_key_length as usize;
138        let common_name = Box::from(cursor.read_bytes(common_name_len)?);
139
140        let permissions_count = metadata.permission_count as usize;
141        let mut permissions = Vec::with_capacity(permissions_count);
142        for _ in 0..permissions_count {
143            permissions.push(Permission::parse(cursor)?);
144        }
145        let permissions = IdAndNameIndexed::new(permissions.into_boxed_slice());
146
147        let constraint_count = metadata.constraint_count as usize;
148        let mut constraints = Vec::with_capacity(constraint_count);
149        for _ in 0..constraint_count {
150            constraints.push(Constraint::parse(cursor)?);
151        }
152        let constraints = constraints.into_boxed_slice();
153
154        let validate_transitions = Array::<ConstraintTerm>::parse(cursor)?;
155        let defaults = ClassDefaults::parse(cursor)?;
156
157        Ok(Self {
158            id,
159            name,
160            common_name,
161            permission_primary_names_count: metadata.permission_primary_names_count,
162            permissions,
163            constraints,
164            validate_transitions,
165            defaults,
166        })
167    }
168}
169
170impl Serialize for Class {
171    fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
172        let metadata = BinaryClassMetadata {
173            key_length: self.name.len() as u32,
174            common_key_length: self.common_name.len() as u32,
175            id: self.id.as_u32(),
176            permission_primary_names_count: self.permission_primary_names_count,
177            permission_count: self.permissions.len() as u32,
178            constraint_count: self.constraints.len() as u32,
179        };
180        metadata.serialize(writer)?;
181
182        writer.extend_from_slice(&self.name);
183        writer.extend_from_slice(&self.common_name);
184
185        for permission in self.permissions.iter() {
186            permission.serialize(writer)?;
187        }
188
189        for constraint in self.constraints.iter() {
190            constraint.serialize(writer)?;
191        }
192
193        self.validate_transitions.serialize(writer)?;
194        self.defaults.serialize(writer)?;
195        Ok(())
196    }
197}
198
199impl Validate for Class {
200    fn validate(&self, policy: &NewPolicy) -> Result<(), ValidateError> {
201        self.permissions.validate(policy)?;
202        for constraint in self.constraints.iter() {
203            constraint.validate(policy)?;
204        }
205        self.validate_transitions.validate(policy)?;
206        self.defaults.validate(policy)?;
207
208        let mut common_permissions_count = 0;
209        if !self.common_name.is_empty() {
210            let common_symbol =
211                policy.common_symbols().get_by_name(&self.common_name).ok_or_else(|| {
212                    ValidateError::UndefinedCommonSymbol { name: self.common_name.to_vec() }
213                })?;
214            common_permissions_count = common_symbol.permissions().len();
215        }
216
217        let expected_at_most = self.permissions.len() + common_permissions_count;
218        if self.permission_primary_names_count > expected_at_most as u32 {
219            return Err(ValidateError::InvalidPrimaryNamesCount {
220                expected_at_most: expected_at_most as u32,
221                found: self.permission_primary_names_count,
222            });
223        }
224
225        Ok(())
226    }
227}
228
229#[cfg(test)]
230mod tests {
231    use super::super::traits::{HasName, HasPolicyId};
232    use super::{PolicyCursor, *};
233
234    #[test]
235    fn test_class_defaults_parse_and_serialize() {
236        let data = [
237            1, 0, 0, 0, // default_user = 1 (Source)
238            2, 0, 0, 0, // default_role = 2 (Target)
239            6, 0, 0, 0, // default_range = 6 (TargetLowHigh)
240            1, 0, 0, 0, // default_type = 1 (Source)
241        ];
242        let mut cursor = PolicyCursor::new(&data);
243        let defaults = ClassDefaults::parse(&mut cursor).unwrap();
244        assert_eq!(defaults.user(), ClassDefault::Source);
245        assert_eq!(defaults.role(), ClassDefault::Target);
246        assert_eq!(defaults.range(), ClassDefaultRange::TargetLowHigh);
247        assert_eq!(defaults.type_(), ClassDefault::Source);
248
249        let mut writer = Vec::new();
250        defaults.serialize(&mut writer).unwrap();
251        assert_eq!(writer, data);
252    }
253
254    #[test]
255    fn test_minimal_class_parse_and_serialize() {
256        let data = [
257            // BinaryClassMetadata
258            4, 0, 0, 0, // key_length = 4
259            0, 0, 0, 0, // common_key_length = 0
260            1, 0, 0, 0, // id = 1
261            0, 0, 0, 0, // permission_primary_names_count = 0
262            0, 0, 0, 0, // permission_count = 0
263            0, 0, 0, 0, // constraint_count = 0
264            116, 101, 115, 116, // name: "test"
265            0, 0, 0, 0, // validate_transitions (Array count = 0)
266            // defaults (all Unspecified = 0)
267            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
268        ];
269        let mut cursor = PolicyCursor::new(&data);
270        let class = Class::parse(&mut cursor).unwrap();
271        assert_eq!(class.id(), ClassId::from_u32(1).unwrap());
272        assert_eq!(class.name(), b"test");
273        assert!(class.common_name().is_empty());
274        assert!(class.permissions().is_empty());
275        assert!(class.constraints().is_empty());
276        assert!(class.validate_transitions().is_empty());
277
278        let mut writer = Vec::new();
279        class.serialize(&mut writer).unwrap();
280        assert_eq!(writer, data);
281    }
282}