/
githubmirror
/
audit-userspace
Обзор
Документация
Войти
/
githubmirror
/
audit-userspace
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
v4.1.2
audisp/queue.c
448 строк
11 KB
Steve Grubb
Consolidate change_runlevel to one
01 авг 2025, 22:24
01 авг 2025, 22:24
346c08e
Код
Авторство
О чём код?
/* queue.c -- * Copyright 2007,2013,2015,2018,2022,2025 Red Hat Inc. * All Rights Reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Authors: * Steve Grubb <sgrubb@redhat.com> */ #include "config.h" #include <pthread.h> #include <stdlib.h> #include <unistd.h> #include <semaphore.h> #include <errno.h> #include <syslog.h> #include <string.h> #include <fcntl.h> #include "queue.h" #include "common.h" /* * Audisp uses a simple ring buffer to pass events from auditd to its * plugin dispatcher thread. The goal is to avoid blocking producers * and consumers on a mutex. The semaphore below tracks how many events * are queued while the atomic indices maintain the next slot to use for * enqueueing and dequeueing. A mutex is only required when the queue is * resized. The queue is not safe for multiple producers. Atomic load/store of * the index alone does not guarantee exclusive access to a ring buffer entry. * A compare‑exchange or other reservation mechanism (or simply a mutex) is * required to make the queue race‑free. However, auditd is the only producer * and audisp is the only consumer, so the queue is safe in practice. */ static volatile event_t **q; static pthread_mutex_t queue_lock; static sem_t queue_nonempty; /* * q_next points to the next free slot for the producer. * q_last points to the next item the consumer should read. * Both are updated atomically and wrap at q_depth. */ #ifdef HAVE_ATOMIC static atomic_uint q_next, q_last; extern ATOMIC_INT disp_hup; #else static unsigned int q_next, q_last; /* Fallback when atomics are absent */ extern volatile ATOMIC_INT disp_hup; #endif static unsigned int q_depth, processing_suspended, overflowed; static ATOMIC_UNSIGNED currently_used, max_used; static int queue_full_warning = 0; static int persist_fd = -1; static int persist_sync = 0; #define QUEUE_FULL_LIMIT 5 void reset_suspended(void) { processing_suspended = 0; queue_full_warning = 0; } static int queue_load_file(int fd) { FILE *f; char buf[MAX_AUDIT_MESSAGE_LENGTH]; unsigned int count = 0; if (fd < 0) return -1; f = fdopen(dup(fd), "r"); if (f == NULL) return -1; while (count < q_depth && fgets(buf, sizeof(buf), f)) { event_t *e = calloc(1, sizeof(*e)); if (e == NULL) break; strncpy(e->data, buf, MAX_AUDIT_MESSAGE_LENGTH); e->data[MAX_AUDIT_MESSAGE_LENGTH-1] = '\0'; e->hdr.size = strlen(e->data); e->hdr.ver = AUDISP_PROTOCOL_VER2; q[count] = e; sem_post(&queue_nonempty); count++; } #ifdef HAVE_ATOMIC atomic_store_explicit(&q_next, count % q_depth, memory_order_relaxed); atomic_store_explicit(&q_last, 0, memory_order_relaxed); #else q_next = count % q_depth; q_last = 0; #endif currently_used = count; if (max_used < count) max_used = count; fclose(f); return 0; } int init_queue_extended(unsigned int size, int flags, const char *path) { // The global variables are initialized to zero by the // compiler. We can sometimes get here by a reconfigure. // If the queue was already initialized, q_depth will be // non-zero. In that case, leave everything alone. If the // queue was destroyed due to lack of plugins, q_depth, // as well as other queue variables, is set to zero so // they do not need reinitializing. if (q_depth == 0) { unsigned int i; q_depth = size; q = malloc(q_depth * sizeof(event_t *)); if (q == NULL) { processing_suspended = 1; return -1; } for (i=0; i < q_depth; i++) q[i] = NULL; /* Setup IPC mechanisms */ pthread_mutex_init(&queue_lock, NULL); sem_init(&queue_nonempty, 0, 0); #ifdef HAVE_ATOMIC atomic_init(&q_next, 0); atomic_init(&q_last, 0); #else q_next = 0; q_last = 0; #endif reset_suspended(); } if (flags & Q_IN_FILE) { int oflag = O_RDWR | O_APPEND; if (flags & Q_CREAT) oflag |= O_CREAT; if (flags & Q_EXCL) oflag |= O_EXCL; persist_fd = open(path, oflag, 0600); if (persist_fd < 0) return -1; persist_sync = (flags & Q_SYNC) ? 1 : 0; queue_load_file(persist_fd); } return 0; } int init_queue(unsigned int size) { return init_queue_extended(size, Q_IN_MEMORY, NULL); } static int do_overflow_action(struct disp_conf *config) { int rc = -1; overflowed = 1; switch (config->overflow_action) { case O_IGNORE: rc = 0; break; case O_SYSLOG: if (queue_full_warning < QUEUE_FULL_LIMIT) { syslog(LOG_ERR, "queue to plugins is full - dropping event"); queue_full_warning++; if (queue_full_warning == QUEUE_FULL_LIMIT) syslog(LOG_ERR, "auditd queue full reporting " "limit reached - ending " "dropped event notifications"); } break; case O_SUSPEND: syslog(LOG_ALERT, "Auditd is suspending event passing to plugins due to overflowing its queue."); processing_suspended = 1; break; case O_SINGLE: syslog(LOG_ALERT, "Auditd is now changing the system to single user mode due to overflowing its queue"); change_runlevel(SINGLE); break; case O_HALT: syslog(LOG_ALERT, "Auditd is now halting the system due to overflowing its queue"); change_runlevel(HALT); break; default: syslog(LOG_ALERT, "Unknown overflow action requested"); break; } return rc; } /* * returns 0 on success, * 1 if the event could not be queued due to overflow or * when processing is suspended, and * -1 on other errors */ int enqueue(event_t *e, struct disp_conf *config) { unsigned int n, retry_cnt = 0; if (processing_suspended) { free(e); return 1; } retry: /* We allow 3 retries and then its over */ if (retry_cnt > 3) { free(e); do_overflow_action(config); return 1; } #ifdef HAVE_ATOMIC /* * Load the producer index with relaxed ordering. sem_post() acts * as a release barrier and sem_wait() in dequeue() provides the * matching acquire barrier. Because the threads synchronize on * the semaphore, a relaxed load of q_next is sufficient here. */ n = atomic_load_explicit(&q_next, memory_order_relaxed) % q_depth; #else n = q_next % q_depth; #endif if (q[n] == NULL) { q[n] = e; #ifdef HAVE_ATOMIC /* * Store the updated producer index with release semantics. * The event was written to q[n] above and sem_post() will be * issued next. sem_post() itself is a release barrier and * sem_wait() in dequeue() will acquire it, so the combination * guarantees the consumer sees the event before noticing that * q_next advanced. */ atomic_store_explicit(&q_next, (n+1) % q_depth, memory_order_release); #else q_next = (n+1) % q_depth; #endif currently_used++; if (currently_used > max_used) max_used = currently_used; if (persist_fd >= 0) { if (write(persist_fd, e->data, e->hdr.size) < 0) { /* Log error but continue - persistence is not critical */ syslog(LOG_WARNING, "Failed to write event to persistent queue"); } if (persist_sync) fdatasync(persist_fd); } sem_post(&queue_nonempty); } else { struct timespec ts; ts.tv_sec = 0; ts.tv_nsec = 2 * 1000 * 1000; /* 2 milliseconds */ nanosleep(&ts, NULL); /* Let other thread try to log it. */ retry_cnt++; goto retry; } return 0; } /* Common dequeue logic after semaphore wait */ static event_t *dequeue_common(void) { event_t *e; unsigned int n; if (AUDIT_ATOMIC_LOAD(disp_hup)) return NULL; #ifdef HAVE_ATOMIC /* * The consumer waits on sem_wait() above which provides an acquire * barrier for the producer's sem_post(). Because of that * synchronization a relaxed load of the consumer index is safe here. */ n = atomic_load_explicit(&q_last, memory_order_relaxed) % q_depth; #else n = q_last % q_depth; #endif if (q[n] != NULL) { e = (event_t *)q[n]; q[n] = NULL; #ifdef HAVE_ATOMIC /* * Release ensures the slot is cleared before we advance the * consumer index. The following sem_post() pairs with the * producer's sem_wait(), so the semaphore again provides the * cross-thread ordering needed for the queue operations. */ atomic_store_explicit(&q_last, (n+1) % q_depth, memory_order_release); #else q_last = (n+1) % q_depth; #endif currently_used--; } else e = NULL; return e; } event_t *dequeue(void) { /* Wait until there is something in the queue */ while (sem_wait(&queue_nonempty) == -1 && errno == EINTR) ; return dequeue_common(); } event_t *dequeue_timed(const struct timespec *timeout) { int result; /* Wait until there is something in the queue */ while ((result = sem_timedwait(&queue_nonempty, timeout)) == -1 && errno == EINTR) ; if (result == -1) return NULL; return dequeue_common(); } void nudge_queue(void) { sem_post(&queue_nonempty); } void increase_queue_depth(unsigned int size) { pthread_mutex_lock(&queue_lock); if (size > q_depth) { unsigned int i; void *tmp_q; tmp_q = realloc(q, size * sizeof(event_t *)); if (tmp_q == NULL) { fprintf(stderr, "Out of Memory. Check %s file, %d line", __FILE__, __LINE__); pthread_mutex_unlock(&queue_lock); return; } q = tmp_q; for (i=q_depth; i<size; i++) q[i] = NULL; q_depth = size; overflowed = 0; } pthread_mutex_unlock(&queue_lock); } void write_queue_state(FILE *f) { fprintf(f, "current plugin queue depth = %u\n", currently_used); fprintf(f, "max plugin queue depth used = %u\n", max_used); fprintf(f, "plugin queue size = %u\n", q_depth); fprintf(f, "plugin queue overflow detected = %s\n", overflowed ? "yes" : "no"); fprintf(f, "plugin queueing suspended = %s\n", processing_suspended ? "yes" : "no"); } void resume_queue(void) { processing_suspended = 0; } void destroy_queue(void) { unsigned int i; for (i=0; i<q_depth; i++) free((void *)q[i]); free(q); pthread_mutex_destroy(&queue_lock); sem_destroy(&queue_nonempty); if (persist_fd >= 0) { if (currently_used == 0) { if (ftruncate(persist_fd, 0) < 0) { /* Log error but continue - cleanup is not critical */ syslog(LOG_WARNING, "Failed to truncate persistent queue file"); } } close(persist_fd); persist_fd = -1; } #ifdef HAVE_ATOMIC /* * Queue teardown is single threaded and no longer interacts with the * semaphore. A relaxed store is therefore sufficient when resetting * the indices. */ atomic_store_explicit(&q_next, 0, memory_order_relaxed); atomic_store_explicit(&q_last, 0, memory_order_relaxed); #else q_next = 0; q_last = 0; #endif q_depth = 0; processing_suspended = 1; currently_used = 0; max_used = 0; overflowed = 0; } unsigned int queue_current_depth(void) { return currently_used; } unsigned int queue_max_depth(void) { return max_used; } int queue_overflowed_p(void) { return overflowed; }