Skip to main content

selinux/new_policy/
roles.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 super::bitmap::IdSet;
6use super::error::{ParseError, SerializeError, ValidateError};
7use super::id_type::IdType;
8use super::parser::PolicyCursor;
9use super::traits::{Parse, PolicyId, Serialize, Validate};
10use super::{ClassId, NewPolicy, TypeId, TypeSet};
11
12use selinux_policy_derive::{HasName, HasPolicyId, Parse, Serialize, Validate};
13
14/// Tag type for type safety of policy role identifiers.
15#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
16pub struct RoleTag;
17
18/// Identifies a role within a policy.
19pub type RoleId = IdType<std::num::NonZeroU16, RoleTag>;
20
21/// Set of [`RoleId`]s.
22pub type RoleSet = IdSet<RoleId>;
23
24#[derive(Parse, Serialize)]
25struct BinaryRoleMetadata {
26    key_length: u32,
27    id: u32,
28    bounds: u32,
29}
30
31/// Parsed SELinux [`Role`] definition.
32#[derive(Debug, Clone, PartialEq, Eq, Validate, HasName, HasPolicyId)]
33pub struct Role {
34    id: RoleId,
35    name: Box<[u8]>,
36    bounds: Option<RoleId>,
37    dominates: RoleSet,
38    types: TypeSet,
39}
40
41impl Role {
42    pub fn bounds(&self) -> Option<RoleId> {
43        self.bounds
44    }
45
46    pub fn dominates(&self) -> &RoleSet {
47        &self.dominates
48    }
49
50    pub fn types(&self) -> &TypeSet {
51        &self.types
52    }
53}
54
55impl Parse for Role {
56    fn parse(cursor: &mut PolicyCursor<'_>) -> Result<Self, ParseError> {
57        let metadata = BinaryRoleMetadata::parse(cursor)?;
58        let name = Box::from(cursor.read_bytes(metadata.key_length as usize)?);
59        let dominates = RoleSet::parse(cursor)?;
60        let types = TypeSet::parse(cursor)?;
61
62        let bounds = RoleId::from_u32(metadata.bounds);
63        let id =
64            RoleId::from_u32(metadata.id).ok_or(ParseError::InvalidId { value: metadata.id })?;
65
66        Ok(Self { id, name, bounds, dominates, types })
67    }
68}
69
70impl Serialize for Role {
71    fn serialize(&self, writer: &mut Vec<u8>) -> Result<(), SerializeError> {
72        let metadata = BinaryRoleMetadata {
73            key_length: self.name.len() as u32,
74            id: self.id.as_u32(),
75            bounds: self.bounds.map_or(0, |id| id.as_u32()),
76        };
77        metadata.serialize(writer)?;
78        writer.extend_from_slice(&self.name);
79        self.dominates.serialize(writer)?;
80        self.types.serialize(writer)?;
81        Ok(())
82    }
83}
84impl Validate for RoleId {
85    fn validate(&self, policy: &NewPolicy) -> Result<(), ValidateError> {
86        policy
87            .roles()
88            .get_by_id(*self)
89            .map(|_| ())
90            .ok_or_else(|| ValidateError::UnknownId { kind: "role", id: self.as_u32() })
91    }
92}
93
94/// SELinux policy role transition rule (`role_transition`).
95#[derive(Clone, Debug, PartialEq, Eq, Parse, Serialize, Validate)]
96pub struct RoleTransition {
97    current_role: RoleId,
98    type_: TypeId,
99    new_role: RoleId,
100    class: ClassId,
101}
102
103impl RoleTransition {
104    pub fn current_role(&self) -> RoleId {
105        self.current_role
106    }
107
108    pub fn type_(&self) -> TypeId {
109        self.type_
110    }
111
112    pub fn new_role(&self) -> RoleId {
113        self.new_role
114    }
115
116    pub fn class(&self) -> ClassId {
117        self.class
118    }
119}
120
121/// SELinux policy role allow rule (`allow` for roles).
122#[derive(Clone, Debug, PartialEq, Eq, Parse, Serialize, Validate)]
123pub struct RoleAllow {
124    source_role: RoleId,
125    new_role: RoleId,
126}
127
128impl RoleAllow {
129    pub fn source_role(&self) -> RoleId {
130        self.source_role
131    }
132
133    pub fn new_role(&self) -> RoleId {
134        self.new_role
135    }
136}
137
138#[cfg(test)]
139mod tests {
140    use super::*;
141    use crate::new_policy::traits::{HasName, HasPolicyId, PolicyId};
142
143    #[test]
144    fn test_role_parse_and_serialize() {
145        let data = [
146            // BinaryRoleMetadata
147            4, 0, 0, 0, // key_length = 4
148            1, 0, 0, 0, // id = 1
149            0, 0, 0, 0, // bounds = 0
150            // name: "test"
151            b't', b'e', b's', b't', // dominates (empty ExtensibleBitmap)
152            64, 0, 0, 0, // map_item_size_bits = 64
153            0, 0, 0, 0, // high_bit = 0
154            0, 0, 0, 0, // items_count = 0
155            // types (empty ExtensibleBitmap)
156            64, 0, 0, 0, // map_item_size_bits = 64
157            0, 0, 0, 0, // high_bit = 0
158            0, 0, 0, 0, // items_count = 0
159        ];
160        let mut cursor = PolicyCursor::new(&data);
161        let role = Role::parse(&mut cursor).unwrap();
162        assert_eq!(role.id(), RoleId::from_u32(1).unwrap());
163        assert_eq!(role.name(), b"test");
164        assert!(role.bounds().is_none());
165        // dominates and types are empty dynamically validated by round-trip
166
167        let mut writer = Vec::new();
168        role.serialize(&mut writer).unwrap();
169        assert_eq!(writer, data);
170    }
171
172    #[test]
173    fn test_role_transition_parse_and_serialize() {
174        let data = [
175            1, 0, 0, 0, // current_role = 1
176            2, 0, 0, 0, // type_ = 2
177            3, 0, 0, 0, // new_role = 3
178            4, 0, 0, 0, // class = 4
179        ];
180        let mut cursor = PolicyCursor::new(&data);
181        let trans = RoleTransition::parse(&mut cursor).unwrap();
182        assert_eq!(trans.current_role(), RoleId::from_u32(1).unwrap());
183        assert_eq!(trans.type_(), TypeId::from_u32(2).unwrap());
184        assert_eq!(trans.new_role(), RoleId::from_u32(3).unwrap());
185        assert_eq!(trans.class(), ClassId::from_u32(4).unwrap());
186
187        let mut writer = Vec::new();
188        trans.serialize(&mut writer).unwrap();
189        assert_eq!(writer, data);
190    }
191
192    #[test]
193    fn test_role_allow_parse_and_serialize() {
194        let data = [
195            1, 0, 0, 0, // source_role = 1
196            2, 0, 0, 0, // new_role = 2
197        ];
198        let mut cursor = PolicyCursor::new(&data);
199        let allow = RoleAllow::parse(&mut cursor).unwrap();
200        assert_eq!(allow.source_role(), RoleId::from_u32(1).unwrap());
201        assert_eq!(allow.new_role(), RoleId::from_u32(2).unwrap());
202
203        let mut writer = Vec::new();
204        allow.serialize(&mut writer).unwrap();
205        assert_eq!(writer, data);
206    }
207
208    #[test]
209    fn test_role_policy_elements() {
210        let policy_bytes =
211            include_bytes!("../../testdata/composite_policies/compiled/role_transition_policy");
212        let new_policy = NewPolicy::parse(policy_bytes).expect("parse role_transition policy");
213        new_policy.validate().expect("validate role_transition policy");
214
215        assert_eq!(new_policy.role_transitions().len(), 2);
216        assert_eq!(new_policy.role_allowlist().len(), 1);
217    }
218}