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