/
githubmirror
/
audit-userspace
Обзор
Документация
Войти
/
githubmirror
/
audit-userspace
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
src/ausearch.c
680 строк
18 KB
Steve Grubb
tools: safely replace directory log overrides
12 июл 2026, 19:52
12 июл 2026, 19:52
90b7003
Код
Авторство
О чём код?
/* * ausearch.c - main file for ausearch utility * Copyright 2005-08,2010,2013,2014,2020-21 Red Hat * All Rights Reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; see the file COPYING. If not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor * Boston, MA 02110-1335, USA. * * Authors: * Steve Grubb <sgrubb@redhat.com> */ #include "config.h" #include <stdio.h> #include <stdio_ext.h> #include <string.h> #include <stdlib.h> #include <getopt.h> #include <unistd.h> #include <ctype.h> #include <time.h> #include <errno.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/param.h> #include <locale.h> #include <signal.h> #include "libaudit.h" #include "common.h" #include "auditd-config.h" #include "ausearch-options.h" #include "ausearch-lol.h" #include "ausearch-lookup.h" #include "auparse.h" #include "ausearch-checkpt.h" #include "ausearch-parse.h" static FILE *log_fd = NULL; static lol lo; static int found = 0; static int input_is_pipe = 0; static int timeout_interval = 3; /* timeout in seconds */ static int files_to_process = 0; /* number of log files yet to process when reading multiple */ static struct daemon_conf config; static int process_logs(void); static int process_log_fd(void); static int process_stdin(void); static int process_file(char *filename); static int get_next_event(llist **); extern const char *checkpt_filename; /* checkpoint file name */ extern int checkpt_timeonly; /* use timestamp from within checkpoint file */ static int have_chkpt_data = 0; /* have checkpt need to compare with */ extern char *user_file; extern int force_logs; static int userfile_is_dir = 0; extern int match(llist *l); extern void output_event(llist *l); extern void ausearch_free_interpretations(void); extern void output_auparse_finish(void); /* * User space configuration items */ extern time_t arg_eoe_timeout; static int is_pipe(int fd) { struct stat st; int pipe_mode=0; if (fstat(fd, &st) == 0) { if (S_ISFIFO(st.st_mode)) pipe_mode = 1; } return pipe_mode; } int main(int argc, char *argv[]) { struct rlimit limit; int rc; struct stat sb; /* Check params and build regexpr */ setlocale (LC_ALL, ""); if (check_params(argc, argv)) return 1; /* Raise the rlimits in case we're being started from a shell * with restrictions. Not a fatal error. */ limit.rlim_cur = RLIM_INFINITY; limit.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_FSIZE, &limit); setrlimit(RLIMIT_CPU, &limit); _set_aumessage_mode(MSG_STDERR, DBG_NO); set_allow_links(1); // auditd might have -l flag, ausearch should be lenient here (void) umask( umask( 077 ) | 027 ); /* Load config so we know where logs are and eoe_timeout */ if (load_config(&config, TEST_SEARCH)) { /* Config was not loaded successfully, * so we are using the default config values */ fprintf(stderr, "NOTE - using built-in end_of_event_timeout: %lu\n", config.end_of_event_timeout); /* Using built-in logs when --input was not given */ if (user_file == NULL) { fprintf(stderr, "NOTE - using built-in logs: %s\n", config.log_file); } } /* Set timeout from the config file */ lol_set_eoe_timeout((time_t)config.end_of_event_timeout); /* * If an override was specified on the command line, override the config */ if (arg_eoe_timeout != 0) lol_set_eoe_timeout((time_t)arg_eoe_timeout); /* Load the checkpoint file if requested */ if (checkpt_filename) { rc = load_ChkPt(checkpt_filename); /* * If < -1, then some load/parse error * If == -1 then no file present (OK) * If == 0, then checkpoint has data */ if (rc < -1) { (void)free((void *)checkpt_filename); free_ChkPtMemory(); return 10; /* bad checkpoint status file */ } else if (rc == -1) { /* * No file, so no checking required. This just means * we have never checkpointed before and this is the * first time. */ have_chkpt_data = 0; } else { /* We will need to check */ have_chkpt_data++; } } lol_create(&lo); if (user_file) { if (stat(user_file, &sb) == -1) { perror("stat"); return 1; } switch (sb.st_mode & S_IFMT) { case S_IFDIR: userfile_is_dir = 1; rc = process_logs(); break; case S_IFREG: default: rc = process_file(user_file); if (checkpt_filename) /* we deal with failures via * checkpt_failure later */ (void)set_ChkPtFileDetails(user_file); free_config(&config); break; } } else if (force_logs) rc = process_logs(); else if (is_pipe(0)) { rc = process_stdin(); if (checkpt_filename) fprintf(stderr, "Warning - checkpointing stdin is not supported"); goto skip_checkpt; // Don't overwrite chkpt when reading a pipe } else rc = process_logs(); /* Generate a checkpoint if required */ if (checkpt_filename) { /* Providing haven't failed and have successfully read data * records, save a checkpoint */ if (!checkpt_failure && (rc == 0)) save_ChkPt(checkpt_filename); free_ChkPtMemory(); free((void *)checkpt_filename); /* * A checkpoint failure at this point means either * - we failed in attempting to create the checkpoint file * and so we will return 11 * - we had a corrupted checkpoint file and so we will return 12 */ if (checkpt_failure) { rc = ((checkpt_failure & CP_CORRUPTED) == CP_CORRUPTED) ? 12 : 11; } } skip_checkpt: lol_clear(&lo); lookup_uid_destroy_list(); ilist_clear(event_type); free(event_type); free(user_file); free((char *)event_key); free((char *)event_tuid); free((char *)event_teuid); free((char *)event_tauid); output_auparse_finish(); if (rc) return rc; if (!found) { if (report_format != RPT_RAW) fprintf(stderr, "<no matches>\n"); return 1; } return 0; } static int process_logs(void) { char *filename; int len; int ret; if (user_file && userfile_is_dir) { char dirname[MAXPATHLEN+1]; char *new_log_file; strncpy(dirname, user_file, MAXPATHLEN-32); dirname[MAXPATHLEN-32] = '\0'; if (dirname[strlen(dirname)-1] != '/') strcat(dirname, "/"); strcat (dirname, "audit.log"); /* Keep loaded configuration allocations until the new path exists. */ new_log_file = strdup(dirname); if (new_log_file == NULL) { fprintf(stderr, "No memory\n"); free_config(&config); return 1; } /* Only the log path is overridden for a directory input. */ free((void *)config.log_file); config.log_file = new_log_file; fprintf(stderr, "NOTE - using logs in %s\n", config.log_file); } len = strlen(config.log_file) + 16; filename = malloc(len); if (!filename) { fprintf(stderr, "No memory\n"); free_config(&config); return 1; } /* * If we have prior checkpoint data, we ignore files till we * find the file we last checkpointed from */ if (checkpt_filename && have_chkpt_data) { int num = 0; int found_chkpt_file = -1; snprintf(filename, len, "%s", config.log_file); do { if (access(filename, R_OK) != 0) break; struct stat sbuf; if (stat(filename, &sbuf)) { fprintf(stderr, "Error stat'ing %s (%s)\n", filename, strerror(errno)); free(filename); free_config(&config); return 1; } /* * Have we accessed the checkpointed file? * If so, stop checking further files. */ if ((sbuf.st_dev == chkpt_input_dev) && (sbuf.st_ino == chkpt_input_ino)) { /* * If we are ignoring all but time, then we * don't stop checking more files and just * let this loop go to completion and hence * we will find the 'oldest' file. */ if (!checkpt_timeonly) { found_chkpt_file = num++; break; } } num++; snprintf(filename, len, "%s.%d", config.log_file, num); } while (1); /* If a checkpoint is loaded but can't find it's file, and we * are not only just checking the timestamp from the checkpoint * file, we need to error */ if (found_chkpt_file == -1 && !checkpt_timeonly) { free(filename); free_config(&config); return 10; } num--; files_to_process = num; } else { /* No checkpointing - do it the usual way */ struct audit_log_info *logs = NULL; size_t log_cnt = 0; /* Count logs */ if (audit_log_list(config.log_file, &logs, &log_cnt)) { fprintf(stderr, "No memory\n"); free(filename); free_config(&config); return 1; } if (log_cnt == 0) { snprintf(filename, len, "%s", config.log_file); ret = process_file(filename); free(filename); free_config(&config); audit_log_free(logs, log_cnt); return ret; } /* Locate the starting file that's in range */ files_to_process = audit_log_find_start(logs, log_cnt, start_time); audit_log_free(logs, log_cnt); } /* Got it, now process logs from last to first */ if (files_to_process > 0) snprintf(filename, len, "%s.%d", config.log_file, files_to_process); else snprintf(filename, len, "%s", config.log_file); do { if ((ret = process_file(filename))) { free(filename); free_config(&config); return ret; } if (just_one && found) break; if (files_to_process == 0) break; files_to_process--; /* one less file to process */ /* Get next log file */ if (files_to_process > 0) snprintf(filename, len, "%s.%d", config.log_file, files_to_process); else snprintf(filename, len, "%s", config.log_file); } while (1); /* * If performing a checkpoint, set the checkpointed * file details - ie remember the last file processed */ if (checkpt_filename) ret = set_ChkPtFileDetails(filename); free(filename); free_config(&config); return 0; } /* * Decide if we should start outputting events given we loaded a checkpoint. * * The previous checkpoint will have recorded the last event outputted, * if there was one. If nothing is to be output, either the audit.log file * is empty, all the events in it were incomplete, or ??? * * We can return * 0 no output * 1 can output * 2 can output but not this event * 3 we have found an event whose time is > MAX_EVENT_DELTA_SECS secs * past our checkpoint time, which means this particular event is * complete. This should not happen, for we should have found our * checkpoint event before ANY other completed event. * */ static int chkpt_output_decision(event * e) { static int can_output = 0; /* Short cut. Once we made the decision, it's made for good */ if (can_output) return 1; /* If there was no checkpoint file, we turn on output */ if (have_chkpt_data == 0) { can_output = 1; return 1; /* can output on this event */ } /* * If the previous checkpoint had no recorded output, then * we assume everything was partial so we turn on output */ if (chkpt_input_levent.sec == 0) { can_output = 1; return 1; /* can output on this event */ } /* * If we are ignoring all but event time from within the checkpoint * file, then we output if the current event's time is greater than * or equal to the checkpoint time. */ if (checkpt_timeonly) { if ( (chkpt_input_levent.sec < e->sec) || ( (chkpt_input_levent.sec == e->sec) && (chkpt_input_levent.milli <= e->milli) ) ) { can_output = 1; return 1; /* can output on this event */ } } if (chkpt_input_levent.sec == e->sec && chkpt_input_levent.milli == e->milli && chkpt_input_levent.serial == e->serial && chkpt_input_levent.type == e->type ) { /* So far a match, so now check the nodes */ if (chkpt_input_levent.node == NULL && e->node == NULL) { can_output = 1; return 2; /* output after this event */ } if (chkpt_input_levent.node && e->node && (strcmp(chkpt_input_levent.node, e->node) == 0) ) { can_output = 1; return 2; /* output after this event */ } /* * The nodes are different. Drop through to further checks. */ } /* * If the event we are looking at is more than MAX_EVENT_DELTA_SECS * seconds past our checkpoint event, then by definition we should * have had a complete event (ie a complete event is one where at * least MAX_EVENT_DELTA_SECS seconds have passed since it's last * output record). * This means there is a problem, for the recorded checkpoint event was * the last complete event in the file when we last processed it. * Normally we see this if the checkpoint is very old and the system * has used the same inode again in an audit log file. */ if ( (chkpt_input_levent.sec < e->sec) && ((e->sec - chkpt_input_levent.sec) > MAX_EVENT_DELTA_SECS) ) { /* fprintf(stderr, "%s %lld.%03u:%lu vs %s %lld.%03u:%lu\n", chkpt_input_levent.host ? chkpt_input_levent.host : "-", (long long int)chkpt_input_levent.sec, chkpt_input_levent.milli, chkpt_input_levent.serial, e->host, (long long int)e->sec, e->milli, e->serial); */ return 3; } return 0; } static int process_log_fd(void) { llist *entries; // list of records in a complete event int ret; int do_output = 1; /* For each record in file */ do { ret = get_next_event(&entries); if ((ret != 0)||(entries->cnt == 0)) break; /* * If we are checkpointing, decide if we output this event. * We need to do it as early as here. The chkpt_input_levent event * might not match the entries, so we need to ensure that we don't * skip the event that is the checkpoint event. That is the marking point * from which we start outputting events. Leaving that event out will produce * empty results. */ if (checkpt_filename) do_output = chkpt_output_decision(&entries->e); /* * We flush all events on the last log file being processed. * Thus incomplete events are 'carried forward' to be * completed from the rest of it's records we expect to find * in the next file we are about to process. */ if (match(entries)) { if (do_output == 1) { found = 1; output_event(entries); } else if (do_output == 3) { fprintf(stderr, "Corrupted checkpoint file. Inode match, but newer complete event (%lld.%03u:%lu) found before loaded checkpoint %lld.%03u:%lu\n", (long long int)entries->e.sec, entries->e.milli, entries->e.serial, (long long int)chkpt_input_levent.sec, chkpt_input_levent.milli, chkpt_input_levent.serial); checkpt_failure |= CP_CORRUPTED; list_clear(entries); free(entries); fclose(log_fd); return 10; } if (just_one) { list_clear(entries); free(entries); break; } if (line_buffered) fflush(stdout); } /* Remember this event if checkpointing, irrespective of if we displayed it or not (do_output == 1) */ if (checkpt_filename) { if (set_ChkPtLastEvent(&entries->e)) { list_clear(entries); free(entries); fclose(log_fd); return 4; /* no memory */ } } ausearch_free_interpretations(); list_clear(entries); free(entries); } while (ret == 0); fclose(log_fd); return 0; } static void alarm_handler(int signal) { /* will interrupt current syscall */ } static int process_stdin(void) { struct sigaction sa; log_fd = stdin; input_is_pipe = 1; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sa.sa_handler = alarm_handler; if (sigaction(SIGALRM, &sa, NULL) < 0) return -1; return process_log_fd(); } static int process_file(char *filename) { log_fd = fopen(filename, "rm"); if (log_fd == NULL) { fprintf(stderr, "Error opening %s (%s)\n", filename, strerror(errno)); return 1; } __fsetlocking(log_fd, FSETLOCKING_BYCALLER); return process_log_fd(); } /* * This function returns a linked list of all the records in the next audit * event. It returns 0 on success, 1 on eof, -1 on error. */ static int get_next_event(llist **l) { char *rc; char *buff = NULL; int rcount = 0, timer_running = 0; /* * If we have any events ready to print ie have all records that * make up the event, we just return. If not, we read more lines * from the files until we get a complete event or finish reading * input */ *l = get_ready_event(&lo); if (*l) return 0; while (1) { rcount++; if (!buff) { buff = malloc(MAX_AUDIT_MESSAGE_LENGTH); if (!buff) return -1; } if (input_is_pipe && rcount > 1) { timer_running = 1; alarm(timeout_interval); } rc = fgets_unlocked(buff, MAX_AUDIT_MESSAGE_LENGTH, log_fd); if (timer_running) { /* timer may have fired but that's ok */ timer_running = 0; alarm(0); } if (rc) { if (lol_add_record(&lo, buff)) { *l = get_ready_event(&lo); if (*l) break; } } else { free(buff); /* * If we get an EINTR error or we are at EOF, we check * to see if we have any events to print and return * appropriately. If we are the last file being * processed, and we are not checkpointing, we mark all incomplete * events as complete so they will be printed. If we are checkpointing * we do an exhaustive validation to see if there are complete events still */ if ((ferror_unlocked(log_fd) && errno == EINTR) || feof_unlocked(log_fd)) { /* * Only attempt to mark all events as L_COMPLETE if we are * the last file being processed. */ if (files_to_process == 0) { if (!checkpt_filename) terminate_all_events(&lo); // terminate as we are not checkpointing else complete_all_events(&lo); // exhaustively check if we can complete events } *l = get_ready_event(&lo); if (*l) return 0; else return 1; } else return -1; /* all other errors are terminal */ } } free(buff); return 0; }