Skip to main content

selinux/new_policy/
object_contexts.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
5//! System object context labeling rules parsed from SELinux binary policy.
6
7use selinux_policy_derive::{Parse, Serialize, Validate};
8
9use super::classes::ClassId;
10use super::context::Context;
11use super::error::{ParseError, SerializeError};
12use super::metadata::PolicyVersion;
13use super::parser::{Array, ByteArray, PolicyCursor, PolicyWriter};
14use super::traits::{Parse, Serialize};
15
16/// Named context pair mapping a filesystem type string to mount and root security [`Context`]s.
17///
18/// Corresponding SELinux text policy syntax: `fs_con <fs_type> <fs_context> <root_context>`.
19#[derive(Debug, Clone, Parse, Serialize, Validate)]
20pub struct FilesystemContext {
21    name: ByteArray,
22    fs_context: Context,
23    root_context: Context,
24}
25
26/// Named context pair mapping a network interface string to device and packet security [`Context`]s.
27///
28/// Corresponding SELinux text policy syntax: `netifcon <interface_name> <if_context> <packet_context>`.
29#[derive(Debug, Clone, Parse, Serialize, Validate)]
30pub struct NetworkInterfaceContext {
31    name: ByteArray,
32    if_context: Context,
33    msg_context: Context,
34}
35
36/// Port specification mapping a protocol and port range to a security [`Context`].
37///
38/// Corresponding SELinux text policy syntax: `portcon <protocol> <port_low>-<port_high> <context>`.
39#[derive(Debug, Clone, Parse, Serialize, Validate)]
40pub struct PortContext {
41    protocol: u32,
42    low_port: u32,
43    high_port: u32,
44    context: Context,
45}
46
47/// IPv4 node specification mapping an address and mask to a security [`Context`].
48///
49/// Corresponding SELinux text policy syntax: `nodecon <ipv4_addr> <netmask> <context>`.
50#[derive(Debug, Clone, Parse, Serialize, Validate)]
51pub struct IPv4NodeContext {
52    address: u32,
53    mask: u32,
54    context: Context,
55}
56
57/// Discriminates among the different kinds of `fs_use_*` labeling statements in policy.
58#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, Parse, Serialize, Validate)]
59#[policy(wire_type = u32)]
60pub enum FsUseType {
61    Xattr = 1,
62    Trans = 2,
63    Task = 3,
64}
65
66/// Filesystem labeling behavior rule (`fs_use_xattr`, `fs_use_trans`, `fs_use_task`).
67///
68/// Corresponding SELinux text policy syntax: `fs_use_xattr <fs_type> <context>`,
69/// `fs_use_trans <fs_type> <context>`, or `fs_use_task <fs_type> <context>`.
70#[derive(Debug, Clone, Parse, Serialize, Validate)]
71pub struct FsUse {
72    behavior: FsUseType,
73    name: ByteArray,
74    context: Context,
75}
76
77impl FsUse {
78    /// Returns the filesystem use statement behavior type.
79    pub fn behavior(&self) -> FsUseType {
80        self.behavior
81    }
82
83    /// Returns the filesystem type name bytes (`<fs_type>`).
84    pub fn fs_type(&self) -> &[u8] {
85        &self.name
86    }
87
88    /// Returns the security [`Context`].
89    pub fn context(&self) -> &Context {
90        &self.context
91    }
92}
93
94/// IPv6 node specification mapping a 128-bit address and mask to a security [`Context`].
95///
96/// Corresponding SELinux text policy syntax: `nodecon <ipv6_addr> <netmask> <context>`.
97#[derive(Debug, Clone, Parse, Serialize, Validate)]
98pub struct IPv6NodeContext {
99    address: [u32; 4],
100    mask: [u32; 4],
101    context: Context,
102}
103
104/// InfiniBand partition key specification (for policy versions >= [`PolicyVersion::MIN_INFINIBAND`]).
105///
106/// Corresponding SELinux text policy syntax: `ibpkeycon <subnet_prefix> <pkey_low>-<pkey_high> <context>`.
107#[derive(Debug, Clone, Parse, Serialize, Validate)]
108pub struct InfiniBandPartitionKey {
109    low: u32,
110    high: u32,
111    context: Context,
112}
113
114/// InfiniBand end port specification (for policy versions >= [`PolicyVersion::MIN_INFINIBAND`]).
115///
116/// Corresponding SELinux text policy syntax: `ibendportcon <device_name> <port> <context>`.
117#[derive(Debug, Clone, Parse, Serialize, Validate)]
118pub struct InfiniBandEndPort {
119    name: ByteArray,
120    port: u32,
121    context: Context,
122}
123
124/// Container for system object context labeling statements (`fs_con`, `portcon`, `netifcon`,
125/// `nodecon`, `fs_use_*`, `ibpkeycon`, `ibendportcon`).
126#[derive(Debug, Clone, Validate)]
127pub struct ObjectContexts {
128    filesystems: Array<FilesystemContext>,
129    ports: Array<PortContext>,
130    network_interfaces: Array<NetworkInterfaceContext>,
131    ipv4_nodes: Array<IPv4NodeContext>,
132    fs_uses: Array<FsUse>,
133    ipv6_nodes: Array<IPv6NodeContext>,
134    infiniband_partition_keys: Array<InfiniBandPartitionKey>,
135    infiniband_end_ports: Array<InfiniBandEndPort>,
136}
137
138impl ObjectContexts {
139    /// Returns the filesystem labeling statements table.
140    pub fn filesystems(&self) -> &[FilesystemContext] {
141        &self.filesystems
142    }
143
144    /// Returns the network port labeling statements table.
145    pub fn ports(&self) -> &[PortContext] {
146        &self.ports
147    }
148
149    /// Returns the network interface labeling statements table.
150    pub fn network_interfaces(&self) -> &[NetworkInterfaceContext] {
151        &self.network_interfaces
152    }
153
154    /// Returns the IPv4 node labeling statements table.
155    pub fn ipv4_nodes(&self) -> &[IPv4NodeContext] {
156        &self.ipv4_nodes
157    }
158
159    /// Returns the filesystem behavior labeling statements table.
160    pub fn fs_uses(&self) -> &[FsUse] {
161        &self.fs_uses
162    }
163
164    /// Returns the IPv6 node labeling statements table.
165    pub fn ipv6_nodes(&self) -> &[IPv6NodeContext] {
166        &self.ipv6_nodes
167    }
168
169    /// Returns the InfiniBand partition key labeling statements table.
170    pub fn infiniband_partition_keys(&self) -> &[InfiniBandPartitionKey] {
171        &self.infiniband_partition_keys
172    }
173
174    /// Returns the InfiniBand end port labeling statements table.
175    pub fn infiniband_end_ports(&self) -> &[InfiniBandEndPort] {
176        &self.infiniband_end_ports
177    }
178}
179
180impl Parse for ObjectContexts {
181    fn parse(cursor: &mut PolicyCursor<'_>) -> Result<Self, ParseError> {
182        let filesystems = Array::<FilesystemContext>::parse(cursor)?;
183        let ports = Array::<PortContext>::parse(cursor)?;
184        let network_interfaces = Array::<NetworkInterfaceContext>::parse(cursor)?;
185        let ipv4_nodes = Array::<IPv4NodeContext>::parse(cursor)?;
186        let fs_uses = Array::<FsUse>::parse(cursor)?;
187        let ipv6_nodes = Array::<IPv6NodeContext>::parse(cursor)?;
188        let (infiniband_partition_keys, infiniband_end_ports) =
189            if cursor.policy_version() >= PolicyVersion::MIN_INFINIBAND {
190                (
191                    Array::<InfiniBandPartitionKey>::parse(cursor)?,
192                    Array::<InfiniBandEndPort>::parse(cursor)?,
193                )
194            } else {
195                (Array::default(), Array::default())
196            };
197
198        Ok(Self {
199            filesystems,
200            ports,
201            network_interfaces,
202            ipv4_nodes,
203            fs_uses,
204            ipv6_nodes,
205            infiniband_partition_keys,
206            infiniband_end_ports,
207        })
208    }
209}
210
211impl Serialize for ObjectContexts {
212    fn serialize(&self, writer: &mut PolicyWriter<'_>) -> Result<(), SerializeError> {
213        self.filesystems.serialize(writer)?;
214        self.ports.serialize(writer)?;
215        self.network_interfaces.serialize(writer)?;
216        self.ipv4_nodes.serialize(writer)?;
217        self.fs_uses.serialize(writer)?;
218        self.ipv6_nodes.serialize(writer)?;
219        if writer.version() >= PolicyVersion::MIN_INFINIBAND {
220            self.infiniband_partition_keys.serialize(writer)?;
221            self.infiniband_end_ports.serialize(writer)?;
222        }
223        Ok(())
224    }
225}
226
227/// Context rule for a specific path prefix within a generic filesystem (`genfscon`).
228#[derive(Debug, Clone, PartialEq, Eq, Parse, Serialize, Validate)]
229pub struct GenfsConPath {
230    partial_path: ByteArray,
231    class: Option<ClassId>,
232    context: Context,
233}
234
235impl GenfsConPath {
236    /// Returns the partial path bytes relative to the root of the filesystem.
237    pub fn partial_path(&self) -> &[u8] {
238        &self.partial_path
239    }
240
241    /// Returns the target [`ClassId`] if specified (0 applies to all object classes).
242    pub fn class(&self) -> Option<ClassId> {
243        self.class
244    }
245
246    /// Returns the security [`Context`].
247    pub fn context(&self) -> &Context {
248        &self.context
249    }
250}
251
252/// Generic filesystem labeling statement (`genfscon [fs_type] [partial_path] [class] [context]`).
253#[derive(Debug, Clone, PartialEq, Eq, Parse, Serialize, Validate)]
254pub struct GenfsCon {
255    fs_type: ByteArray,
256    paths: Array<GenfsConPath>,
257}
258
259impl GenfsCon {
260    /// Returns the filesystem type name bytes (e.g. `b"proc"` or `b"sysfs"`).
261    pub fn fs_type(&self) -> &[u8] {
262        &self.fs_type
263    }
264
265    /// Returns the array of partial path context rules for this filesystem type.
266    pub fn paths(&self) -> &[GenfsConPath] {
267        &self.paths
268    }
269}
270
271#[cfg(test)]
272mod tests {
273    use super::*;
274    use crate::new_policy::traits::PolicyId;
275    use crate::new_policy::{NewPolicy, UserId};
276
277    #[test]
278    fn test_object_contexts_minimal_policy() {
279        let policy_bytes =
280            include_bytes!("../../testdata/composite_policies/compiled/minimal_policy");
281        let policy = NewPolicy::parse(policy_bytes).expect("parse minimal_policy");
282        policy.validate().expect("validate minimal_policy");
283
284        let object_contexts = policy.object_contexts();
285        assert!(object_contexts.filesystems().is_empty());
286        assert!(object_contexts.ports().is_empty());
287        assert!(object_contexts.network_interfaces().is_empty());
288        assert!(object_contexts.ipv4_nodes().is_empty());
289        assert!(!object_contexts.fs_uses().is_empty());
290        assert!(object_contexts.ipv6_nodes().is_empty());
291        assert!(object_contexts.infiniband_partition_keys().is_empty());
292        assert!(object_contexts.infiniband_end_ports().is_empty());
293    }
294
295    #[test]
296    fn test_genfscon_parse_and_serialize() {
297        let data = [
298            // GenfsCon fs_type (ByteArray: len + "sysfs"):
299            5, 0, 0, 0, b's', b'y', b's', b'f', b's', // paths count = 1:
300            1, 0, 0, 0, // GenfsConPath partial_path (ByteArray: len + "/"):
301            1, 0, 0, 0, b'/', // class = 0 (all classes):
302            0, 0, 0, 0, // Context:
303            1, 0, 0, 0, // user = 1
304            1, 0, 0, 0, // role = 1
305            1, 0, 0, 0, // type = 1
306            // MlsRange:
307            1, 0, 0, 0, // levels_count = 1
308            1, 0, 0, 0, // sensitivity_low = 1
309            64, 0, 0, 0, // map_item_size_bits = 64
310            0, 0, 0, 0, // high_bit = 0
311            0, 0, 0, 0, // categories count = 0
312        ];
313        let mut cursor = PolicyCursor::new(&data);
314        let genfscon = GenfsCon::parse(&mut cursor).expect("parse GenfsCon");
315        assert_eq!(genfscon.fs_type(), b"sysfs");
316        assert_eq!(genfscon.paths().len(), 1);
317        let path = &genfscon.paths()[0];
318        assert_eq!(path.partial_path(), b"/");
319        assert_eq!(path.class(), None);
320        assert_eq!(path.context().user(), UserId::from_u32(1).unwrap());
321
322        let mut writer = Vec::new();
323        let mut policy_writer = PolicyWriter::new(PolicyVersion::V33, &mut writer);
324        genfscon.serialize(&mut policy_writer).expect("serialize GenfsCon");
325        assert_eq!(writer.as_slice(), &data);
326    }
327}