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}