nlink/netlink/selinux.rs
1//! SELinux implementation for `Connection<SELinux>`.
2//!
3//! This module provides methods for receiving SELinux event notifications
4//! via the NETLINK_SELINUX protocol.
5//!
6//! # Overview
7//!
8//! NETLINK_SELINUX provides notifications when:
9//! - SELinux enforcement mode changes (setenforce 0/1)
10//! - A new SELinux policy is loaded
11//!
12//! # Example
13//!
14//! ```ignore
15//! use nlink::netlink::{Connection, SELinux};
16//! use nlink::netlink::selinux::SELinuxEvent;
17//!
18//! let conn = Connection::<SELinux>::new()?;
19//!
20//! // Receive SELinux events
21//! loop {
22//! let event = conn.recv().await?;
23//! match event {
24//! SELinuxEvent::SetEnforce { enforcing } => {
25//! println!("SELinux mode changed: {}",
26//! if enforcing { "enforcing" } else { "permissive" });
27//! }
28//! SELinuxEvent::PolicyLoad { seqno } => {
29//! println!("Policy loaded, sequence: {}", seqno);
30//! }
31//! }
32//! }
33//! ```
34
35use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
36
37use super::connection::Connection;
38use super::error::{Error, Result};
39use super::protocol::{ProtocolState, SELinux};
40use super::socket::NetlinkSocket;
41
42// Netlink header size
43const NLMSG_HDRLEN: usize = 16;
44
45// SELinux netlink message types (from linux/selinux_netlink.h)
46/// Policy enforcement status change.
47const SELNL_MSG_SETENFORCE: u16 = 0x10;
48/// Policy was (re)loaded.
49const SELNL_MSG_POLICYLOAD: u16 = 0x11;
50
51// SELinux netlink multicast group
52/// AVC decisions group (receives all events).
53const SELNLGRP_AVC: u32 = 1;
54
55/// SELinux setenforce message payload.
56#[repr(C)]
57#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
58pub struct SelnlMsgSetenforce {
59 /// 1 = enforcing, 0 = permissive.
60 pub val: i32,
61}
62
63/// SELinux policyload message payload.
64#[repr(C)]
65#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
66pub struct SelnlMsgPolicyload {
67 /// Policy sequence number.
68 pub seqno: u32,
69}
70
71/// SELinux event received from the kernel.
72#[derive(Debug, Clone, PartialEq, Eq)]
73#[non_exhaustive]
74pub enum SELinuxEvent {
75 /// SELinux enforcement mode changed.
76 SetEnforce {
77 /// True if now in enforcing mode, false if permissive.
78 enforcing: bool,
79 },
80 /// SELinux policy was loaded.
81 PolicyLoad {
82 /// Policy sequence number.
83 seqno: u32,
84 },
85}
86
87impl Connection<SELinux> {
88 /// Create a new SELinux connection.
89 ///
90 /// This creates a netlink socket bound to the SELNLGRP_AVC multicast group
91 /// to receive SELinux event notifications.
92 ///
93 /// # Example
94 ///
95 /// ```ignore
96 /// use nlink::netlink::{Connection, SELinux};
97 ///
98 /// let conn = Connection::<SELinux>::new()?;
99 /// ```
100 pub fn new() -> Result<Self> {
101 let mut socket = NetlinkSocket::new(SELinux::PROTOCOL)?;
102
103 // Bind to the AVC multicast group to receive events
104 socket.add_membership(SELNLGRP_AVC)?;
105
106 Ok(Self::from_parts(socket, SELinux))
107 }
108
109 /// Receive the next SELinux event.
110 ///
111 /// This method blocks until an event is received from the kernel.
112 ///
113 /// # Example
114 ///
115 /// ```ignore
116 /// use nlink::netlink::{Connection, SELinux};
117 /// use nlink::netlink::selinux::SELinuxEvent;
118 ///
119 /// let conn = Connection::<SELinux>::new()?;
120 ///
121 /// loop {
122 /// match conn.recv().await? {
123 /// SELinuxEvent::SetEnforce { enforcing } => {
124 /// if enforcing {
125 /// println!("SELinux now enforcing");
126 /// } else {
127 /// println!("SELinux now permissive");
128 /// }
129 /// }
130 /// SELinuxEvent::PolicyLoad { seqno } => {
131 /// println!("New policy loaded (seqno: {})", seqno);
132 /// }
133 /// }
134 /// }
135 /// ```
136 pub async fn recv(&self) -> Result<SELinuxEvent> {
137 loop {
138 let data = self.socket().recv_msg().await?;
139
140 if data.len() < NLMSG_HDRLEN {
141 continue; // Invalid message, skip
142 }
143
144 let nlmsg_type = u16::from_ne_bytes([data[4], data[5]]);
145 let payload = &data[NLMSG_HDRLEN..];
146
147 match nlmsg_type {
148 SELNL_MSG_SETENFORCE => {
149 if payload.len() < std::mem::size_of::<SelnlMsgSetenforce>() {
150 return Err(Error::InvalidMessage("setenforce payload too short".into()));
151 }
152
153 let (msg, _) = SelnlMsgSetenforce::ref_from_prefix(payload)
154 .map_err(|_| Error::InvalidMessage("failed to parse setenforce".into()))?;
155
156 return Ok(SELinuxEvent::SetEnforce {
157 enforcing: msg.val != 0,
158 });
159 }
160 SELNL_MSG_POLICYLOAD => {
161 if payload.len() < std::mem::size_of::<SelnlMsgPolicyload>() {
162 return Err(Error::InvalidMessage("policyload payload too short".into()));
163 }
164
165 let (msg, _) = SelnlMsgPolicyload::ref_from_prefix(payload)
166 .map_err(|_| Error::InvalidMessage("failed to parse policyload".into()))?;
167
168 return Ok(SELinuxEvent::PolicyLoad { seqno: msg.seqno });
169 }
170 _ => {
171 // Unknown message type, skip
172 continue;
173 }
174 }
175 }
176 }
177
178 /// Check if SELinux is available on this system.
179 ///
180 /// This checks if the SELinux filesystem is mounted.
181 pub fn is_available() -> bool {
182 std::path::Path::new("/sys/fs/selinux").exists()
183 }
184
185 /// Get the current SELinux enforcement mode.
186 ///
187 /// Returns `true` if enforcing, `false` if permissive,
188 /// or an error if SELinux is not available.
189 pub fn get_enforce() -> Result<bool> {
190 let content = std::fs::read_to_string("/sys/fs/selinux/enforce")?;
191
192 Ok(content.trim() == "1")
193 }
194}
195
196#[cfg(test)]
197mod tests {
198 use super::*;
199
200 #[test]
201 fn setenforce_size() {
202 assert_eq!(std::mem::size_of::<SelnlMsgSetenforce>(), 4);
203 }
204
205 #[test]
206 fn policyload_size() {
207 assert_eq!(std::mem::size_of::<SelnlMsgPolicyload>(), 4);
208 }
209
210 #[test]
211 fn event_eq() {
212 assert_eq!(
213 SELinuxEvent::SetEnforce { enforcing: true },
214 SELinuxEvent::SetEnforce { enforcing: true }
215 );
216
217 assert_ne!(
218 SELinuxEvent::SetEnforce { enforcing: true },
219 SELinuxEvent::SetEnforce { enforcing: false }
220 );
221
222 assert_eq!(
223 SELinuxEvent::PolicyLoad { seqno: 42 },
224 SELinuxEvent::PolicyLoad { seqno: 42 }
225 );
226 }
227}