/** * @file * Implementation for the audit log parser. * * @author Meggan Whalen mwhalen@tresys.com * @author Jeremy A. Mowery jmowery@tresys.com * @author Jason Tang jtang@tresys.com * @author Jeremy Solt jsolt@tresys.com * * Copyright (C) 2003-2007 Tresys Technology, LLC * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "seaudit_internal.h" #include #include #include #include #include #include #include #include #include #include #define ALT_SYSCALL_STRING "msg=audit(" /* should contain SYSCALL_STRING */ #define AUDITD_MSG "type=" #define AVCMSG " avc: " #define BOOLMSG "committed booleans" #define LOADMSG " security: " #define NUM_TIME_COMPONENTS 3 #define OLD_LOAD_POLICY_STRING "loadingpolicyconfigurationfrom" #define PARSE_NUM_SYSCALL_FIELDS 3 #define SYSCALL_STRING "audit(" /** * Given a line from an audit log, create and return a vector of * tokens from that line. The caller is responsible for calling * apol_vector_destroy() upon that vector. Note that this function * will modify the passed in line. */ static int get_tokens(seaudit_log_t * log, char *line, apol_vector_t ** tokens) { char *line_ptr, *next; *tokens = NULL; int error = 0; if ((*tokens = apol_vector_create(NULL)) == NULL) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } line_ptr = line; /* Tokenize line while ignoring any adjacent whitespace chars. */ while ((next = strsep(&line_ptr, " ")) != NULL) { if (strcmp(next, "") && !apol_str_is_only_white_space(next)) { if (apol_vector_append(*tokens, next) < 0) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } } } cleanup: if (error != 0) { apol_vector_destroy(tokens); errno = error; return -1; } return 0; } /** * Given a line, determine what type of audit message it is. */ static seaudit_message_type_e is_selinux(const char *line) { if (strstr(line, BOOLMSG) && (strstr(line, "kernel") || strstr(line, AUDITD_MSG))) return SEAUDIT_MESSAGE_TYPE_BOOL; else if (strstr(line, LOADMSG) && (strstr(line, "kernel") || strstr(line, AUDITD_MSG))) return SEAUDIT_MESSAGE_TYPE_LOAD; else if (strstr(line, AVCMSG) && (strstr(line, "kernel") || strstr(line, AUDITD_MSG))) return SEAUDIT_MESSAGE_TYPE_AVC; else return SEAUDIT_MESSAGE_TYPE_INVALID; } extern int daylight; /** * Fill in the date_stamp field of a message. If the stamp was not * already allocated space then do it here. * * @return 0 on success, > 0 on warning, < 0 on error. */ static int insert_time(const seaudit_log_t * log, const apol_vector_t * tokens, size_t * position, seaudit_message_t * msg) { char *t = NULL; size_t i, length = 0; int error; if (*position + NUM_TIME_COMPONENTS >= apol_vector_get_size(tokens)) { WARN(log, "%s", "Not enough tokens for time."); return 1; } for (i = 0; i < NUM_TIME_COMPONENTS; i++) { length += strlen((char *)apol_vector_get_element(tokens, i + *position)); } /* Increase size for terminating string char and whitespace within. */ length += NUM_TIME_COMPONENTS; if ((t = (char *)calloc(1, length)) == NULL) { error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } for (i = 0; i < NUM_TIME_COMPONENTS; i++) { if (i > 0) { strcat(t, " "); } strcat(t, (char *)apol_vector_get_element(tokens, *position)); (*position)++; } if (!msg->date_stamp) { if ((msg->date_stamp = (struct tm *)calloc(1, sizeof(struct tm))) == NULL) { error = errno; ERR(log, "%s", strerror(error)); free(t); errno = error; return -1; } } if (strptime(t, "%b %d %T", msg->date_stamp) != NULL) { /* set year to 1900 since we know no valid logs were * generated. this will tell us that the msg does not * really have a year */ msg->date_stamp->tm_isdst = 0; msg->date_stamp->tm_year = 0; } free(t); return 0; } /** * Fill in the host field of a message. * * @return 0 on success, > 0 on warning, < 0 on error. */ static int insert_hostname(const seaudit_log_t * log, const apol_vector_t * tokens, size_t * position, seaudit_message_t * msg) { char *s, *host; if (*position >= apol_vector_get_size(tokens)) { WARN(log, "%s", "Not enough tokens for hostname."); return 1; } s = apol_vector_get_element(tokens, *position); /* Make sure this is not the kernel string identifier, which * may indicate that the hostname is empty. */ if (strstr(s, "kernel")) { msg->host = NULL; return 1; } (*position)++; if ((host = strdup(s)) == NULL || apol_bst_insert_and_get(log->hosts, (void **)&host, NULL) < 0) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } msg->host = host; return 0; } static int insert_standard_msg_header(const seaudit_log_t * log, const apol_vector_t * tokens, size_t * position, seaudit_message_t * msg) { int ret = 0; if ((ret = insert_time(log, tokens, position, msg)) != 0) { return ret; } if ((ret = insert_hostname(log, tokens, position, msg)) != 0) { return ret; } return ret; } /** * Parse the object manager that generated this audit message. */ static int insert_manager(const seaudit_log_t * log, seaudit_message_t * msg, const char *manager) { char *m; if ((m = strdup(manager)) == NULL || apol_bst_insert_and_get(log->managers, (void **)&m, NULL) < 0) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } msg->manager = m; return 0; } /** * Parse a context (user:role:type). For each of the pieces, add them * to the log's BSTs. Set reference pointers to those strings. */ static int parse_context(seaudit_log_t * log, char *token, char **user, char **role, char **type, char **mls_lvl, char **mls_clr) { char *s, *range; int error, ret = 0; context_t con = context_new(token); *user = *role = *type = *mls_lvl = *mls_clr = NULL; if (con == NULL) { WARN(log, "%s", "Error parsing context."); ret = 1; goto out; } if ((s = strdup(context_user_get(con))) == NULL || apol_bst_insert_and_get(log->users, (void **)&s, NULL) < 0) { error = errno; ERR(log, "%s", strerror(error)); errno = error; ret = -1; goto out; } *user = s; if ((s = strdup(context_role_get(con))) == NULL || apol_bst_insert_and_get(log->roles, (void **)&s, NULL) < 0) { error = errno; ERR(log, "%s", strerror(error)); errno = error; ret = -1; goto out; } *role = s; if ((s = strdup(context_type_get(con))) == NULL || apol_bst_insert_and_get(log->types, (void **)&s, NULL) < 0) { error = errno; ERR(log, "%s", strerror(error)); errno = error; ret = -1; goto out; } *type = s; if (range = context_range_get(con)) { char *lvl, *clr; lvl = strsep(&range, "-"); clr = strsep(&range, "-"); if (clr == NULL) /* level and clearance are the same */ clr = lvl; if ((s = strdup(lvl)) == NULL || apol_bst_insert_and_get(log->mls_lvl, (void **)&s, NULL) < 0) { error = errno; ERR(log, "%s", strerror(error)); errno = error; ret = -1; goto out; } *mls_lvl = s; if ((s = strdup(clr)) == NULL || apol_bst_insert_and_get(log->mls_clr, (void **)&s, NULL) < 0) { error = errno; ERR(log, "%s", strerror(error)); errno = error; ret = -1; goto out; } *mls_clr = s; } out: context_free(con); return ret; } /******************** AVC message parsing ********************/ /** * Given a token, determine if it is the new AVC header or not. */ static int avc_msg_is_token_new_audit_header(const char *token) { return (strstr(token, SYSCALL_STRING) ? 1 : 0); } /** * If the given token begins with prefix, then set reference pointer * result to everything following prefix and return 1. Otherwise * return 0. */ static int avc_msg_is_prefix(char *token, char *prefix, char **result) { size_t i = 0, length; length = strlen(prefix); if (strlen(token) < length) return 0; for (i = 0; i < length; i++) { if (token[i] != prefix[i]) { return 0; } } *result = token + length; return 1; } /** * Beginning with element *position, fill in the given avc message * with all permissions found. Afterwards update *position to point * to the next unprocessed token. Permissions should start and end * with braces and if not, then this is invalid. * * @return 0 on success, > 0 on warning, < 0 on error. */ static int avc_msg_insert_perms(const seaudit_log_t * log, apol_vector_t * tokens, size_t * position, seaudit_avc_message_t * avc) { char *s, *perm; int error; if ((s = apol_vector_get_element(tokens, *position)) == NULL || strcmp(s, "{") != 0) { WARN(log, "%s", "Expected an opening brace while parsing permissions."); return 1; } (*position)++; while (*position < apol_vector_get_size(tokens)) { s = apol_vector_get_element(tokens, *position); assert(s != NULL); (*position)++; if (strcmp(s, "}") == 0) { return 0; } if ((perm = strdup(s)) == NULL || apol_bst_insert_and_get(log->perms, (void **)&perm, NULL) < 0 || apol_vector_append(avc->perms, perm) < 0) { error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } } /* if got here, then message is too short */ WARN(log, "%s", "Expected a closing brace while parsing permissions."); return 1; } static int avc_msg_insert_syscall_info(const seaudit_log_t * log, char *token, seaudit_message_t * msg, seaudit_avc_message_t * avc) { size_t length, header_len = 0, i = 0; char *fields[PARSE_NUM_SYSCALL_FIELDS]; char *time_str = NULL; time_t temp; length = strlen(token); /* Chop off the ':' at the end of the syscall info token */ if (token[length - 1] == ':') { token[length - 1] = '\0'; length--; } /* Chop off the ')' at the end of the syscall info token */ if (token[length - 1] == ')') { token[length - 1] = '\0'; length--; } header_len = strlen(SYSCALL_STRING); /* Check to see if variations on syscall header exist */ if (strstr(token, ALT_SYSCALL_STRING)) { header_len = strlen(ALT_SYSCALL_STRING); } time_str = token + header_len; /* Parse seconds.nanoseconds:serial */ while (i < PARSE_NUM_SYSCALL_FIELDS && (fields[i] = strsep(&time_str, ".:")) != NULL) { i++; } if (i != PARSE_NUM_SYSCALL_FIELDS) { WARN(log, "%s", "Not enough fields for syscall info."); return 1; } temp = (time_t) atol(fields[0]); avc->tm_stmp_sec = temp; avc->tm_stmp_nano = atoi(fields[1]); avc->serial = atoi(fields[2]); if (msg->date_stamp == NULL) { if ((msg->date_stamp = (struct tm *)malloc(sizeof(struct tm))) == NULL) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } } localtime_r(&temp, msg->date_stamp); return 0; } static int avc_msg_insert_access_type(const seaudit_log_t * log, const char *token, seaudit_avc_message_t * avc) { if (strcmp(token, "granted") == 0) { avc->msg = SEAUDIT_AVC_GRANTED; return 0; } else if (strcmp(token, "denied") == 0) { avc->msg = SEAUDIT_AVC_DENIED; return 0; } WARN(log, "%s", "No AVC message type found, assuming it was a denial."); avc->msg = SEAUDIT_AVC_DENIED; return 1; } static int avc_msg_insert_scon(seaudit_log_t * log, seaudit_avc_message_t * avc, char *tmp) { char *user, *role, *type, *mls_lvl, *mls_clr; int retval; if (tmp == NULL) { WARN(log, "%s", "Invalid source context."); return 1; } retval = parse_context(log, tmp, &user, &role, &type, &mls_lvl, &mls_clr); if (retval != 0) { return retval; } avc->suser = user; avc->srole = role; avc->stype = type; avc->smls_lvl = mls_lvl; avc->smls_clr = mls_clr; return 0; } static int avc_msg_insert_tcon(seaudit_log_t * log, seaudit_avc_message_t * avc, char *tmp) { char *user, *role, *type, *mls_lvl, *mls_clr; int retval; if (tmp == NULL) { WARN(log, "%s", "Invalid target context."); return 1; } retval = parse_context(log, tmp, &user, &role, &type, &mls_lvl, &mls_clr); if (retval != 0) { return retval; } avc->tuser = user; avc->trole = role; avc->ttype = type; avc->tmls_lvl = mls_lvl; avc->tmls_clr = mls_clr; return 0; } static int avc_msg_insert_tclass(seaudit_log_t * log, seaudit_avc_message_t * avc, const char *tmp) { char *tclass; if ((tclass = strdup(tmp)) == NULL || apol_bst_insert_and_get(log->classes, (void **)&tclass, NULL) < 0) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } avc->tclass = tclass; return 0; } static int avc_msg_insert_string(const seaudit_log_t * log, char *src, char **dest) { if ((*dest = strdup(src)) == NULL) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } return 0; } /** * Removes quotes from a string, this is currently to remove quotes * from the command argument. */ static int avc_msg_remove_quotes_insert_string(const seaudit_log_t * log, char *src, char **dest) { size_t i, j, l; l = strlen(src); /* see if there are any quotes to begin with if there aren't * just run insert string */ if (src[0] == '\"' && l > 0 && src[l - 1] == '\"') { if ((*dest = calloc(1, l + 1)) == NULL) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } for (i = 0, j = 0; i < l; i++) { if (src[i] != '\"') { (*dest)[j] = src[i]; j++; } } return 0; } else return avc_msg_insert_string(log, src, dest); } /** * If there is exactly one equal sign in orig_token then return 1. * Otherwise return 0. */ static int avc_msg_is_valid_additional_field(const char *orig_token) { char *first_eq = strchr(orig_token, '='); if (first_eq == NULL) { return 0; } if (strchr(first_eq + 1, '=') != NULL) { return 0; } return 1; } static int avc_msg_reformat_path(const seaudit_log_t * log, seaudit_avc_message_t * avc, const char *token) { int error; if (avc->path == NULL) { if ((avc->path = strdup(token)) == NULL) { error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } } else { size_t len = strlen(avc->path) + strlen(token) + 2; char *s = realloc(avc->path, len); if (s == NULL) { error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } avc->path = s; strcat(avc->path, " "); strcat(avc->path, token); } return 0; } /** * Parse the remaining tokens of an AVC message, filling as much * information as possible. * * @return 0 on success, > 0 if warnings, < 0 on error */ static int avc_msg_insert_additional_field_data(seaudit_log_t * log, apol_vector_t * tokens, seaudit_avc_message_t * avc, size_t * position) { char *token, *v; int retval, has_warnings = 0; avc->avc_type = SEAUDIT_AVC_DATA_FS; for (; (*position) < apol_vector_get_size(tokens); (*position)++) { token = apol_vector_get_element(tokens, (*position)); v = NULL; if (strcmp(token, "") == 0) { break; } if (!avc->is_pid && avc_msg_is_prefix(token, "pid=", &v)) { avc->pid = atoi(v); avc->is_pid = 1; continue; } if (!avc->exe && avc_msg_is_prefix(token, "exe=", &v)) { if (avc_msg_insert_string(log, v, &avc->exe) < 0) { return -1; } continue; } if (!avc->comm && avc_msg_is_prefix(token, "comm=", &v)) { if (avc_msg_remove_quotes_insert_string(log, v, &avc->comm) < 0) { return -1; } continue; } /* Gather all tokens located after the path=XXXX token * until we encounter a valid additional field. This * is because a path name file name may be seperated * by whitespace. Look ahead at the next token, but we * make sure not to access memory beyond the total * number of tokens. */ if (!avc->path && avc_msg_is_prefix(token, "path=", &v)) { if (avc_msg_reformat_path(log, avc, v) < 0) { return -1; } while (*position + 1 < apol_vector_get_size(tokens)) { token = apol_vector_get_element(tokens, *position + 1); if (avc_msg_is_valid_additional_field(token)) { break; } (*position)++; if (avc_msg_reformat_path(log, avc, token) < 0) { return -1; } } continue; } if (!avc->name && avc_msg_is_prefix(token, "name=", &v)) { if (avc_msg_remove_quotes_insert_string(log, v, &avc->name) < 0) { return -1; } continue; } if (!avc->dev && avc_msg_is_prefix(token, "dev=", &v)) { if (avc_msg_insert_string(log, v, &avc->dev) < 0) { return -1; } continue; } if (!avc->saddr && avc_msg_is_prefix(token, "saddr=", &v)) { if (avc_msg_insert_string(log, v, &avc->saddr) < 0) { return -1; } continue; } if (!avc->source && (avc_msg_is_prefix(token, "source=", &v) || avc_msg_is_prefix(token, "src=", &v))) { avc->source = atoi(v); continue; } if (!avc->daddr && avc_msg_is_prefix(token, "daddr=", &v)) { if (avc_msg_insert_string(log, v, &avc->daddr)) { return -1; } continue; } if (!avc->dest && avc_msg_is_prefix(token, "dest=", &v)) { avc->dest = atoi(v); continue; } if (!avc->netif && avc_msg_is_prefix(token, "netif=", &v)) { if (avc_msg_insert_string(log, v, &avc->netif)) { return -1; } avc->avc_type = SEAUDIT_AVC_DATA_NET; continue; } if (!avc->laddr && avc_msg_is_prefix(token, "laddr=", &v)) { if (avc_msg_insert_string(log, v, &avc->laddr)) { return -1; } continue; } if (!avc->lport && avc_msg_is_prefix(token, "lport=", &v)) { avc->lport = atoi(v); avc->avc_type = SEAUDIT_AVC_DATA_NET; continue; } if (!avc->faddr && avc_msg_is_prefix(token, "faddr=", &v)) { if (avc_msg_insert_string(log, v, &avc->faddr)) { return -1; } continue; } if (!avc->fport && avc_msg_is_prefix(token, "fport=", &v)) { avc->fport = atoi(v); continue; } if (!avc->port && avc_msg_is_prefix(token, "port=", &v)) { avc->port = atoi(v); avc->avc_type = SEAUDIT_AVC_DATA_NET; continue; } if (!avc->is_src_sid && avc_msg_is_prefix(token, "ssid=", &v)) { avc->src_sid = (unsigned int)strtoul(v, NULL, 10); avc->is_src_sid = 1; continue; } if (!avc->is_tgt_sid && avc_msg_is_prefix(token, "tsid=", &v)) { avc->tgt_sid = (unsigned int)strtoul(v, NULL, 10); avc->is_tgt_sid = 1; continue; } if (!avc->is_capability && avc_msg_is_prefix(token, "capability=", &v)) { avc->capability = atoi(v); avc->is_capability = 1; avc->avc_type = SEAUDIT_AVC_DATA_CAP; continue; } if (!avc->is_key && avc_msg_is_prefix(token, "key=", &v)) { avc->key = atoi(v); avc->is_key = 1; avc->avc_type = SEAUDIT_AVC_DATA_IPC; continue; } if (!avc->is_inode && avc_msg_is_prefix(token, "ino=", &v)) { avc->inode = strtoul(v, NULL, 10); avc->is_inode = 1; continue; } if (!avc->ipaddr && avc_msg_is_prefix(token, "ipaddr=", &v)) { if (avc_msg_insert_string(log, v, &avc->ipaddr)) { return -1; } continue; } if (!avc->suser && avc_msg_is_prefix(token, "scontext=", &v)) { retval = avc_msg_insert_scon(log, avc, v); if (retval < 0) { return retval; } else if (retval > 0) { has_warnings = 1; } continue; } if (!avc->tuser && avc_msg_is_prefix(token, "tcontext=", &v)) { retval = avc_msg_insert_tcon(log, avc, v); if (retval < 0) { return retval; } else if (retval > 0) { has_warnings = 1; } continue; } if (!avc->tclass && avc_msg_is_prefix(token, "tclass=", &v)) { if (avc_msg_insert_tclass(log, avc, v) < 0) { return -1; } continue; } /* found a field that this parser did not understand, * so flag the entire message as a warning */ has_warnings = 1; } /* can't have both a sid and a context */ if ((avc->is_src_sid && avc->suser) || (avc->is_tgt_sid && avc->tuser)) { has_warnings = 1; } if (!avc->tclass) { has_warnings = 1; } if (has_warnings) { avc->avc_type = SEAUDIT_AVC_DATA_MALFORMED; } return has_warnings; } static int avc_parse(seaudit_log_t * log, apol_vector_t * tokens) { seaudit_message_t *msg; seaudit_avc_message_t *avc; seaudit_message_type_e type; int ret, has_warnings = 0; size_t position = 0, num_tokens = apol_vector_get_size(tokens); char *token, *t; if ((msg = message_create(log, SEAUDIT_MESSAGE_TYPE_AVC)) == NULL) { return -1; } avc = seaudit_message_get_data(msg, &type); token = apol_vector_get_element(tokens, position); /* Check for new auditd log format */ if (strstr(token, AUDITD_MSG)) { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for audit header."); return 1; } log->logtype = SEAUDIT_LOG_TYPE_AUDITD; token = apol_vector_get_element(tokens, position); } /* Insert the audit header if it exists */ if (avc_msg_is_token_new_audit_header(token)) { ret = avc_msg_insert_syscall_info(log, token, msg, avc); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); } } else { ret = insert_standard_msg_header(log, tokens, &position, msg); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); /* for now, only let avc messages set their object * manager */ if ((t = strrchr(token, ':')) == NULL) { WARN(log, "%s", "Expected to find an object manager here."); has_warnings = 1; /* Hold the position */ } else { *t = '\0'; if ((ret = insert_manager(log, msg, token)) < 0) { return ret; } position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); } /* new style audit messages can show up in syslog * files starting with FC5. This means that both the * old kernel: header and the new audit header might * be present. So, here we check again for the audit * message. */ if (avc_msg_is_token_new_audit_header(token)) { ret = avc_msg_insert_syscall_info(log, token, msg, avc); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); } } } /* Make sure the following token is the string "avc:" */ if (strcmp(token, "avc:") != 0) { /* Hold the position */ has_warnings = 1; WARN(log, "%s", "Expected an avc: token here."); } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); } /* Insert denied or granted */ if (avc_msg_insert_access_type(log, token, avc)) { has_warnings = 1; } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); } /* Insert perm(s) */ ret = avc_msg_insert_perms(log, tokens, &position, avc); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } if (position >= num_tokens) { WARN(log, "%s", "Message appears to be truncated."); return 1; } token = apol_vector_get_element(tokens, position); if (strcmp(token, "for") != 0) { /* Hold the position */ has_warnings = 1; WARN(log, "%s", "Expected a 'for' token here."); } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for new audit header."); return 1; } token = apol_vector_get_element(tokens, position); } /* At this point we have a valid message, for we have gathered * all of the standard fields so insert anything else. If * nothing else is left, the message is still considered * valid. */ ret = avc_msg_insert_additional_field_data(log, tokens, avc, &position); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } return has_warnings; } /******************** boolean parsing ********************/ static int boolean_msg_insert_bool(seaudit_log_t * log, seaudit_bool_message_t * boolm, char *token) { size_t len = strlen(token); int value; /* Strip off ending comma */ if (token[len - 1] == ',') { token[len - 1] = '\0'; len--; } if (token[len - 2] != ':') { WARN(log, "%s", "Boolean change was not in correct format."); return 1; } if (token[len - 1] == '0') value = 0; else if (token[len - 1] == '1') value = 1; else { WARN(log, "%s", "Invalid new boolean value."); return 1; } token[len - 2] = '\0'; return bool_change_append(log, boolm, token, value); } static int bool_parse(seaudit_log_t * log, apol_vector_t * tokens) { seaudit_message_t *msg; seaudit_bool_message_t *boolm; seaudit_message_type_e type; int ret, has_warnings = 0, next_line = log->next_line; size_t position = 0, num_tokens = apol_vector_get_size(tokens); char *token; if (log->next_line) { /* still processing a boolean change message, so don't * create a new one */ size_t num_messages = apol_vector_get_size(log->messages); assert(num_messages > 0); msg = apol_vector_get_element(log->messages, num_messages - 1); log->next_line = 0; } else { if ((msg = message_create(log, SEAUDIT_MESSAGE_TYPE_BOOL)) == NULL) { return -1; } } boolm = seaudit_message_get_data(msg, &type); ret = insert_standard_msg_header(log, tokens, &position, msg); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for boolean change."); return 1; } token = apol_vector_get_element(tokens, position); /* Make sure the following token is the string "kernel:" */ if (!strstr(token, "kernel:")) { WARN(log, "%s", "Expected to see kernel here."); has_warnings = 1; /* Hold the position */ } else { if ((ret = insert_manager(log, msg, "kernel")) < 0) { return ret; } position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for boolean change."); return 1; } token = apol_vector_get_element(tokens, position); } if (!next_line) { if (!strstr(token, "security:")) { WARN(log, "%s", "Expected to see security here."); has_warnings = 1; /* Hold the position */ } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for boolean change."); return 1; } token = apol_vector_get_element(tokens, position); } if (!strstr(token, "committed")) { WARN(log, "%s", "Expected to see committed here."); has_warnings = 1; /* Hold the position */ } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for boolean change."); return 1; } token = apol_vector_get_element(tokens, position); } if (!strstr(token, "booleans")) { WARN(log, "%s", "Expected to see booleans here."); has_warnings = 1; /* Hold the position */ } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for boolean change."); return 1; } token = apol_vector_get_element(tokens, position); } if (!strstr(token, "{")) { WARN(log, "%s", "Expected to see '{' here."); has_warnings = 1; /* Hold the position */ } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for boolean change."); return 1; } token = apol_vector_get_element(tokens, position); } } /* keep parsing until a closing brace is found. if end of * tokens is reached, then keep parsing the next line */ while (position < num_tokens) { token = apol_vector_get_element(tokens, position); position++; if (!strcmp(token, "}")) { if (position < num_tokens) { WARN(log, "%s", "Excess tokens after closing brace"); has_warnings = 1; } return has_warnings; } ret = boolean_msg_insert_bool(log, boolm, token); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } } /* did not find a closing brace yet */ log->next_line = 1; return has_warnings; } /******************** policy load parsing ********************/ /** * Determine if a series of tokens represents the older style of a * policy load. * * @return 0 if not older style, 1 if it is the older style, < 0 on * error. If it is the older style, then increment reference pointer * position to point to the next unprocessed token. */ static int load_policy_msg_is_old_load_policy_string(const seaudit_log_t * log, const apol_vector_t * tokens, size_t * position) { size_t i, length = 0; int rt; char *tmp = NULL; if (*position + 4 >= apol_vector_get_size(tokens)) { return 0; } for (i = 0; i < 4; i++) { length += strlen((char *)apol_vector_get_element(tokens, i + *position)); } if ((tmp = (char *)calloc(length + 1, sizeof(char))) == NULL) { int error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } for (i = 0; i < 4; i++) { strcat(tmp, (char *)apol_vector_get_element(tokens, i + *position)); } rt = strcmp(tmp, OLD_LOAD_POLICY_STRING); free(tmp); if (rt == 0) { *position += 4; return 1; } else return 0; } static void load_policy_msg_get_policy_components(seaudit_load_message_t * load, const apol_vector_t * tokens, size_t * position) { char *arg = apol_vector_get_element(tokens, *position); char *endptr; unsigned int val = (unsigned int)strtoul(arg, &endptr, 10); if (*endptr != '\0') { /* found a key-value pair where the key is not a * number, so skip this */ (*position)++; return; } char *id = apol_vector_get_element(tokens, *position + 1); assert(id != NULL && arg != NULL); if (load->classes == 0 && strstr(id, "classes")) { load->classes = val; } else if (load->rules == 0 && strstr(id, "rules")) { load->rules = val; } else if (load->users == 0 && strstr(id, "users")) { load->users = val; } else if (load->roles == 0 && strstr(id, "roles")) { load->roles = val; } else if (load->types == 0 && strstr(id, "types")) { load->types = val; } else if (load->bools == 0 && strstr(id, "bools")) { load->bools = val; } *position += 2; } static int load_parse(seaudit_log_t * log, const apol_vector_t * tokens) { seaudit_message_t *msg; seaudit_load_message_t *load; seaudit_message_type_e type; int ret, error, has_warnings = 0; size_t position = 0, num_tokens = apol_vector_get_size(tokens); char *token; if (log->next_line) { /* still processing a load message, so don't create a * new one */ size_t num_messages = apol_vector_get_size(log->messages); assert(num_messages > 0); msg = apol_vector_get_element(log->messages, num_messages - 1); log->next_line = 0; } else { if ((msg = message_create(log, SEAUDIT_MESSAGE_TYPE_LOAD)) == NULL) { return -1; } } load = seaudit_message_get_data(msg, &type); ret = insert_standard_msg_header(log, tokens, &position, msg); if (ret < 0) { return ret; } else if (ret > 0) { has_warnings = 1; } if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for policy load."); return 1; } token = apol_vector_get_element(tokens, position); if (strcmp(token, "invalidating") == 0) { WARN(log, "%s", "Got an unexpected invalidating message."); return 1; } if (position + 1 >= num_tokens) { WARN(log, "%s", "Not enough tokens for policy load."); return 1; } if (strcmp((char *)apol_vector_get_element(tokens, position + 1), "bools") == 0) { WARN(log, "%s", "Got an unexpected bools message."); return 1; } /* Check the following token for the string "kernel:" */ if (!strstr(token, "kernel:")) { WARN(log, "%s", "Expected to see kernel here."); has_warnings = 1; /* Hold the position */ } else { if ((ret = insert_manager(log, msg, "kernel")) < 0) { return ret; } position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for policy load."); return 1; } token = apol_vector_get_element(tokens, position); } if (strcmp(token, "security:")) { WARN(log, "%s", "Expected to see security here."); has_warnings = 1; /* Hold the position */ } else { position++; if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for policy load."); return 1; } token = apol_vector_get_element(tokens, position); } ret = load_policy_msg_is_old_load_policy_string(log, tokens, &position); if (ret < 0) { return ret; } else if (ret > 0) { if (position >= num_tokens) { WARN(log, "%s", "Not enough tokens for policy load."); return 1; } token = apol_vector_get_element(tokens, position); if ((load->binary = strdup(token)) == NULL) { error = errno; ERR(log, "%s", strerror(error)); errno = error; return -1; } log->next_line = 1; } else { while (position < num_tokens) { load_policy_msg_get_policy_components(load, tokens, &position); } /* Check to see if we have gathered ALL policy * components. If not, we need to load the next * line. */ if (load->classes == 0 || load->rules == 0 || load->users == 0 || load->roles == 0 || load->types == 0) { log->next_line = 1; } } return has_warnings; } /** * Parse a single nul-terminated line from an selinux audit log. */ static int seaudit_log_parse_line(seaudit_log_t * log, char *line) { char *orig_line = NULL; seaudit_message_t *prev_message; seaudit_message_type_e is_sel, prev_message_type; apol_vector_t *tokens = NULL; int retval = -1, retval2, has_warnings = 0, error = 0; is_sel = is_selinux(line); if (log->next_line) { prev_message = apol_vector_get_element(log->messages, apol_vector_get_size(log->messages) - 1); seaudit_message_get_data(prev_message, &prev_message_type); if (!(is_sel == SEAUDIT_MESSAGE_TYPE_INVALID && prev_message_type == SEAUDIT_MESSAGE_TYPE_BOOL) && !(is_sel == SEAUDIT_MESSAGE_TYPE_LOAD && prev_message_type == SEAUDIT_MESSAGE_TYPE_LOAD)) { WARN(log, "%s", "Parser was in the middle of a line, but next message was not the correct format."); has_warnings = 1; log->next_line = 0; } else { is_sel = prev_message_type; } } if (is_sel == SEAUDIT_MESSAGE_TYPE_INVALID) { /* unknown line, so ignore it */ return 0; } if ((orig_line = strdup(line)) == NULL) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } if (get_tokens(log, line, &tokens) < 0) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } switch (is_sel) { case SEAUDIT_MESSAGE_TYPE_AVC: retval2 = avc_parse(log, tokens); break; case SEAUDIT_MESSAGE_TYPE_BOOL: retval2 = bool_parse(log, tokens); break; case SEAUDIT_MESSAGE_TYPE_LOAD: retval2 = load_parse(log, tokens); break; default: /* should never get here */ assert(0); errno = EINVAL; retval2 = -1; } if (retval2 < 0) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } else if (retval2 > 0) { if (apol_vector_append(log->malformed_msgs, orig_line) < 0) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } orig_line = NULL; has_warnings = 1; } retval = 0; cleanup: free(orig_line); apol_vector_destroy(&tokens); if (retval < 0) { errno = error; return -1; } return has_warnings; } /******************** public functions below ********************/ int seaudit_log_parse(seaudit_log_t * log, FILE * syslog) { FILE *audit_file = syslog; char *line = NULL; int retval = -1, retval2, has_warnings = 0, error = 0; size_t line_size = 0, i; if (log == NULL || syslog == NULL) { ERR(log, "%s", strerror(EINVAL)); error = EINVAL; goto cleanup; } if (!log->tz_initialized) { tzset(); log->tz_initialized = 1; } clearerr(audit_file); while (1) { if (getline(&line, &line_size, audit_file) < 0) { error = errno; if (!feof(audit_file)) { ERR(log, "%s", strerror(error)); goto cleanup; } break; } apol_str_trim(line); retval2 = seaudit_log_parse_line(log, line); if (retval2 < 0) { error = errno; goto cleanup; } else if (retval2 > 0) { has_warnings = 1; } } retval = 0; cleanup: free(line); for (i = 0; i < apol_vector_get_size(log->models); i++) { seaudit_model_t *m = apol_vector_get_element(log->models, i); model_notify_log_changed(m, log); } if (retval < 0) { errno = error; return -1; } if (has_warnings) { WARN(log, "%s", "Audit log was parsed, but there were one or more invalid message found within it."); } return has_warnings; } int seaudit_log_parse_buffer(seaudit_log_t * log, const char *buffer, const size_t bufsize) { const char *s; char *line = NULL, *l; int retval = -1, retval2, has_warnings = 0, error = 0; size_t offset = 0, line_size, i; if (log == NULL || buffer == NULL) { ERR(log, "%s", strerror(EINVAL)); error = EINVAL; goto cleanup; } if (!log->tz_initialized) { tzset(); log->tz_initialized = 1; } while (offset < bufsize) { /* create a new string up to the first newline or end of * buffer, whichever comes first */ for (s = buffer + offset; s < buffer + bufsize && *s != '\n'; s++) ; line_size = s - (buffer + offset); assert(line_size > 0); if ((l = realloc(line, line_size + 1)) == NULL) { error = errno; ERR(log, "%s", strerror(error)); goto cleanup; } line = l; memcpy(line, buffer + offset, line_size); line[line_size] = '\0'; offset += line_size; if (s < buffer + bufsize) { /* this branch can only be true if not at end of file */ assert(*s == '\n'); offset++; } apol_str_trim(line); retval2 = seaudit_log_parse_line(log, line); if (retval2 < 0) { error = errno; goto cleanup; } else if (retval2 > 0) { has_warnings = 1; } } retval = 0; cleanup: free(line); for (i = 0; i < apol_vector_get_size(log->models); i++) { seaudit_model_t *m = apol_vector_get_element(log->models, i); model_notify_log_changed(m, log); } if (retval < 0) { errno = error; return -1; } if (has_warnings) { WARN(log, "%s", "Audit log was parsed, but there were one or more invalid message found within it."); } return has_warnings; }