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)]
73pub enum SELinuxEvent {
74 /// SELinux enforcement mode changed.
75 SetEnforce {
76 /// True if now in enforcing mode, false if permissive.
77 enforcing: bool,
78 },
79 /// SELinux policy was loaded.
80 PolicyLoad {
81 /// Policy sequence number.
82 seqno: u32,
83 },
84}
85
86impl Connection<SELinux> {
87 /// Create a new SELinux connection.
88 ///
89 /// This creates a netlink socket bound to the SELNLGRP_AVC multicast group
90 /// to receive SELinux event notifications.
91 ///
92 /// # Example
93 ///
94 /// ```ignore
95 /// use nlink::netlink::{Connection, SELinux};
96 ///
97 /// let conn = Connection::<SELinux>::new()?;
98 /// ```
99 pub fn new() -> Result<Self> {
100 let mut socket = NetlinkSocket::new(SELinux::PROTOCOL)?;
101
102 // Bind to the AVC multicast group to receive events
103 socket.add_membership(SELNLGRP_AVC)?;
104
105 Ok(Self::from_parts(socket, SELinux))
106 }
107
108 /// Receive the next SELinux event.
109 ///
110 /// This method blocks until an event is received from the kernel.
111 ///
112 /// # Example
113 ///
114 /// ```ignore
115 /// use nlink::netlink::{Connection, SELinux};
116 /// use nlink::netlink::selinux::SELinuxEvent;
117 ///
118 /// let conn = Connection::<SELinux>::new()?;
119 ///
120 /// loop {
121 /// match conn.recv().await? {
122 /// SELinuxEvent::SetEnforce { enforcing } => {
123 /// if enforcing {
124 /// println!("SELinux now enforcing");
125 /// } else {
126 /// println!("SELinux now permissive");
127 /// }
128 /// }
129 /// SELinuxEvent::PolicyLoad { seqno } => {
130 /// println!("New policy loaded (seqno: {})", seqno);
131 /// }
132 /// }
133 /// }
134 /// ```
135 pub async fn recv(&self) -> Result<SELinuxEvent> {
136 loop {
137 let data = self.socket().recv_msg().await?;
138
139 if data.len() < NLMSG_HDRLEN {
140 continue; // Invalid message, skip
141 }
142
143 let nlmsg_type = u16::from_ne_bytes([data[4], data[5]]);
144 let payload = &data[NLMSG_HDRLEN..];
145
146 match nlmsg_type {
147 SELNL_MSG_SETENFORCE => {
148 if payload.len() < std::mem::size_of::<SelnlMsgSetenforce>() {
149 return Err(Error::InvalidMessage("setenforce payload too short".into()));
150 }
151
152 let (msg, _) = SelnlMsgSetenforce::ref_from_prefix(payload)
153 .map_err(|_| Error::InvalidMessage("failed to parse setenforce".into()))?;
154
155 return Ok(SELinuxEvent::SetEnforce {
156 enforcing: msg.val != 0,
157 });
158 }
159 SELNL_MSG_POLICYLOAD => {
160 if payload.len() < std::mem::size_of::<SelnlMsgPolicyload>() {
161 return Err(Error::InvalidMessage("policyload payload too short".into()));
162 }
163
164 let (msg, _) = SelnlMsgPolicyload::ref_from_prefix(payload)
165 .map_err(|_| Error::InvalidMessage("failed to parse policyload".into()))?;
166
167 return Ok(SELinuxEvent::PolicyLoad { seqno: msg.seqno });
168 }
169 _ => {
170 // Unknown message type, skip
171 continue;
172 }
173 }
174 }
175 }
176
177 /// Check if SELinux is available on this system.
178 ///
179 /// This checks if the SELinux filesystem is mounted.
180 pub fn is_available() -> bool {
181 std::path::Path::new("/sys/fs/selinux").exists()
182 }
183
184 /// Get the current SELinux enforcement mode.
185 ///
186 /// Returns `true` if enforcing, `false` if permissive,
187 /// or an error if SELinux is not available.
188 pub fn get_enforce() -> Result<bool> {
189 let content = std::fs::read_to_string("/sys/fs/selinux/enforce")?;
190
191 Ok(content.trim() == "1")
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn setenforce_size() {
201 assert_eq!(std::mem::size_of::<SelnlMsgSetenforce>(), 4);
202 }
203
204 #[test]
205 fn policyload_size() {
206 assert_eq!(std::mem::size_of::<SelnlMsgPolicyload>(), 4);
207 }
208
209 #[test]
210 fn event_eq() {
211 assert_eq!(
212 SELinuxEvent::SetEnforce { enforcing: true },
213 SELinuxEvent::SetEnforce { enforcing: true }
214 );
215
216 assert_ne!(
217 SELinuxEvent::SetEnforce { enforcing: true },
218 SELinuxEvent::SetEnforce { enforcing: false }
219 );
220
221 assert_eq!(
222 SELinuxEvent::PolicyLoad { seqno: 42 },
223 SELinuxEvent::PolicyLoad { seqno: 42 }
224 );
225 }
226}