Skip to main content

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}