/* * auvirt.c - A tool to extract data related to virtualization. * Copyright (c) 2011 IBM Corp. * All Rights Reserved. * * This software may be freely redistributed and/or modified under the * terms of the GNU General Public License as published by the Free * Software Foundation; either version 2, 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., 675 Mass Ave, Cambridge, MA 02139, USA. * * Authors: * Marcelo Henrique Cerri */ #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include "auparse.h" #include "libaudit.h" #include "ausearch-time.h" #include "auvirt-list.h" /* Command line parameters */ static int help_flag = 0; static int stdin_flag = 0; static int summary_flag = 0; static int all_events_flag = 0; static int uuid_flag = 0; static int proof_flag = 0; static const char *vm = NULL; static const char *uuid = NULL; static const char *file = NULL; static int debug = 0; /* * The start time and end time given in the command line is stored respectively * in the variables start_time and end_time that are declared/defined in the * files ausearch-time.h and ausearch-time.c. These files are reused from the * ausearch tool source code: * * time_t start_time = 0; * time_t end_time = 0; */ /* List of events */ enum event_type { ET_NONE = 0, ET_START, ET_STOP, ET_MACHINE_ID, ET_AVC, ET_RES, ET_ANOM, ET_DOWN }; struct record_id { time_t time; unsigned int milli; unsigned long serial; }; struct event { enum event_type type; time_t start; time_t end; uid_t uid; char *uuid; char *name; int success; pid_t pid; /* Fields specific for resource events: */ char *reason; char *res_type; char *res; /* Fields specific for cgroup resources */ char *cgroup_class; char *cgroup_detail; char *cgroup_acl; /* Fields specific for machine id events: */ char *seclevel; /* Fields specific for avc events: */ char *avc_result; char *avc_operation; char *target; char *comm; char *context; /* Fields to print proof information: */ struct record_id proof[4]; }; list_t *events = NULL; /* Auxiliary functions to allocate and to free events. */ struct event *event_alloc(void) { struct event *event = malloc(sizeof(struct event)); if (event) { /* The new event is initialized with values that represents * unset values: -1 for uid and pid and 0 (or NULL) for numbers * and pointers. For example, event->end = 0 represents an * unfinished event. */ memset(event, 0, sizeof(struct event)); event->uid = -1; event->pid = -1; } return event; } void event_free(struct event *event) { if (event) { free(event->uuid); free(event->name); free(event->reason); free(event->res_type); free(event->res); free(event->avc_result); free(event->avc_operation); free(event->seclevel); free(event->target); free(event->comm); free(event->cgroup_class); free(event->cgroup_detail); free(event->cgroup_acl); free(event->context); free(event); } } inline char *copy_str(const char *str) { return (str) ? strdup(str) : NULL; } void usage(FILE *output) { fprintf(output, "usage: auvirt [--stdin] [--all-events] [--summary] " "[--start start-date [start-time]] " "[--end end-date [end-time]] [--file file-name] " "[--show-uuid] [--proof] " "[--uuid uuid] [--vm vm-name]\n"); } /* Parse and check command line arguments */ int parse_args(int argc, char **argv) { /* Based on http://www.ietf.org/rfc/rfc4122.txt */ const char *uuid_pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-" "[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$"; int i, rc = 0; regex_t uuid_regex; if (regcomp(&uuid_regex, uuid_pattern, REG_EXTENDED)) { fprintf(stderr, "Failed to initialize program.\n"); return 1; } for (i = 1; i < argc; i++) { const char *opt = argv[i]; if (opt[0] != '-') { fprintf(stderr, "Argument not expected: %s\n", opt); goto error; } else if (strcmp("--vm", opt) == 0 || strcmp("-v", opt) == 0) { if ((i + 1) >= argc || argv[i + 1][0] == '-') { fprintf(stderr, "\"%s\" option requires " "an argument.\n", opt); goto error; } vm = argv[++i]; } else if (strcmp("--uuid", opt) == 0 || strcmp("-u", opt) == 0) { if ((i + 1) >= argc || argv[i + 1][0] == '-') { fprintf(stderr, "\"%s\" option requires " "an argument.\n", opt); goto error; } if (regexec(&uuid_regex, argv[i + 1], 0, NULL, 0)) { fprintf(stderr, "Invalid uuid: %s\n", argv[i + 1]); goto error; } uuid = argv[++i]; } else if (strcmp("--all-events", opt) == 0 || strcmp("-a", opt) == 0) { all_events_flag = 1; } else if (strcmp("--summary", opt) == 0 || strcmp("-s", opt) == 0) { summary_flag = 1; } else if (strcmp("--file", opt) == 0 || strcmp("-f", opt) == 0) { if ((i + 1) >= argc || argv[i + 1][0] == '-') { fprintf(stderr, "\"%s\" option requires " "an argument.\n", opt); goto error; } file = argv[++i]; } else if (strcmp("--show-uuid", opt) == 0) { uuid_flag = 1; } else if (strcmp("--stdin", opt) == 0) { stdin_flag = 1; } else if (strcmp("--proof", opt) == 0) { proof_flag = 1; } else if (strcmp("--help", opt) == 0 || strcmp("-h", opt) == 0) { help_flag = 1; goto exit; } else if (strcmp("--start", opt) == 0 || strcmp("-ts", opt) == 0) { const char *date, *time = NULL; if ((i + 1) >= argc || argv[i + 1][0] == '-') { fprintf(stderr, "\"%s\" option requires at " "least one argument.\n", opt); goto error; } date = argv[++i]; if ((i + 1) < argc && argv[i + 1][0] != '-') time = argv[++i]; /* This will set start_time */ if(ausearch_time_start(date, time)) goto error; } else if (strcmp("--end", opt) == 0 || strcmp("-te", opt) == 0) { const char *date, *time = NULL; if ((i + 1) >= argc || argv[i + 1][0] == '-') { fprintf(stderr, "\"%s\" option requires at " "least one argument.\n", opt); goto error; } date = argv[++i]; if ((i + 1) < argc && argv[i + 1][0] != '-') time = argv[++i]; /* This will set end_time */ if (ausearch_time_end(date, time)) goto error; } else if (strcmp("--debug", opt) == 0) { debug = 1; } else { fprintf(stderr, "Unknown option \"%s\".\n", opt); goto error; } } /* Validate conflicting options */ if (stdin_flag && file) { fprintf(stderr, "\"--sdtin\" and \"--file\" options " "must not be specified together.\n"); goto error; } if (debug) { fprintf(stderr, "help_flag='%i'\n", help_flag); fprintf(stderr, "stdin_flag='%i'\n", stdin_flag); fprintf(stderr, "all_events_flag='%i'\n", all_events_flag); fprintf(stderr, "summary_flag='%i'\n", summary_flag); fprintf(stderr, "uuid='%s'\n", uuid ? uuid : "(null)"); fprintf(stderr, "vm='%s'\n", vm ? vm : "(null)"); fprintf(stderr, "file='%s'\n", file ? file : "(null)"); fprintf(stderr, "start_time='%-.16s'\n", (start_time == 0L) ? "" : ctime(&start_time)); fprintf(stderr, "end_time='%-.16s'\n", (end_time == 0L) ? "" : ctime(&end_time)); } exit: regfree(&uuid_regex); return rc; error: rc = 1; goto exit; } /* Initialize an auparse_state_t with the correct log source. */ auparse_state_t *init_auparse(void) { auparse_state_t *au = NULL; if (stdin_flag) { au = auparse_init(AUSOURCE_FILE_POINTER, stdin); } else if (file) { au = auparse_init(AUSOURCE_FILE, file); } else { if (getuid()) { fprintf(stderr, "You probably need to be root for " "this to work\n"); } au = auparse_init(AUSOURCE_LOGS, NULL); } if (au == NULL) { fprintf(stderr, "Error: %s\n", strerror(errno)); } return au; } /* Create a criteria to search for the virtualization related records */ int create_search_criteria(auparse_state_t *au) { char *error = NULL; char expr[1024]; snprintf(expr, sizeof(expr), "(\\record_type >= %d && \\record_type <= %d)", AUDIT_FIRST_VIRT_MSG, AUDIT_LAST_VIRT_MSG); if (ausearch_add_expression(au, expr, &error, AUSEARCH_RULE_CLEAR)) { fprintf(stderr, "Criteria error: %s\n", error); free(error); return 1; } if (uuid) { if (ausearch_add_item(au, "uuid", "=", uuid, AUSEARCH_RULE_AND)) { fprintf(stderr, "Criteria error: uuid\n"); return 1; } } if (vm) { if (ausearch_add_interpreted_item(au, "vm", "=", vm, AUSEARCH_RULE_AND)) { fprintf(stderr, "Criteria error: id\n"); return 1; } } if (all_events_flag || summary_flag) { if (ausearch_add_item(au, "type", "=", "AVC", AUSEARCH_RULE_OR)) { fprintf(stderr, "Criteria error: AVC\n"); return 1; } if (ausearch_add_item(au, "type", "=", "SYSTEM_SHUTDOWN", AUSEARCH_RULE_OR)) { fprintf(stderr, "Criteria error: shutdown\n"); return 1; } snprintf(expr, sizeof(expr), "(\\record_type >= %d && \\record_type <= %d) ||" "(\\record_type >= %d && \\record_type <= %d)", AUDIT_FIRST_ANOM_MSG, AUDIT_LAST_ANOM_MSG, AUDIT_FIRST_KERN_ANOM_MSG, AUDIT_LAST_KERN_ANOM_MSG); if (ausearch_add_expression(au, expr, &error, AUSEARCH_RULE_OR)) { fprintf(stderr, "Criteria error: %s\n", error); free(error); return 1; } } if (start_time) { if (ausearch_add_timestamp_item(au, ">=", start_time, 0, AUSEARCH_RULE_AND)) { fprintf(stderr, "Criteria error: start_time\n"); return 1; } } if (end_time) { if (ausearch_add_timestamp_item(au, "<=", end_time, 0, AUSEARCH_RULE_AND)) { fprintf(stderr, "Criteria error: end_time\n"); return 1; } } return 0; } /* Extract the most common fields from virtualization-related records. */ int extract_virt_fields(auparse_state_t *au, const char **p_uuid, uid_t *p_uid, time_t *p_time, const char **p_name, int *p_suc) { const char *field; auparse_first_record(au); /* Order matters */ if (p_uid) { if (!auparse_find_field(au, field = "uid")) goto error; *p_uid = auparse_get_field_int(au); } if (p_name) { if (!auparse_find_field(au, field = "vm")) goto error; *p_name = auparse_interpret_field(au); } if (p_uuid) { if (!auparse_find_field(au, field = "uuid")) goto error; *p_uuid = auparse_get_field_str(au); } if (p_suc) { const char *res = auparse_find_field(au, field = "res"); if (res == NULL) goto error; *p_suc = (strcmp("success", res) == 0) ? 1 : 0; } if (p_time) { *p_time = auparse_get_time(au); } return 0; error: if (debug) { fprintf(stderr, "Failed to get field \"%s\" for record " "%ld.%03u:%lu\n", field ? field : "", auparse_get_time(au), auparse_get_milli(au), auparse_get_serial(au)); } return 1; } /* Return label and categories from a security context. */ const char *get_seclevel(const char *seclabel) { /* * system_u:system_r:svirt_t:s0:c107,c434 * \____ _____/ * ' * level + cat */ int c = 0; for (;seclabel && *seclabel; seclabel++) { if (*seclabel == ':') c += 1; if (c == 3) return seclabel + 1; } return NULL; } int add_proof(struct event *event, auparse_state_t *au) { if (!proof_flag) return 0; size_t i, proof_len = sizeof(event->proof)/sizeof(event->proof[0]); for (i = 0; i < proof_len; i++) { if (event->proof[i].time == 0) break; } if (i == proof_len) { if (debug) fprintf(stderr, "Failed to add proof.\n"); return 1; } event->proof[i].time = auparse_get_time(au); event->proof[i].milli = auparse_get_milli(au); event->proof[i].serial = auparse_get_serial(au); return 0; } /* * machine_id records are used to get the selinux context associated to a * guest. */ int process_machine_id_event(auparse_state_t *au) { uid_t uid; time_t time; const char *seclevel, *uuid, *name; struct event *event; int success; seclevel = get_seclevel(auparse_find_field(au, "vm-ctx")); if (seclevel == NULL) { if (debug) fprintf(stderr, "Security context not found for " "MACHINE_ID event.\n"); } if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success)) return 0; event = event_alloc(); if (event == NULL) return 1; event->type = ET_MACHINE_ID; event->uuid = copy_str(uuid); event->name = copy_str(name); event->success = success; event->seclevel = copy_str(seclevel); event->uid = uid; event->start = time; add_proof(event, au); if (list_append(events, event) == NULL) { event_free(event); return 1; } return 0; } int add_start_guest_event(auparse_state_t *au) { struct event *start; uid_t uid; time_t time; const char *uuid, *name; int success; list_node_t *it; /* Just skip this record if it failed to get some of the fields */ if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success)) return 0; /* On failure, loop backwards to update all the resources associated to * the last session of this guest. When a machine_id or a stop event is * found the loop can be broken because a machine_id is created at the * beginning of a session and a stop event indicates a previous * session. */ if (!success) { for (it = events->tail; it; it = it->prev) { struct event *event = it->data; if (event->success && event->uuid && strcmp(uuid, event->uuid) == 0) { if (event->type == ET_STOP || event->type == ET_MACHINE_ID) { /* An old session found. */ break; } else if (event->type == ET_RES && event->end == 0) { event->end = time; add_proof(event, au); } } } } start = event_alloc(); if (start == NULL) return 1; start->type = ET_START; start->uuid = copy_str(uuid); start->name = copy_str(name); start->success = success; start->uid = uid; start->start = time; auparse_first_record(au); if (auparse_find_field(au, "vm-pid")) start->pid = auparse_get_field_int(au); add_proof(start, au); if (list_append(events, start) == NULL) { event_free(start); return 1; } return 0; } int add_stop_guest_event(auparse_state_t *au) { list_node_t *it; struct event *stop, *start = NULL, *event = NULL; uid_t uid; time_t time; const char *uuid, *name; int success; /* Just skip this record if it failed to get some of the fields */ if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success)) return 0; /* Loop backwards to find the last start event for the uuid and * update all resource records related to that guest session. */ for (it = events->tail; it; it = it->prev) { event = it->data; if (event->success && event->uuid && strcmp(uuid, event->uuid) == 0) { if (event->type == ET_START) { /* If an old session is found it's no longer * necessary to update the resource records. */ if (event->end || start) break; /* This is the start event related to the * current session. */ start = event; } else if (event->type == ET_STOP || event->type == ET_MACHINE_ID) { /* Old session found. */ break; } else if (event->type == ET_RES && event->end == 0) { /* Update the resource assignments. */ event->end = time; add_proof(event, au); } } } if (start == NULL) { if (debug) { fprintf(stderr, "Couldn't find the correlated start " "record to the stop event.\n"); } return 0; } /* Create a new stop event */ stop = event_alloc(); if (stop == NULL) return 1; stop->type = ET_STOP; stop->uuid = copy_str(uuid); stop->name = copy_str(name); stop->success = success; stop->uid = uid; stop->start = time; auparse_first_record(au); if (auparse_find_field(au, "vm-pid")) stop->pid = auparse_get_field_int(au); add_proof(stop, au); if (list_append(events, stop) == NULL) { event_free(stop); return 1; } /* Update the correlated start event. */ if (success) { start->end = time; add_proof(start, au); } return 0; } int process_control_event(auparse_state_t *au) { const char *op; op = auparse_find_field(au, "op"); if (op == NULL) { if (debug) fprintf(stderr, "Invalid op field.\n"); return 0; } if (strcmp("start", op) == 0) { if (add_start_guest_event(au)) return 1; } else if (strcmp("stop", op) == 0) { if (add_stop_guest_event(au)) return 1; } else if (debug) { fprintf(stderr, "Unknown op: %s\n", op); } return 0; } inline int is_resource(const char *res) { if (res == NULL || res[0] == '\0' || strcmp("0", res) == 0 || strcmp("?", res) == 0) return 0; return 1; } int add_resource(auparse_state_t *au, const char *uuid, uid_t uid, time_t time, const char *name, int success, const char *reason, const char *res_type, const char *res) { if (!is_resource(res)) return 0; struct event *event = event_alloc(); if (event == NULL) return 1; event->type = ET_RES; event->uuid = copy_str(uuid); event->name = copy_str(name); event->success = success; event->reason = copy_str(reason); event->res_type = copy_str(res_type); event->res = copy_str(res); event->uid = uid; event->start = time; add_proof(event, au); /* Get cgroup specific fields. */ if (strcmp("cgroup", res_type) == 0) { event->cgroup_class = copy_str(auparse_find_field(au, "class")); if (event->cgroup_class) { const char *detail = NULL; if (strcmp("path", event->cgroup_class) == 0) { if (auparse_find_field(au, "path")) detail = auparse_interpret_field(au); } else if (strcmp("major", event->cgroup_class) == 0) { detail = auparse_find_field(au, "category"); } event->cgroup_detail = copy_str(detail); } event->cgroup_acl = copy_str(auparse_find_field(au, "acl")); } if (list_append(events, event) == NULL) { event_free(event); return 1; } return 0; } int update_resource(auparse_state_t *au, const char *uuid, uid_t uid, time_t time, const char *name, int success, const char *reason, const char *res_type, const char *res) { if (!is_resource(res) || !success) return 0; list_node_t *it; struct event *start = NULL; /* Find the last start event for the uuid */ for (it = events->tail; it; it = it->prev) { start = it->data; if (start->type == ET_RES && start->success && start->uuid && strcmp(uuid, start->uuid) == 0 && strcmp(res_type, start->res_type) == 0 && strcmp(res, start->res) == 0) break; } if (it == NULL) { if (debug) { fprintf(stderr, "Couldn't find the correlated resource" " record to update for %s.\n", res_type); } return 0; } start->end = time; add_proof(start, au); return 0; } int process_resource_event(auparse_state_t *au) { uid_t uid; time_t time; const char *res_type, *uuid, *name; char field[64]; const char *reason; int success; /* Just skip this record if it failed to get some of the fields */ if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success)) return 0; /* Get the resource type */ auparse_first_record(au); res_type = auparse_find_field(au, "resrc"); reason = auparse_find_field(au, "reason"); if (res_type == NULL) { if (debug) fprintf(stderr, "Invalid resrc field.\n"); return 0; } /* Resource records with these types have old and new values. New * values indicate resources assignments and are added to the event * list. Old values are used to update the end time of a resource * assignment. */ int rc = 0; if (strcmp("disk", res_type) == 0 || strcmp("vcpu", res_type) == 0 || strcmp("mem", res_type) == 0 || strcmp("rng", res_type) == 0 || strcmp("net", res_type) == 0) { const char *res = NULL; /* Resource removed */ snprintf(field, sizeof(field), "old-%s", res_type); if(auparse_find_field(au, field)) res = auparse_interpret_field(au); if (res == NULL && debug) { fprintf(stderr, "Failed to get %s field.\n", field); } else { rc += update_resource(au, uuid, uid, time, name, success, reason, res_type, res); } /* Resource added */ res = NULL; snprintf(field, sizeof(field), "new-%s", res_type); if (auparse_find_field(au, field)) res = auparse_interpret_field(au); if (res == NULL && debug) { fprintf(stderr, "Failed to get %s field.\n", field); } else { rc += add_resource(au, uuid, uid, time, name, success, reason, res_type, res); } } else if (strcmp("cgroup", res_type) == 0) { auparse_first_record(au); const char *cgroup = NULL; if (auparse_find_field(au, "cgroup")) cgroup = auparse_interpret_field(au); rc += add_resource(au, uuid, uid, time, name, success, reason, res_type, cgroup); } else if (debug) { fprintf(stderr, "Found an unknown resource: %s.\n", res_type); } return rc; } /* Search for the last machine_id record with the given seclevel */ struct event *get_machine_id_by_seclevel(const char *seclevel) { struct event *machine_id = NULL; list_node_t *it; for (it = events->tail; it; it = it->prev) { struct event *event = it->data; if (event->type == ET_MACHINE_ID && event->seclevel != NULL && strcmp(event->seclevel, seclevel) == 0) { machine_id = event; break; } } return machine_id; } int process_avc_selinux_context(auparse_state_t *au, const char *context) { const char *seclevel; struct event *machine_id, *avc; uid_t uid; time_t time; seclevel = get_seclevel(auparse_find_field(au, context)); if (seclevel == NULL) { if (debug) { fprintf(stderr, "Security context not found " "for AVC event.\n"); } return 0; } if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL)) return 0; machine_id = get_machine_id_by_seclevel(seclevel); if (machine_id == NULL) { if (debug) { fprintf(stderr, "Couldn't get the security " "level from the AVC event.\n"); } return 0; } avc = event_alloc(); if (avc == NULL) return 1; avc->type = ET_AVC; /* Guest info */ avc->uuid = copy_str(machine_id->uuid); avc->name = copy_str(machine_id->name); memcpy(avc->proof, machine_id->proof, sizeof(avc->proof)); /* AVC info */ avc->start = time; avc->uid = uid; avc->seclevel = copy_str(seclevel); auparse_first_record(au); avc->avc_result = copy_str(auparse_find_field(au, "seresult")); avc->avc_operation = copy_str(auparse_find_field(au, "seperms")); if (auparse_find_field(au, "comm")) avc->comm = copy_str(auparse_interpret_field(au)); if (auparse_find_field(au, "name")) avc->target = copy_str(auparse_interpret_field(au)); /* get the context related to the permission that was denied. */ if (avc->avc_operation) { const char *ctx = NULL; if (strcmp("relabelfrom", avc->avc_operation) == 0) { ctx = auparse_find_field(au, "scontext"); } else if (strcmp("relabelto", avc->avc_operation) == 0) { ctx = auparse_find_field(au, "tcontext"); } avc->context = copy_str(ctx); } add_proof(avc, au); if (list_append(events, avc) == NULL) { event_free(avc); return 1; } return 0; } /* AVC records are correlated to guest through the selinux context. */ int process_avc_selinux(auparse_state_t *au) { const char **context; const char *contexts[] = { "tcontext", "scontext", NULL }; for (context = contexts; context && *context; context++) { if (process_avc_selinux_context(au, *context)) return 1; } return 0; } #ifdef WITH_APPARMOR int process_avc_apparmor_source(auparse_state_t *au) { uid_t uid = -1; time_t time = 0; struct event *avc; const char *target; /* Get the target object. */ if (auparse_find_field(au, "name") == NULL) { if (debug) { auparse_first_record(au); fprintf(stderr, "Couldn't get the resource name from " "the AVC record: %s\n", auparse_get_record_text(au)); } return 0; } target = auparse_interpret_field(au); /* Loop backwards to find a guest session with the target object * assigned to. */ struct list_node_t *it; struct event *res = NULL; for (it = events->tail; it; it = it->prev) { struct event *event = it->data; if (event->success) { if (event->type == ET_DOWN) { /* It's just possible to find a matching guest * session in the current host session. */ break; } else if (event->type == ET_RES && event->end == 0 && event->res != NULL && strcmp(target, event->res) == 0) { res = event; break; } } } /* Check if a resource event was found. */ if (res == NULL) { if (debug) { fprintf(stderr, "Target object not found for AVC " "event.\n"); } return 0; } if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL)) return 0; avc = event_alloc(); if (avc == NULL) return 1; avc->type = ET_AVC; /* Guest info */ avc->uuid = copy_str(res->uuid); avc->name = copy_str(res->name); memcpy(avc->proof, res->proof, sizeof(avc->proof)); /* AVC info */ avc->start = time; avc->uid = uid; auparse_first_record(au); if (auparse_find_field(au, "apparmor")) { int i; avc->avc_result = copy_str(auparse_interpret_field(au)); for (i = 0; avc->avc_result && avc->avc_result[i]; i++) { avc->avc_result[i] = tolower(avc->avc_result[i]); } } if (auparse_find_field(au, "operation")) avc->avc_operation = copy_str(auparse_interpret_field(au)); avc->target = copy_str(target); if (auparse_find_field(au, "comm")) avc->comm = copy_str(auparse_interpret_field(au)); add_proof(avc, au); if (list_append(events, avc) == NULL) { event_free(avc); return 1; } return 0; } int process_avc_apparmor_target(auparse_state_t *au) { uid_t uid; time_t time; const char *profile; struct event *avc; /* Get profile associated with the AVC record */ if (auparse_find_field(au, "profile") == NULL) { if (debug) { auparse_first_record(au); fprintf(stderr, "AppArmor profile not found for AVC " "record: %s\n", auparse_get_record_text(au)); } return 0; } profile = auparse_interpret_field(au); /* Break path to get just the basename */ const char *basename = profile + strlen(profile); while (basename != profile && *basename != '/') basename--; if (*basename == '/') basename++; /* Check if it is an apparmor profile generated by libvirt and get the * guest UUID from it */ const char *prefix = "libvirt-"; if (strncmp(prefix, basename, strlen(prefix)) != 0) { if (debug) { fprintf(stderr, "Found a profile which is not " "generated by libvirt: %s\n", profile); } return 0; } /* Try to find a valid guest session */ const char *uuid = basename + strlen(prefix); struct list_node_t *it; struct event *machine_id = NULL; for (it = events->tail; it; it = it->prev) { struct event *event = it->data; if (event->success) { if (event->uuid != NULL && strcmp(event->uuid, uuid) == 0) { /* machine_id is used here instead of the start * event because it is generated before any * other event when a guest is started. So, * it's possible to correlate AVC events that * occurs during a guest start. */ if (event->type == ET_MACHINE_ID) { machine_id = event; break; } else if (event->type == ET_STOP) { break; } } else if (event->type == ET_DOWN) { break; } } } if (machine_id == NULL) { if (debug) { fprintf(stderr, "Found an AVC record for an unknown " "guest.\n"); } return 0; } if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL)) return 0; avc = event_alloc(); if (avc == NULL) return 1; avc->type = ET_AVC; /* Guest info */ avc->uuid = copy_str(machine_id->uuid); avc->name = copy_str(machine_id->name); memcpy(avc->proof, machine_id->proof, sizeof(avc->proof)); /* AVC info */ avc->start = time; avc->uid = uid; auparse_first_record(au); if (auparse_find_field(au, "apparmor")) { int i; avc->avc_result = copy_str(auparse_interpret_field(au)); for (i = 0; avc->avc_result && avc->avc_result[i]; i++) { avc->avc_result[i] = tolower(avc->avc_result[i]); } } if (auparse_find_field(au, "operation")) avc->avc_operation = copy_str(auparse_interpret_field(au)); if (auparse_find_field(au, "name")) avc->target = copy_str(auparse_interpret_field(au)); if (auparse_find_field(au, "comm")) avc->comm = copy_str(auparse_interpret_field(au)); add_proof(avc, au); if (list_append(events, avc) == NULL) { event_free(avc); return 1; } return 0; } /* AVC records are correlated to guest through the apparmor path name. */ int process_avc_apparmor(auparse_state_t *au) { if (process_avc_apparmor_target(au)) return 1; auparse_first_record(au); return process_avc_apparmor_source(au); } #endif int process_avc(auparse_state_t *au) { /* Check if it is a SELinux AVC record */ if (auparse_find_field(au, "tcontext")) { auparse_first_record(au); return process_avc_selinux(au); } #ifdef WITH_APPARMOR /* Check if it is an AppArmor AVC record */ auparse_first_record(au); if (auparse_find_field(au, "apparmor")) { auparse_first_record(au); return process_avc_apparmor(au); } #endif return 0; } /* This function tries to correlate an anomaly record to a guest using the qemu * pid or the selinux context. */ int process_anom(auparse_state_t *au) { uid_t uid; time_t time; pid_t pid = -1; list_node_t *it; struct event *anom, *start = NULL; /* An anomaly record is correlated to a guest by the process id */ if (auparse_find_field(au, "pid")) { pid = auparse_get_field_int(au); } else { if (debug) { fprintf(stderr, "Found an anomaly record " "without pid.\n"); } } /* Loop backwards to find a running guest with the same pid. */ if (pid >= 0) { for (it = events->tail; it; it = it->next) { struct event *event = it->data; if (event->pid == pid && event->success) { if (event->type == ET_STOP) { break; } else if (event->type == ET_START) { if (event->end == 0) start = event; break; } } } } /* Try to match using selinux context */ if (start == NULL) { const char *seclevel; struct event *machine_id; seclevel = get_seclevel(auparse_find_field(au, "subj")); if (seclevel == NULL) { if (debug) { auparse_first_record(au); const char *text = auparse_get_record_text(au); fprintf(stderr, "Security context not found " "for anomaly event: %s\n", text ? text : ""); } return 0; } machine_id = get_machine_id_by_seclevel(seclevel); if (machine_id == NULL) { if (debug) { fprintf(stderr, "Couldn't get the security " "level from the anomaly event.\n"); } return 0; } for (it = events->tail; it; it = it->next) { struct event *event = it->data; if (event->success && machine_id->uuid && event->uuid && strcmp(machine_id->uuid, event->uuid) == 0) { if (event->type == ET_STOP) { break; } else if (event->type == ET_START) { if (event->end == 0) start = event; break; } } } } if (start == NULL) { if (debug) { const char *text = auparse_get_record_text(au); fprintf(stderr, "Guest not found for " "anomaly record: %s.\n", text ? text : ""); } return 0; } if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL)) return 0; anom = event_alloc(); if (anom == NULL) return 1; anom->type = ET_ANOM; anom->uuid = copy_str(start->uuid); anom->name = copy_str(start->name); anom->uid = uid; anom->start = time; anom->pid = pid; memcpy(anom->proof, start->proof, sizeof(anom->proof)); add_proof(anom, au); if (list_append(events, anom) == NULL) { event_free(anom); return 1; } return 0; } int process_shutdown(auparse_state_t *au) { uid_t uid = -1; time_t time = 0; struct event *down; list_node_t *it; int success = 0; if (extract_virt_fields(au, NULL, &uid, &time, NULL, &success)) return 0; for (it = events->tail; it; it = it->prev) { struct event *event = it->data; if (event->success) { if (event->type == ET_START || event->type == ET_RES) { if (event->end == 0) { event->end = time; add_proof(event, au); } } else if (event->type == ET_DOWN) { break; } } } down = event_alloc(); if (down == NULL) return 1; down->type = ET_DOWN; down->uid = uid; down->start = time; down->success = success; add_proof(down, au); if (list_append(events, down) == NULL) { event_free(down); return 1; } return 0; } /* Convert record type to a string */ const char *get_rec_type(struct event *e) { static char buf[64]; if (e == NULL) return ""; switch (e->type) { case ET_START: return "start"; case ET_STOP: return "stop"; case ET_RES: return "res"; case ET_AVC: return "avc"; case ET_ANOM: return "anom"; case ET_DOWN: return "down"; } snprintf(buf, sizeof(buf), "%d", e->type); return buf; } /* Convert uid to a string */ const char *get_username(struct event *e) { static char s[256]; if (!e || (int)e->uid == -1) { s[0] = '?'; s[1] = '\0'; } else { struct passwd *passwd = getpwuid(e->uid); if (passwd == NULL || passwd->pw_name == NULL) { snprintf(s, sizeof(s), "%d", e->uid); } else { snprintf(s, sizeof(s), "%s", passwd->pw_name); } } return s; } /* Convert a time period to string */ const char *get_time_period(struct event *event) { size_t i = 0; static char buf[128]; i += sprintf(buf + i, "%-16.16s", ctime(&event->start)); if (event->end) { time_t secs = event->end - event->start; int mins, hours, days; i += sprintf(buf + i, " - %-7.5s", ctime(&event->end) + 11); mins = (secs / 60) % 60; hours = (secs / 3600) % 24; days = secs / 86400; if (days) { i += sprintf(buf + i, "(%d+%02d:%02d)", days, hours, mins); } else { i += sprintf(buf + i, "(%02d:%02d)", hours, mins); } } else { if (!event->success && event->type != ET_AVC && event->type != ET_ANOM) { i += sprintf(buf + i, " - failed"); } } return buf; } void print_event(struct event *event) { /* Auxiliary macro to convert NULL to "" */ #define N(str) ((str) ? str : "") /* machine id records are used just to get information about * the guests. */ if (event->type == ET_MACHINE_ID) return; /* If "--all-events" is not given, only the start event is shown. */ if (!all_events_flag && event->type != ET_START) return; /* The type of event is shown only when all records are shown */ if (all_events_flag) printf("%-5.5s ", get_rec_type(event)); /* Print common fields */ printf("%-25.25s", N(event->name)); if (uuid_flag) printf("\t%-36.36s", N(event->uuid)); printf("\t%-11.11s\t%-35.35s", get_username(event), get_time_period(event)); /* Print type specific fields */ if (event->type == ET_RES) { printf("\t%-12.12s", N(event->res_type)); printf("\t%-10.10s", N(event->reason)); if (strcmp("cgroup", event->res_type) != 0) { printf("\t%s", N(event->res)); } else { printf("\t%s\t%s\t%s", N(event->cgroup_class), N(event->cgroup_acl), N(event->cgroup_detail)); } } else if (event->type == ET_MACHINE_ID) { printf("\t%s", N(event->seclevel)); } else if (event->type == ET_AVC) { printf("\t%-12.12s", N(event->avc_operation)); printf("\t%-10.10s", N(event->avc_result)); printf("\t%s\t%s\t%s", N(event->comm), N(event->target), N(event->context)); } printf("\n"); /* Print proof */ if (proof_flag) { int first = 1; int i, len = sizeof(event->proof)/sizeof(event->proof[0]); printf(" Proof:"); for (i = 0; i < len; i++) { if (event->proof[i].time) { printf("%s %ld.%03u:%lu", (first) ? "" : ",", event->proof[i].time, event->proof[i].milli, event->proof[i].serial); first = 0; } } printf("\n\n"); } } /* Print all events */ void print_events(void) { list_node_t *it; for (it = events->head; it; it = it->next) { struct event *event = it->data; if (event) print_event(event); } } /* Count and print summary */ void print_summary(void) { /* Summary numbers */ time_t start_time = 0, end_time = 0; long start = 0, stop = 0, res = 0, avc = 0, anom = 0, shutdown = 0, failure = 0; char start_buf[32], end_buf[32]; /* Calculate summary */ list_node_t *it; for (it = events->head; it; it = it->next) { struct event *event = it->data; if (event->success == 0 && (event->type == ET_START || event->type == ET_STOP || event->type == ET_RES)) { failure++; } else { switch (event->type) { case ET_START: start++; break; case ET_STOP: stop++; break; case ET_RES: res++; break; case ET_AVC: avc++; break; case ET_ANOM: anom++; break; case ET_DOWN: shutdown++; break; } } /* Calculate time range */ if (event->start) { if (start_time == 0 || event->start < start_time) { start_time = event->start; } if (end_time == 0 || event->start > end_time) { end_time = event->start; } } if (event->end) { if (start_time == 0 || event->end < start_time) { start_time = event->end; } if (end_time == 0 || event->end > end_time) { end_time = event->end; } } } if (start_time) ctime_r(&start_time, start_buf); else strcpy(start_buf, "undef"); if (end_time) ctime_r(&end_time, end_buf); else strcpy(end_buf, "undef"); /* Print summary */ printf("Range of time for report: %-.16s - %-.16s\n", start_buf, end_buf); printf("Number of guest starts: %ld\n", start); printf("Number of guest stops: %ld\n", stop); printf("Number of resource assignments: %ld\n", res); printf("Number of related AVCs: %ld\n", avc); printf("Number of related anomalies: %ld\n", anom); printf("Number of host shutdowns: %ld\n", shutdown); printf("Number of failed operations: %ld\n", failure); } int main(int argc, char **argv) { int rc = 0; auparse_state_t *au = NULL; setlocale(LC_ALL, ""); if (parse_args(argc, argv)) goto error; if (help_flag) { usage(stdout); goto exit; } /* Initialize event list*/ events = list_new((list_free_data_fn*) event_free); if (events == NULL) goto unexpected_error; /* Initialize auparse */ au = init_auparse(); if (au == NULL) goto error; if (create_search_criteria(au)) goto error; while (ausearch_next_event(au) > 0) { int err = 0; switch(auparse_get_type(au)) { case AUDIT_VIRT_MACHINE_ID: err = process_machine_id_event(au); break; case AUDIT_VIRT_CONTROL: err = process_control_event(au); break; case AUDIT_VIRT_RESOURCE: err = process_resource_event(au); break; case AUDIT_AVC: err = process_avc(au); break; case AUDIT_FIRST_ANOM_MSG ... AUDIT_LAST_ANOM_MSG: case AUDIT_FIRST_KERN_ANOM_MSG ... AUDIT_LAST_KERN_ANOM_MSG: err = process_anom(au); break; case AUDIT_SYSTEM_SHUTDOWN: err = process_shutdown(au); break; } if (err) { goto unexpected_error; } auparse_next_event(au); } /* Show results */ if (summary_flag) { print_summary(); } else { print_events(); } /* success */ goto exit; unexpected_error: fprintf(stderr, "Unexpected error\n"); error: rc = 1; exit: if (au) auparse_destroy(au); list_free(events); if (debug) fprintf(stdout, "Exit code: %d\n", rc); return rc; }