Skip to main content

starnix_core/security/
audit.rs

1// Copyright 2025 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::vfs::socket::AuditNetlinkClient;
6use linux_uapi::{
7    AUDIT_CONFIG_CHANGE, AUDIT_FAIL_PANIC, AUDIT_FAIL_PRINTK, AUDIT_FAIL_SILENT,
8    AUDIT_FIRST_USER_MSG, AUDIT_FIRST_USER_MSG2, AUDIT_GET, AUDIT_LAST_USER_MSG,
9    AUDIT_LAST_USER_MSG2, AUDIT_SET, AUDIT_STATUS_BACKLOG_LIMIT, AUDIT_STATUS_ENABLED,
10    AUDIT_STATUS_FAILURE, AUDIT_STATUS_LOST, AUDIT_STATUS_PID, AUDIT_USER,
11};
12use starnix_lifecycle::AtomicCounter;
13use starnix_logging::log_warn;
14use starnix_sync::{AuditSinkLock, LockDepGuard, LockDepMutex};
15use starnix_uapi::errors::Errno;
16use starnix_uapi::{audit_status, error, pid_t};
17use std::collections::VecDeque;
18use std::fmt::Display;
19use std::sync::Arc;
20use std::sync::atomic::{AtomicU8, AtomicU32, Ordering};
21use std::time::SystemTime;
22
23use crate::task::{ArgNameAndValue, CurrentTask, Kernel};
24const DEFAULT_BACKLOG_LIMIT: u32 = 128;
25
26/// Supported requests that manipulate the `AuditLogger`
27pub enum AuditRequest {
28    AuditGet,
29    AuditSet,
30    AuditUser,
31}
32
33impl TryFrom<u32> for AuditRequest {
34    type Error = Errno;
35
36    fn try_from(value: u32) -> Result<Self, Self::Error> {
37        match value {
38            AUDIT_GET => Ok(Self::AuditGet),
39            AUDIT_SET => Ok(Self::AuditSet),
40            AUDIT_USER
41            | AUDIT_FIRST_USER_MSG..=AUDIT_LAST_USER_MSG
42            | AUDIT_FIRST_USER_MSG2..=AUDIT_LAST_USER_MSG2 => Ok(Self::AuditUser),
43            _ => error!(ENOTSUP),
44        }
45    }
46}
47
48/// Possible modes of the audit framework.
49#[derive(PartialEq)]
50enum AuditMode {
51    Disabled,
52    Unspecified,
53    Enabled,
54}
55
56/// The audit sink reference structure.
57#[derive(Default)]
58struct AuditNetlinkClientRef {
59    /// Inner reference to the registered audit sink, if any.
60    client: Option<Arc<AuditNetlinkClient>>,
61    /// The PID of the registered audit sink.
62    pid: pid_t,
63    /// Deque for the audit messages, always present.
64    // TODO: https://fxbug.dev/438677236 - confirm single queue behaviour is valid.
65    messages: VecDeque<AuditMessage>,
66}
67
68/// Audit status structure defining the behaviour of the logger.
69struct AuditConfig {
70    /// The audit mode set by kernel command line.
71    audit_mode: AuditMode,
72    /// The maximum number of audit messages that can be stored by the logger.
73    backlog_limit: AtomicU32,
74    /// Action to take in case of audit failure.
75    fail_action: AtomicU8,
76    /// Socket to which the logger writes audit messages.
77    audit_sink: LockDepMutex<AuditNetlinkClientRef, AuditSinkLock>,
78}
79
80impl Default for AuditConfig {
81    fn default() -> Self {
82        Self {
83            audit_mode: AuditMode::Unspecified,
84            backlog_limit: AtomicU32::new(DEFAULT_BACKLOG_LIMIT),
85            fail_action: AtomicU8::new(AUDIT_FAIL_PRINTK as u8),
86            audit_sink: Default::default(),
87        }
88    }
89}
90
91impl AuditConfig {
92    pub fn new<'a>(cmdline_iter: impl Iterator<Item = ArgNameAndValue<'a>>) -> Self {
93        let mut config = Self::default();
94        // The logger may be disabled by the kernel command line.
95        config.apply_kernel_cmdline(cmdline_iter);
96        config
97    }
98
99    /// Function to apply the optional kernel command line arguments.
100    fn apply_kernel_cmdline<'a>(
101        &mut self,
102        cmdline_iter: impl Iterator<Item = ArgNameAndValue<'a>>,
103    ) {
104        for arg in cmdline_iter {
105            match arg {
106                ArgNameAndValue { name: "audit", value: Some(value) } => match value {
107                    "0" | "off" => self.audit_mode = AuditMode::Disabled,
108                    // If the audit option is "1"/"on"/anything else, fully enable auditing.
109                    _ => self.audit_mode = AuditMode::Enabled,
110                },
111                ArgNameAndValue { name: "audit_backlog_limit", value: Some(value) } => self
112                    .backlog_limit
113                    .store(value.parse().unwrap_or(DEFAULT_BACKLOG_LIMIT), Ordering::Release),
114                _ => (),
115            }
116        }
117    }
118}
119
120/// Audit logging structure.
121pub struct AuditLogger {
122    /// Audit status structure.
123    configuration: AuditConfig,
124    /// The number of audit messages lost due to writing errors.
125    lost_audit_messages: AtomicU32,
126    /// Monotonic counter for audit serial numbers
127    serial_counter: AtomicCounter<u64>,
128}
129
130impl AuditLogger {
131    pub fn new(kernel: &Kernel) -> Self {
132        Self {
133            configuration: AuditConfig::new(kernel.cmdline_args_iter()),
134            lost_audit_messages: Default::default(),
135            // Audit event serial numbers start at 1 (0 is reserved to indicate an error in
136            // `auparse_get_serial(3)`).
137            serial_counter: AtomicCounter::new(1),
138        }
139    }
140
141    pub fn is_disabled(&self) -> bool {
142        self.configuration.audit_mode == AuditMode::Disabled
143    }
144
145    /// Audit logging function that adds an audit message to the queue.
146    ///
147    /// The `audit_formatter` function is called only if the auditing is enabled.
148    pub fn audit_log<M: Display, T: FnOnce() -> M>(&self, audit_type: u16, audit_formatter: T) {
149        if self.configuration.audit_mode == AuditMode::Disabled {
150            return;
151        }
152        self.add_audit_to_backlog(audit_type, audit_formatter);
153    }
154
155    /// Called by the `NetlinkAuditClient` to pull the next audit log from the backlog.
156    pub fn read_audit_log(&self, client: &Arc<AuditNetlinkClient>) -> Option<AuditMessage> {
157        let mut client_guard = self.configuration.audit_sink.lock();
158        let Some(current_client) = client_guard.client.as_ref() else {
159            return None;
160        };
161        // Check if the current client is reading the backlog.
162        if !Arc::ptr_eq(&current_client, client) {
163            return None;
164        }
165        client_guard.messages.pop_front()
166    }
167
168    /// Function to detach the `AuditNetlinkClient` from the `AuditLogger` if
169    /// the provided client matches the one registered.
170    pub fn detach_client(&self, client: &Arc<AuditNetlinkClient>) {
171        let mut client_guard = self.configuration.audit_sink.lock();
172        if client_guard
173            .client
174            .as_ref()
175            .is_some_and(|current_client| Arc::ptr_eq(client, &current_client))
176        {
177            let pid = client_guard.pid;
178            client_guard.client = None;
179            client_guard.pid = 0;
180            client_guard.messages.clear();
181            drop(client_guard);
182            self.audit_log(AUDIT_CONFIG_CHANGE as u16, || format!("audit sink detached pid={pid}"));
183        }
184    }
185
186    /// Applies the specified changes to the audit logger settings.
187    pub fn set_status(
188        &self,
189        current_task: &CurrentTask,
190        status: audit_status,
191        client: &Arc<AuditNetlinkClient>,
192    ) -> Result<(), Errno> {
193        // Dummy check for enable/disable request. This should be used again if other
194        // subsystems will use the audit logger.
195        if status.mask & AUDIT_STATUS_ENABLED != 0 && status.enabled > 1 {
196            return error!(EINVAL);
197        }
198        if status.mask & AUDIT_STATUS_BACKLOG_LIMIT != 0 {
199            self.configuration.backlog_limit.store(status.backlog_limit, Ordering::Release);
200        }
201        if status.mask & AUDIT_STATUS_FAILURE != 0 {
202            self.configuration.fail_action.store(status.failure as u8, Ordering::Release);
203        }
204        if status.mask & AUDIT_STATUS_LOST != 0 {
205            self.lost_audit_messages.store(0, Ordering::Release);
206        }
207        if status.mask & AUDIT_STATUS_PID != 0 {
208            self.update_client(current_task.get_pid(), status.pid as pid_t, client)?;
209        }
210        Ok(())
211    }
212
213    /// Retrieve the `AuditConfig` as `audit_status` struct.
214    pub fn get_status(&self) -> audit_status {
215        let (pid, backlog) = {
216            let audit_sink = self.configuration.audit_sink.lock();
217            (audit_sink.pid as u32, audit_sink.messages.len() as u32)
218        };
219        audit_status {
220            mask: Default::default(),
221            enabled: u32::from(!self.is_disabled()),
222            failure: self.configuration.fail_action.load(Ordering::Acquire) as u32,
223            pid,
224            rate_limit: u32::MAX,
225            backlog_limit: self.configuration.backlog_limit.load(Ordering::Acquire),
226            lost: self.lost_audit_messages.load(Ordering::Acquire),
227            backlog,
228            __bindgen_anon_1: Default::default(),
229            backlog_wait_time: Default::default(),
230            backlog_wait_time_actual: Default::default(),
231        }
232    }
233
234    /// Retrieve the number of audit messages in the backlog.
235    pub fn get_backlog_count(&self, client: &Arc<AuditNetlinkClient>) -> usize {
236        let client_guard = self.configuration.audit_sink.lock();
237        if let Some(current_client) = &client_guard.client {
238            if Arc::ptr_eq(&current_client, client) {
239                return client_guard.messages.len();
240            }
241        }
242        0
243    }
244
245    /// Function to update the attached `client` and its PID
246    fn update_client(
247        &self,
248        pid: pid_t,
249        request_pid: pid_t,
250        client: &Arc<AuditNetlinkClient>,
251    ) -> Result<(), Errno> {
252        if request_pid == 0 {
253            let client_ref = {
254                let client_guard = self.configuration.audit_sink.lock();
255                // If there is no audit client registered and unregister is requested, return without error.
256                if client_guard.pid == 0 {
257                    return Ok(());
258                } else if pid != client_guard.pid {
259                    return error!(EPERM);
260                }
261                client_guard.client.clone()
262            };
263            client_ref.inspect(|client_ref| self.detach_client(&client_ref));
264            return Ok(());
265        }
266        if pid != request_pid {
267            return error!(EINVAL);
268        }
269
270        let mut client_guard = self.configuration.audit_sink.lock();
271        if client_guard.client.is_some() {
272            return error!(EEXIST);
273        }
274        client_guard.client = Some(client.clone());
275        client_guard.pid = pid;
276        drop(client_guard);
277        self.audit_log(AUDIT_CONFIG_CHANGE as u16, || format!("new audit sink attached pid={pid}"));
278        Ok(())
279    }
280
281    /// Add an audit message to the backlog if it is enabled.
282    fn add_audit_to_backlog<M: Display, T: FnOnce() -> M>(
283        &self,
284        audit_type: u16,
285        audit_formatter: T,
286    ) {
287        // At this point, we know that the audit framework is not disabled until reboot.
288        let audit_message = self.prepend_audit_metadata(audit_formatter);
289
290        let mut client_guard = self.configuration.audit_sink.lock();
291        // If there is no audit sink and the auditing is partially enabled, print and return
292        // without pushing the message to the backlog.
293        if client_guard.client.is_none() {
294            log_warn!("audit: type={audit_type} msg={audit_message}");
295        }
296
297        if client_guard.client.is_some() || self.configuration.audit_mode == AuditMode::Enabled {
298            self.push_back_audit(audit_type, audit_message, &mut client_guard);
299            let client = client_guard.client.clone();
300            drop(client_guard);
301            if let Some(client) = client {
302                client.notify();
303            }
304        }
305    }
306
307    /// Push the audit message in the backlog after checking its limit.
308    fn push_back_audit(
309        &self,
310        audit_type: u16,
311        audit_message: String,
312        client_guard: &mut LockDepGuard<'_, AuditNetlinkClientRef>,
313    ) {
314        // TODO: https://fxbug.dev/440090442 - implement backlog waiting.
315        if self.check_backlog(client_guard.messages.len() as u32) {
316            return;
317        }
318        client_guard.messages.push_back(AuditMessage { audit_type, message: audit_message.into() });
319    }
320
321    /// Function to check the backlog size against the backlog limit.
322    /// If the limit is set to 0, ignore the check.
323    ///
324    /// Return true if the limit is reached, false otherwise.
325    fn check_backlog(&self, backlog_size: u32) -> bool {
326        let limit = self.configuration.backlog_limit.load(Ordering::Acquire);
327        if limit != 0 && backlog_size >= limit {
328            let lost = self.lost_audit_messages.fetch_add(1, Ordering::Release) + 1;
329            log_warn!("audit_lost={lost} backlog_limit={limit}");
330            // If the backlog is full, use failure-to-log action.
331            match self.configuration.fail_action.load(Ordering::Acquire) as u32 {
332                AUDIT_FAIL_PANIC => panic!("backlog limit exceeded"),
333                AUDIT_FAIL_PRINTK => log_warn!("backlog limit exceeded"),
334                AUDIT_FAIL_SILENT | _ => (),
335            }
336            return true;
337        }
338        false
339    }
340
341    /// Function to prepend an audit message with a timestamp and serial number.
342    fn prepend_audit_metadata<M: Display, T: FnOnce() -> M>(&self, audit: T) -> String {
343        let duration = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap_or_default();
344
345        format!(
346            "audit({}.{:03}:{}): {}",
347            duration.as_secs(),
348            duration.subsec_millis(),
349            self.serial_counter.next(),
350            audit()
351        )
352    }
353}
354
355/// Audit message structure.
356pub struct AuditMessage {
357    /// The type of the audit message (e.g., AUDIT_AVC).
358    pub audit_type: u16,
359    /// The message to be audit-logged.
360    pub message: Vec<u8>,
361}
362
363#[cfg(test)]
364mod tests {
365    use super::*;
366    use regex_lite::Regex;
367
368    fn test_logger(configuration: AuditConfig) -> AuditLogger {
369        AuditLogger {
370            configuration,
371            lost_audit_messages: Default::default(),
372            serial_counter: AtomicCounter::new(1),
373        }
374    }
375
376    #[::fuchsia::test]
377    fn prepend_audit_metadata_formats_millis_and_serial() {
378        let logger = test_logger(AuditConfig::default());
379
380        let re1 = Regex::new(r"^audit\(\d+\.\d{3}:1\): msg1$").unwrap();
381        let msg1 = logger.prepend_audit_metadata(|| "msg1");
382        assert!(re1.is_match(&msg1), "unexpected format: {msg1}");
383
384        let re2 = Regex::new(r"^audit\(\d+\.\d{3}:2\): msg2$").unwrap();
385        let msg2 = logger.prepend_audit_metadata(|| "msg2");
386        assert!(re2.is_match(&msg2), "unexpected format: {msg2}");
387    }
388
389    #[::fuchsia::test]
390    fn get_status_reports_backlog_len() {
391        let logger = test_logger(AuditConfig::new(
392            [ArgNameAndValue { name: "audit", value: Some("1") }].into_iter(),
393        ));
394        assert_eq!(logger.get_status().pid, 0);
395        assert_eq!(logger.get_status().backlog, 0);
396
397        logger.audit_log(AUDIT_USER as u16, || "test audit message 1");
398        assert_eq!(logger.get_status().backlog, 1);
399
400        logger.audit_log(AUDIT_USER as u16, || "test audit message 2");
401        assert_eq!(logger.get_status().backlog, 2);
402    }
403
404    #[::fuchsia::test]
405    fn get_status_reports_enabled() {
406        let enabled_logger = test_logger(AuditConfig::new(
407            [ArgNameAndValue { name: "audit", value: Some("1") }].into_iter(),
408        ));
409        assert_eq!(enabled_logger.get_status().enabled, 1);
410
411        let default_logger = test_logger(AuditConfig::default());
412        assert_eq!(default_logger.get_status().enabled, 1);
413
414        let disabled_logger = test_logger(AuditConfig::new(
415            [ArgNameAndValue { name: "audit", value: Some("0") }].into_iter(),
416        ));
417        assert_eq!(disabled_logger.get_status().enabled, 0);
418    }
419}