use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
use super::{
connection::Connection,
error::{Error, Result},
protocol::{Audit, ProtocolState},
socket::NetlinkSocket,
};
const NLMSG_ERROR: u16 = 2;
const NLM_F_REQUEST: u16 = 0x01;
const NLM_F_ACK: u16 = 0x04;
const NLMSG_HDRLEN: usize = 16;
const AUDIT_GET: u16 = 1000;
const AUDIT_SET: u16 = 1001;
const AUDIT_LIST: u16 = 1002;
const AUDIT_ADD: u16 = 1003;
const AUDIT_DEL: u16 = 1004;
const AUDIT_USER: u16 = 1005;
const AUDIT_LOGIN: u16 = 1006;
const AUDIT_WATCH_INS: u16 = 1007;
const AUDIT_WATCH_REM: u16 = 1008;
const AUDIT_WATCH_LIST: u16 = 1009;
const AUDIT_SIGNAL_INFO: u16 = 1010;
const AUDIT_ADD_RULE: u16 = 1011;
const AUDIT_DEL_RULE: u16 = 1012;
const AUDIT_LIST_RULES: u16 = 1013;
const AUDIT_TRIM: u16 = 1014;
const AUDIT_MAKE_EQUIV: u16 = 1015;
const AUDIT_TTY_GET: u16 = 1016;
const AUDIT_TTY_SET: u16 = 1017;
const AUDIT_SET_FEATURE: u16 = 1018;
const AUDIT_GET_FEATURE: u16 = 1019;
const AUDIT_SYSCALL: u16 = 1300;
const AUDIT_PATH: u16 = 1302;
const AUDIT_IPC: u16 = 1303;
const AUDIT_SOCKADDR: u16 = 1306;
const AUDIT_CWD: u16 = 1307;
const AUDIT_EXECVE: u16 = 1309;
const AUDIT_IPC_SET_PERM: u16 = 1311;
const AUDIT_MQ_OPEN: u16 = 1312;
const AUDIT_MQ_SENDRECV: u16 = 1313;
const AUDIT_MQ_NOTIFY: u16 = 1314;
const AUDIT_MQ_GETSETATTR: u16 = 1315;
const AUDIT_EOE: u16 = 1320;
const AUDIT_SECCOMP: u16 = 1326;
const AUDIT_PROCTITLE: u16 = 1327;
const AUDIT_BPF: u16 = 1334;
const AUDIT_AVC: u16 = 1400;
const AUDIT_STATUS_ENABLED: u32 = 0x0001;
const AUDIT_STATUS_FAILURE: u32 = 0x0002;
const AUDIT_STATUS_PID: u32 = 0x0004;
const AUDIT_STATUS_RATE_LIMIT: u32 = 0x0008;
const AUDIT_STATUS_BACKLOG_LIMIT: u32 = 0x0010;
const AUDIT_STATUS_BACKLOG_WAIT_TIME: u32 = 0x0020;
const AUDIT_STATUS_LOST: u32 = 0x0040;
const AUDIT_FAIL_SILENT: u32 = 0;
const AUDIT_FAIL_PRINTK: u32 = 1;
const AUDIT_FAIL_PANIC: u32 = 2;
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct AuditStatus {
pub mask: u32,
pub enabled: u32,
pub failure: u32,
pub pid: u32,
pub rate_limit: u32,
pub backlog_limit: u32,
pub lost: u32,
pub backlog: u32,
pub feature_bitmap: u32,
pub backlog_wait_time: u32,
pub backlog_wait_time_actual: u32,
}
impl AuditStatus {
pub fn is_enabled(&self) -> bool {
self.enabled == 1
}
pub fn is_locked(&self) -> bool {
self.enabled == 2
}
pub fn failure_mode(&self) -> AuditFailureMode {
AuditFailureMode::from_u32(self.failure)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AuditFailureMode {
Silent,
Printk,
Panic,
Unknown(u32),
}
impl AuditFailureMode {
fn from_u32(val: u32) -> Self {
match val {
AUDIT_FAIL_SILENT => Self::Silent,
AUDIT_FAIL_PRINTK => Self::Printk,
AUDIT_FAIL_PANIC => Self::Panic,
other => Self::Unknown(other),
}
}
pub fn as_u32(&self) -> u32 {
match self {
Self::Silent => AUDIT_FAIL_SILENT,
Self::Printk => AUDIT_FAIL_PRINTK,
Self::Panic => AUDIT_FAIL_PANIC,
Self::Unknown(n) => *n,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct AuditRuleData {
pub flags: u32,
pub action: u32,
pub field_count: u32,
pub mask: [u32; 64],
pub fields: [u32; 64],
pub values: [u32; 64],
pub fieldflags: [u32; 64],
pub buflen: u32,
}
impl Default for AuditRuleData {
fn default() -> Self {
Self {
flags: 0,
action: 0,
field_count: 0,
mask: [0; 64],
fields: [0; 64],
values: [0; 64],
fieldflags: [0; 64],
buflen: 0,
}
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct AuditTtyStatus {
pub enabled: u32,
pub log_passwd: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct AuditFeatures {
pub vers: u32,
pub mask: u32,
pub features: u32,
pub lock: u32,
}
#[repr(C)]
#[derive(Debug, Clone, Copy, Default, FromBytes, IntoBytes, Immutable, KnownLayout)]
pub struct AuditSignalInfo {
pub uid: u32,
pub pid: u32,
pub ctx: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum AuditEventType {
Syscall,
Path,
Ipc,
Sockaddr,
Cwd,
Execve,
EndOfEvent,
Seccomp,
Proctitle,
Bpf,
Avc,
User,
Other(u16),
}
impl AuditEventType {
#[allow(dead_code)] pub fn from_u16(val: u16) -> Self {
match val {
AUDIT_SYSCALL => Self::Syscall,
AUDIT_PATH => Self::Path,
AUDIT_IPC => Self::Ipc,
AUDIT_SOCKADDR => Self::Sockaddr,
AUDIT_CWD => Self::Cwd,
AUDIT_EXECVE => Self::Execve,
AUDIT_EOE => Self::EndOfEvent,
AUDIT_SECCOMP => Self::Seccomp,
AUDIT_PROCTITLE => Self::Proctitle,
AUDIT_BPF => Self::Bpf,
AUDIT_AVC => Self::Avc,
AUDIT_USER => Self::User,
other => Self::Other(other),
}
}
pub fn as_u16(&self) -> u16 {
match self {
Self::Syscall => AUDIT_SYSCALL,
Self::Path => AUDIT_PATH,
Self::Ipc => AUDIT_IPC,
Self::Sockaddr => AUDIT_SOCKADDR,
Self::Cwd => AUDIT_CWD,
Self::Execve => AUDIT_EXECVE,
Self::EndOfEvent => AUDIT_EOE,
Self::Seccomp => AUDIT_SECCOMP,
Self::Proctitle => AUDIT_PROCTITLE,
Self::Bpf => AUDIT_BPF,
Self::Avc => AUDIT_AVC,
Self::User => AUDIT_USER,
Self::Other(n) => *n,
}
}
}
#[derive(Debug, Clone)]
pub struct AuditEvent {
pub event_type: AuditEventType,
pub msg_type: u16,
pub data: String,
}
impl Connection<Audit> {
pub fn new() -> Result<Self> {
let socket = NetlinkSocket::new(Audit::PROTOCOL)?;
Ok(Self::from_parts(socket, Audit))
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_status"))]
pub async fn get_status(&self) -> Result<AuditStatus> {
let seq = self.socket().next_seq();
let pid = self.socket().pid();
let mut buf = Vec::with_capacity(32);
buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&AUDIT_GET.to_ne_bytes()); buf.extend_from_slice(&(NLM_F_REQUEST | NLM_F_ACK).to_ne_bytes()); buf.extend_from_slice(&seq.to_ne_bytes()); buf.extend_from_slice(&pid.to_ne_bytes());
let len = buf.len() as u32;
buf[0..4].copy_from_slice(&len.to_ne_bytes());
self.socket().send(&buf).await?;
let data = self.socket().recv_msg().await?;
if data.len() < NLMSG_HDRLEN {
return Err(Error::InvalidMessage("response too short".into()));
}
let nlmsg_type = u16::from_ne_bytes([data[4], data[5]]);
if nlmsg_type == NLMSG_ERROR {
if data.len() >= 20 {
let errno = i32::from_ne_bytes([data[16], data[17], data[18], data[19]]);
if errno != 0 {
return Err(Error::from_errno(-errno));
}
}
let data = self.socket().recv_msg().await?;
return self.parse_status_response(&data);
}
self.parse_status_response(&data)
}
fn parse_status_response(&self, data: &[u8]) -> Result<AuditStatus> {
if data.len() < NLMSG_HDRLEN {
return Err(Error::InvalidMessage("response too short".into()));
}
let nlmsg_type = u16::from_ne_bytes([data[4], data[5]]);
if nlmsg_type != AUDIT_GET {
return Err(Error::InvalidMessage(format!(
"unexpected message type: {}",
nlmsg_type
)));
}
if data.len() < NLMSG_HDRLEN + std::mem::size_of::<AuditStatus>() {
let mut status = AuditStatus::default();
let payload = &data[NLMSG_HDRLEN..];
if payload.len() >= 32 {
status.mask = u32::from_ne_bytes([payload[0], payload[1], payload[2], payload[3]]);
status.enabled =
u32::from_ne_bytes([payload[4], payload[5], payload[6], payload[7]]);
status.failure =
u32::from_ne_bytes([payload[8], payload[9], payload[10], payload[11]]);
status.pid =
u32::from_ne_bytes([payload[12], payload[13], payload[14], payload[15]]);
status.rate_limit =
u32::from_ne_bytes([payload[16], payload[17], payload[18], payload[19]]);
status.backlog_limit =
u32::from_ne_bytes([payload[20], payload[21], payload[22], payload[23]]);
status.lost =
u32::from_ne_bytes([payload[24], payload[25], payload[26], payload[27]]);
status.backlog =
u32::from_ne_bytes([payload[28], payload[29], payload[30], payload[31]]);
}
return Ok(status);
}
let (status, _) = AuditStatus::ref_from_prefix(&data[NLMSG_HDRLEN..])
.map_err(|_| Error::InvalidMessage("failed to parse audit status".into()))?;
Ok(*status)
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_tty_status"))]
pub async fn get_tty_status(&self) -> Result<AuditTtyStatus> {
let seq = self.socket().next_seq();
let pid = self.socket().pid();
let mut buf = Vec::with_capacity(32);
buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&AUDIT_TTY_GET.to_ne_bytes()); buf.extend_from_slice(&(NLM_F_REQUEST | NLM_F_ACK).to_ne_bytes()); buf.extend_from_slice(&seq.to_ne_bytes()); buf.extend_from_slice(&pid.to_ne_bytes());
let len = buf.len() as u32;
buf[0..4].copy_from_slice(&len.to_ne_bytes());
self.socket().send(&buf).await?;
loop {
let data = self.socket().recv_msg().await?;
if data.len() < NLMSG_HDRLEN {
return Err(Error::InvalidMessage("response too short".into()));
}
let nlmsg_type = u16::from_ne_bytes([data[4], data[5]]);
if nlmsg_type == NLMSG_ERROR {
if data.len() >= 20 {
let errno = i32::from_ne_bytes([data[16], data[17], data[18], data[19]]);
if errno != 0 {
return Err(Error::from_errno(-errno));
}
}
continue;
}
if nlmsg_type == AUDIT_TTY_GET {
if data.len() < NLMSG_HDRLEN + std::mem::size_of::<AuditTtyStatus>() {
return Err(Error::InvalidMessage(
"TTY status response too short".into(),
));
}
let (status, _) = AuditTtyStatus::ref_from_prefix(&data[NLMSG_HDRLEN..])
.map_err(|_| Error::InvalidMessage("failed to parse TTY status".into()))?;
return Ok(*status);
}
return Err(Error::InvalidMessage(format!(
"unexpected message type: {}",
nlmsg_type
)));
}
}
#[tracing::instrument(level = "debug", skip_all, fields(method = "get_features"))]
pub async fn get_features(&self) -> Result<AuditFeatures> {
let seq = self.socket().next_seq();
let pid = self.socket().pid();
let mut buf = Vec::with_capacity(32);
buf.extend_from_slice(&0u32.to_ne_bytes()); buf.extend_from_slice(&AUDIT_GET_FEATURE.to_ne_bytes()); buf.extend_from_slice(&(NLM_F_REQUEST | NLM_F_ACK).to_ne_bytes()); buf.extend_from_slice(&seq.to_ne_bytes()); buf.extend_from_slice(&pid.to_ne_bytes());
let len = buf.len() as u32;
buf[0..4].copy_from_slice(&len.to_ne_bytes());
self.socket().send(&buf).await?;
loop {
let data = self.socket().recv_msg().await?;
if data.len() < NLMSG_HDRLEN {
return Err(Error::InvalidMessage("response too short".into()));
}
let nlmsg_type = u16::from_ne_bytes([data[4], data[5]]);
if nlmsg_type == NLMSG_ERROR {
if data.len() >= 20 {
let errno = i32::from_ne_bytes([data[16], data[17], data[18], data[19]]);
if errno != 0 {
return Err(Error::from_errno(-errno));
}
}
continue;
}
if nlmsg_type == AUDIT_GET_FEATURE {
if data.len() < NLMSG_HDRLEN + std::mem::size_of::<AuditFeatures>() {
return Err(Error::InvalidMessage("features response too short".into()));
}
let (features, _) = AuditFeatures::ref_from_prefix(&data[NLMSG_HDRLEN..])
.map_err(|_| Error::InvalidMessage("failed to parse features".into()))?;
return Ok(*features);
}
return Err(Error::InvalidMessage(format!(
"unexpected message type: {}",
nlmsg_type
)));
}
}
}
const _: () = {
let _ = AUDIT_SET;
let _ = AUDIT_LIST;
let _ = AUDIT_ADD;
let _ = AUDIT_DEL;
let _ = AUDIT_LOGIN;
let _ = AUDIT_WATCH_INS;
let _ = AUDIT_WATCH_REM;
let _ = AUDIT_WATCH_LIST;
let _ = AUDIT_SIGNAL_INFO;
let _ = AUDIT_ADD_RULE;
let _ = AUDIT_DEL_RULE;
let _ = AUDIT_LIST_RULES;
let _ = AUDIT_TRIM;
let _ = AUDIT_MAKE_EQUIV;
let _ = AUDIT_TTY_SET;
let _ = AUDIT_SET_FEATURE;
let _ = AUDIT_IPC_SET_PERM;
let _ = AUDIT_MQ_OPEN;
let _ = AUDIT_MQ_SENDRECV;
let _ = AUDIT_MQ_NOTIFY;
let _ = AUDIT_MQ_GETSETATTR;
let _ = AUDIT_STATUS_ENABLED;
let _ = AUDIT_STATUS_FAILURE;
let _ = AUDIT_STATUS_PID;
let _ = AUDIT_STATUS_RATE_LIMIT;
let _ = AUDIT_STATUS_BACKLOG_LIMIT;
let _ = AUDIT_STATUS_BACKLOG_WAIT_TIME;
let _ = AUDIT_STATUS_LOST;
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn audit_status_size() {
assert_eq!(std::mem::size_of::<AuditStatus>(), 44);
}
#[test]
fn audit_tty_status_size() {
assert_eq!(std::mem::size_of::<AuditTtyStatus>(), 8);
}
#[test]
fn audit_features_size() {
assert_eq!(std::mem::size_of::<AuditFeatures>(), 16);
}
#[test]
fn failure_mode_roundtrip() {
assert_eq!(AuditFailureMode::Silent.as_u32(), 0);
assert_eq!(AuditFailureMode::from_u32(0), AuditFailureMode::Silent);
assert_eq!(AuditFailureMode::Printk.as_u32(), 1);
assert_eq!(AuditFailureMode::from_u32(1), AuditFailureMode::Printk);
assert_eq!(AuditFailureMode::Panic.as_u32(), 2);
assert_eq!(AuditFailureMode::from_u32(2), AuditFailureMode::Panic);
}
#[test]
fn event_type_roundtrip() {
assert_eq!(AuditEventType::Syscall.as_u16(), 1300);
assert_eq!(AuditEventType::from_u16(1300), AuditEventType::Syscall);
assert_eq!(AuditEventType::Avc.as_u16(), 1400);
assert_eq!(AuditEventType::from_u16(1400), AuditEventType::Avc);
}
#[test]
fn audit_status_helpers() {
let status = AuditStatus {
enabled: 0,
..Default::default()
};
assert!(!status.is_enabled());
assert!(!status.is_locked());
let status = AuditStatus {
enabled: 1,
..Default::default()
};
assert!(status.is_enabled());
assert!(!status.is_locked());
let status = AuditStatus {
enabled: 2,
..Default::default()
};
assert!(!status.is_enabled());
assert!(status.is_locked());
}
}