2 * auvirt.c - A tool to extract data related to virtualization.
3 * Copyright (c) 2011 IBM Corp.
6 * This software may be freely redistributed and/or modified under the
7 * terms of the GNU General Public License as published by the Free
8 * Software Foundation; either version 2, or (at your option) any
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; see the file COPYING. If not, write to the
18 * Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 * Marcelo Henrique Cerri <mhcerri@br.ibm.com>
33 #include <sys/types.h>
38 #include "ausearch-time.h"
39 #include "auvirt-list.h"
41 /* Command line parameters */
42 static int help_flag = 0;
43 static int stdin_flag = 0;
44 static int summary_flag = 0;
45 static int all_events_flag = 0;
46 static int uuid_flag = 0;
47 static int proof_flag = 0;
48 static const char *vm = NULL;
49 static const char *uuid = NULL;
50 static const char *file = NULL;
53 * The start time and end time given in the command line is stored respectively
54 * in the variables start_time and end_time that are declared/defined in the
55 * files ausearch-time.h and ausearch-time.c. These files are reused from the
56 * ausearch tool source code:
58 * time_t start_time = 0;
59 * time_t end_time = 0;
64 ET_NONE = 0, ET_START, ET_STOP, ET_MACHINE_ID, ET_AVC, ET_RES, ET_ANOM,
81 /* Fields specific for resource events: */
85 /* Fields specific for cgroup resources */
89 /* Fields specific for machine id events: */
91 /* Fields specific for avc events: */
97 /* Fields to print proof information: */
98 struct record_id proof[4];
100 list_t *events = NULL;
103 /* Auxiliary functions to allocate and to free events. */
104 struct event *event_alloc(void)
106 struct event *event = malloc(sizeof(struct event));
108 /* The new event is initialized with values that represents
109 * unset values: -1 for uid and pid and 0 (or NULL) for numbers
110 * and pointers. For example, event->end = 0 represents an
113 memset(event, 0, sizeof(struct event));
120 void event_free(struct event *event)
126 free(event->res_type);
128 free(event->avc_result);
129 free(event->avc_operation);
130 free(event->seclevel);
133 free(event->cgroup_class);
134 free(event->cgroup_detail);
135 free(event->cgroup_acl);
136 free(event->context);
141 inline char *copy_str(const char *str)
143 return (str) ? strdup(str) : NULL;
146 void usage(FILE *output)
148 fprintf(output, "usage: auvirt [--stdin] [--all-events] [--summary] "
149 "[--start start-date [start-time]] "
150 "[--end end-date [end-time]] [--file file-name] "
151 "[--show-uuid] [--proof] "
152 "[--uuid uuid] [--vm vm-name]\n");
155 /* Parse and check command line arguments */
156 int parse_args(int argc, char **argv)
158 /* Based on http://www.ietf.org/rfc/rfc4122.txt */
159 const char *uuid_pattern = "^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-"
160 "[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$";
164 if (regcomp(&uuid_regex, uuid_pattern, REG_EXTENDED)) {
165 fprintf(stderr, "Failed to initialize program.\n");
169 for (i = 1; i < argc; i++) {
170 const char *opt = argv[i];
172 fprintf(stderr, "Argument not expected: %s\n", opt);
174 } else if (strcmp("--vm", opt) == 0 ||
175 strcmp("-v", opt) == 0) {
176 if ((i + 1) >= argc || argv[i + 1][0] == '-') {
177 fprintf(stderr, "\"%s\" option requires "
178 "an argument.\n", opt);
182 } else if (strcmp("--uuid", opt) == 0 ||
183 strcmp("-u", opt) == 0) {
184 if ((i + 1) >= argc || argv[i + 1][0] == '-') {
185 fprintf(stderr, "\"%s\" option requires "
186 "an argument.\n", opt);
189 if (regexec(&uuid_regex, argv[i + 1], 0, NULL, 0)) {
190 fprintf(stderr, "Invalid uuid: %s\n",
195 } else if (strcmp("--all-events", opt) == 0 ||
196 strcmp("-a", opt) == 0) {
198 } else if (strcmp("--summary", opt) == 0 ||
199 strcmp("-s", opt) == 0) {
201 } else if (strcmp("--file", opt) == 0 ||
202 strcmp("-f", opt) == 0) {
203 if ((i + 1) >= argc || argv[i + 1][0] == '-') {
204 fprintf(stderr, "\"%s\" option requires "
205 "an argument.\n", opt);
209 } else if (strcmp("--show-uuid", opt) == 0) {
211 } else if (strcmp("--stdin", opt) == 0) {
213 } else if (strcmp("--proof", opt) == 0) {
215 } else if (strcmp("--help", opt) == 0 ||
216 strcmp("-h", opt) == 0) {
219 } else if (strcmp("--start", opt) == 0 ||
220 strcmp("-ts", opt) == 0) {
221 const char *date, *time = NULL;
222 if ((i + 1) >= argc || argv[i + 1][0] == '-') {
223 fprintf(stderr, "\"%s\" option requires at "
224 "least one argument.\n", opt);
228 if ((i + 1) < argc && argv[i + 1][0] != '-')
230 /* This will set start_time */
231 if(ausearch_time_start(date, time))
233 } else if (strcmp("--end", opt) == 0 ||
234 strcmp("-te", opt) == 0) {
235 const char *date, *time = NULL;
236 if ((i + 1) >= argc || argv[i + 1][0] == '-') {
237 fprintf(stderr, "\"%s\" option requires at "
238 "least one argument.\n", opt);
242 if ((i + 1) < argc && argv[i + 1][0] != '-')
244 /* This will set end_time */
245 if (ausearch_time_end(date, time))
247 } else if (strcmp("--debug", opt) == 0) {
250 fprintf(stderr, "Unknown option \"%s\".\n", opt);
255 /* Validate conflicting options */
256 if (stdin_flag && file) {
257 fprintf(stderr, "\"--sdtin\" and \"--file\" options "
258 "must not be specified together.\n");
263 fprintf(stderr, "help_flag='%i'\n", help_flag);
264 fprintf(stderr, "stdin_flag='%i'\n", stdin_flag);
265 fprintf(stderr, "all_events_flag='%i'\n", all_events_flag);
266 fprintf(stderr, "summary_flag='%i'\n", summary_flag);
267 fprintf(stderr, "uuid='%s'\n", uuid ? uuid : "(null)");
268 fprintf(stderr, "vm='%s'\n", vm ? vm : "(null)");
269 fprintf(stderr, "file='%s'\n", file ? file : "(null)");
270 fprintf(stderr, "start_time='%-.16s'\n", (start_time == 0L) ?
271 "" : ctime(&start_time));
272 fprintf(stderr, "end_time='%-.16s'\n", (end_time == 0L) ?
273 "" : ctime(&end_time));
277 regfree(&uuid_regex);
284 /* Initialize an auparse_state_t with the correct log source. */
285 auparse_state_t *init_auparse(void)
287 auparse_state_t *au = NULL;
289 au = auparse_init(AUSOURCE_FILE_POINTER, stdin);
291 au = auparse_init(AUSOURCE_FILE, file);
294 fprintf(stderr, "You probably need to be root for "
297 au = auparse_init(AUSOURCE_LOGS, NULL);
300 fprintf(stderr, "Error: %s\n", strerror(errno));
305 /* Create a criteria to search for the virtualization related records */
306 int create_search_criteria(auparse_state_t *au)
310 snprintf(expr, sizeof(expr),
311 "(\\record_type >= %d && \\record_type <= %d)",
312 AUDIT_FIRST_VIRT_MSG, AUDIT_LAST_VIRT_MSG);
313 if (ausearch_add_expression(au, expr, &error, AUSEARCH_RULE_CLEAR)) {
314 fprintf(stderr, "Criteria error: %s\n", error);
319 if (ausearch_add_item(au, "uuid", "=", uuid,
320 AUSEARCH_RULE_AND)) {
321 fprintf(stderr, "Criteria error: uuid\n");
326 if (ausearch_add_interpreted_item(au, "vm", "=", vm,
327 AUSEARCH_RULE_AND)) {
328 fprintf(stderr, "Criteria error: id\n");
332 if (all_events_flag || summary_flag) {
333 if (ausearch_add_item(au, "type", "=", "AVC",
335 fprintf(stderr, "Criteria error: AVC\n");
338 if (ausearch_add_item(au, "type", "=", "SYSTEM_SHUTDOWN",
340 fprintf(stderr, "Criteria error: shutdown\n");
343 snprintf(expr, sizeof(expr),
344 "(\\record_type >= %d && \\record_type <= %d) ||"
345 "(\\record_type >= %d && \\record_type <= %d)",
346 AUDIT_FIRST_ANOM_MSG, AUDIT_LAST_ANOM_MSG,
347 AUDIT_FIRST_KERN_ANOM_MSG, AUDIT_LAST_KERN_ANOM_MSG);
348 if (ausearch_add_expression(au, expr, &error,
350 fprintf(stderr, "Criteria error: %s\n", error);
356 if (ausearch_add_timestamp_item(au, ">=", start_time, 0,
357 AUSEARCH_RULE_AND)) {
358 fprintf(stderr, "Criteria error: start_time\n");
363 if (ausearch_add_timestamp_item(au, "<=", end_time, 0,
364 AUSEARCH_RULE_AND)) {
365 fprintf(stderr, "Criteria error: end_time\n");
372 /* Extract the most common fields from virtualization-related records. */
373 int extract_virt_fields(auparse_state_t *au, const char **p_uuid,
374 uid_t *p_uid, time_t *p_time, const char **p_name,
378 auparse_first_record(au);
381 if (!auparse_find_field(au, field = "uid"))
383 *p_uid = auparse_get_field_int(au);
386 if (!auparse_find_field(au, field = "vm"))
388 *p_name = auparse_interpret_field(au);
391 if (!auparse_find_field(au, field = "uuid"))
393 *p_uuid = auparse_get_field_str(au);
396 const char *res = auparse_find_field(au, field = "res");
399 *p_suc = (strcmp("success", res) == 0) ? 1 : 0;
402 *p_time = auparse_get_time(au);
408 fprintf(stderr, "Failed to get field \"%s\" for record "
409 "%ld.%03u:%lu\n", field ? field : "",
410 auparse_get_time(au),
411 auparse_get_milli(au),
412 auparse_get_serial(au));
417 /* Return label and categories from a security context. */
418 const char *get_seclevel(const char *seclabel)
421 * system_u:system_r:svirt_t:s0:c107,c434
427 for (;seclabel && *seclabel; seclabel++) {
428 if (*seclabel == ':')
436 int add_proof(struct event *event, auparse_state_t *au)
441 size_t i, proof_len = sizeof(event->proof)/sizeof(event->proof[0]);
442 for (i = 0; i < proof_len; i++) {
443 if (event->proof[i].time == 0)
446 if (i == proof_len) {
448 fprintf(stderr, "Failed to add proof.\n");
452 event->proof[i].time = auparse_get_time(au);
453 event->proof[i].milli = auparse_get_milli(au);
454 event->proof[i].serial = auparse_get_serial(au);
459 * machine_id records are used to get the selinux context associated to a
462 int process_machine_id_event(auparse_state_t *au)
466 const char *seclevel, *uuid, *name;
470 seclevel = get_seclevel(auparse_find_field(au, "vm-ctx"));
471 if (seclevel == NULL) {
473 fprintf(stderr, "Security context not found for "
474 "MACHINE_ID event.\n");
477 if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success))
480 event = event_alloc();
483 event->type = ET_MACHINE_ID;
484 event->uuid = copy_str(uuid);
485 event->name = copy_str(name);
486 event->success = success;
487 event->seclevel = copy_str(seclevel);
490 add_proof(event, au);
491 if (list_append(events, event) == NULL) {
498 int add_start_guest_event(auparse_state_t *au)
503 const char *uuid, *name;
507 /* Just skip this record if it failed to get some of the fields */
508 if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success))
511 /* On failure, loop backwards to update all the resources associated to
512 * the last session of this guest. When a machine_id or a stop event is
513 * found the loop can be broken because a machine_id is created at the
514 * beginning of a session and a stop event indicates a previous
518 for (it = events->tail; it; it = it->prev) {
519 struct event *event = it->data;
520 if (event->success && event->uuid &&
521 strcmp(uuid, event->uuid) == 0) {
522 if (event->type == ET_STOP ||
523 event->type == ET_MACHINE_ID) {
524 /* An old session found. */
526 } else if (event->type == ET_RES &&
529 add_proof(event, au);
535 start = event_alloc();
538 start->type = ET_START;
539 start->uuid = copy_str(uuid);
540 start->name = copy_str(name);
541 start->success = success;
544 auparse_first_record(au);
545 if (auparse_find_field(au, "vm-pid"))
546 start->pid = auparse_get_field_int(au);
547 add_proof(start, au);
548 if (list_append(events, start) == NULL) {
555 int add_stop_guest_event(auparse_state_t *au)
558 struct event *stop, *start = NULL, *event = NULL;
561 const char *uuid, *name;
564 /* Just skip this record if it failed to get some of the fields */
565 if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success))
568 /* Loop backwards to find the last start event for the uuid and
569 * update all resource records related to that guest session.
571 for (it = events->tail; it; it = it->prev) {
573 if (event->success && event->uuid &&
574 strcmp(uuid, event->uuid) == 0) {
575 if (event->type == ET_START) {
576 /* If an old session is found it's no longer
577 * necessary to update the resource records.
579 if (event->end || start)
581 /* This is the start event related to the
582 * current session. */
584 } else if (event->type == ET_STOP ||
585 event->type == ET_MACHINE_ID) {
586 /* Old session found. */
588 } else if (event->type == ET_RES && event->end == 0) {
589 /* Update the resource assignments. */
591 add_proof(event, au);
597 fprintf(stderr, "Couldn't find the correlated start "
598 "record to the stop event.\n");
603 /* Create a new stop event */
604 stop = event_alloc();
607 stop->type = ET_STOP;
608 stop->uuid = copy_str(uuid);
609 stop->name = copy_str(name);
610 stop->success = success;
613 auparse_first_record(au);
614 if (auparse_find_field(au, "vm-pid"))
615 stop->pid = auparse_get_field_int(au);
617 if (list_append(events, stop) == NULL) {
622 /* Update the correlated start event. */
625 add_proof(start, au);
630 int process_control_event(auparse_state_t *au)
634 op = auparse_find_field(au, "op");
637 fprintf(stderr, "Invalid op field.\n");
641 if (strcmp("start", op) == 0) {
642 if (add_start_guest_event(au))
644 } else if (strcmp("stop", op) == 0) {
645 if (add_stop_guest_event(au))
648 fprintf(stderr, "Unknown op: %s\n", op);
653 inline int is_resource(const char *res)
657 strcmp("0", res) == 0 ||
658 strcmp("?", res) == 0)
663 int add_resource(auparse_state_t *au, const char *uuid, uid_t uid, time_t time,
664 const char *name, int success, const char *reason,
665 const char *res_type, const char *res)
667 if (!is_resource(res))
670 struct event *event = event_alloc();
673 event->type = ET_RES;
674 event->uuid = copy_str(uuid);
675 event->name = copy_str(name);
676 event->success = success;
677 event->reason = copy_str(reason);
678 event->res_type = copy_str(res_type);
679 event->res = copy_str(res);
682 add_proof(event, au);
684 /* Get cgroup specific fields. */
685 if (strcmp("cgroup", res_type) == 0) {
686 event->cgroup_class = copy_str(auparse_find_field(au, "class"));
687 if (event->cgroup_class) {
688 const char *detail = NULL;
689 if (strcmp("path", event->cgroup_class) == 0) {
690 if (auparse_find_field(au, "path"))
691 detail = auparse_interpret_field(au);
692 } else if (strcmp("major", event->cgroup_class) == 0) {
693 detail = auparse_find_field(au, "category");
695 event->cgroup_detail = copy_str(detail);
697 event->cgroup_acl = copy_str(auparse_find_field(au, "acl"));
700 if (list_append(events, event) == NULL) {
707 int update_resource(auparse_state_t *au, const char *uuid, uid_t uid,
708 time_t time, const char *name, int success, const char *reason,
709 const char *res_type, const char *res)
711 if (!is_resource(res) || !success)
715 struct event *start = NULL;
717 /* Find the last start event for the uuid */
718 for (it = events->tail; it; it = it->prev) {
720 if (start->type == ET_RES &&
723 strcmp(uuid, start->uuid) == 0 &&
724 strcmp(res_type, start->res_type) == 0 &&
725 strcmp(res, start->res) == 0)
730 fprintf(stderr, "Couldn't find the correlated resource"
731 " record to update for %s.\n", res_type);
737 add_proof(start, au);
741 int process_resource_event(auparse_state_t *au)
745 const char *res_type, *uuid, *name;
750 /* Just skip this record if it failed to get some of the fields */
751 if (extract_virt_fields(au, &uuid, &uid, &time, &name, &success))
754 /* Get the resource type */
755 auparse_first_record(au);
756 res_type = auparse_find_field(au, "resrc");
757 reason = auparse_find_field(au, "reason");
758 if (res_type == NULL) {
760 fprintf(stderr, "Invalid resrc field.\n");
764 /* Resource records with these types have old and new values. New
765 * values indicate resources assignments and are added to the event
766 * list. Old values are used to update the end time of a resource
770 if (strcmp("disk", res_type) == 0 ||
771 strcmp("vcpu", res_type) == 0 ||
772 strcmp("mem", res_type) == 0 ||
773 strcmp("rng", res_type) == 0 ||
774 strcmp("net", res_type) == 0) {
775 const char *res = NULL;
776 /* Resource removed */
777 snprintf(field, sizeof(field), "old-%s", res_type);
778 if(auparse_find_field(au, field))
779 res = auparse_interpret_field(au);
780 if (res == NULL && debug) {
781 fprintf(stderr, "Failed to get %s field.\n", field);
783 rc += update_resource(au, uuid, uid, time, name,
784 success, reason, res_type, res);
789 snprintf(field, sizeof(field), "new-%s", res_type);
790 if (auparse_find_field(au, field))
791 res = auparse_interpret_field(au);
792 if (res == NULL && debug) {
793 fprintf(stderr, "Failed to get %s field.\n", field);
795 rc += add_resource(au, uuid, uid, time, name, success,
796 reason, res_type, res);
798 } else if (strcmp("cgroup", res_type) == 0) {
799 auparse_first_record(au);
800 const char *cgroup = NULL;
801 if (auparse_find_field(au, "cgroup"))
802 cgroup = auparse_interpret_field(au);
803 rc += add_resource(au, uuid, uid, time, name, success, reason,
806 fprintf(stderr, "Found an unknown resource: %s.\n",
812 /* Search for the last machine_id record with the given seclevel */
813 struct event *get_machine_id_by_seclevel(const char *seclevel)
815 struct event *machine_id = NULL;
818 for (it = events->tail; it; it = it->prev) {
819 struct event *event = it->data;
820 if (event->type == ET_MACHINE_ID &&
821 event->seclevel != NULL &&
822 strcmp(event->seclevel, seclevel) == 0) {
831 int process_avc_selinux_context(auparse_state_t *au, const char *context)
833 const char *seclevel;
834 struct event *machine_id, *avc;
838 seclevel = get_seclevel(auparse_find_field(au, context));
839 if (seclevel == NULL) {
841 fprintf(stderr, "Security context not found "
847 if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL))
850 machine_id = get_machine_id_by_seclevel(seclevel);
851 if (machine_id == NULL) {
853 fprintf(stderr, "Couldn't get the security "
854 "level from the AVC event.\n");
865 avc->uuid = copy_str(machine_id->uuid);
866 avc->name = copy_str(machine_id->name);
867 memcpy(avc->proof, machine_id->proof, sizeof(avc->proof));
872 avc->seclevel = copy_str(seclevel);
873 auparse_first_record(au);
874 avc->avc_result = copy_str(auparse_find_field(au, "seresult"));
875 avc->avc_operation = copy_str(auparse_find_field(au, "seperms"));
876 if (auparse_find_field(au, "comm"))
877 avc->comm = copy_str(auparse_interpret_field(au));
878 if (auparse_find_field(au, "name"))
879 avc->target = copy_str(auparse_interpret_field(au));
881 /* get the context related to the permission that was denied. */
882 if (avc->avc_operation) {
883 const char *ctx = NULL;
884 if (strcmp("relabelfrom", avc->avc_operation) == 0) {
885 ctx = auparse_find_field(au, "scontext");
886 } else if (strcmp("relabelto", avc->avc_operation) == 0) {
887 ctx = auparse_find_field(au, "tcontext");
889 avc->context = copy_str(ctx);
893 if (list_append(events, avc) == NULL) {
900 /* AVC records are correlated to guest through the selinux context. */
901 int process_avc_selinux(auparse_state_t *au)
903 const char **context;
904 const char *contexts[] = { "tcontext", "scontext", NULL };
906 for (context = contexts; context && *context; context++) {
907 if (process_avc_selinux_context(au, *context))
914 int process_avc_apparmor_source(auparse_state_t *au)
921 /* Get the target object. */
922 if (auparse_find_field(au, "name") == NULL) {
924 auparse_first_record(au);
925 fprintf(stderr, "Couldn't get the resource name from "
926 "the AVC record: %s\n",
927 auparse_get_record_text(au));
931 target = auparse_interpret_field(au);
933 /* Loop backwards to find a guest session with the target object
935 struct list_node_t *it;
936 struct event *res = NULL;
937 for (it = events->tail; it; it = it->prev) {
938 struct event *event = it->data;
939 if (event->success) {
940 if (event->type == ET_DOWN) {
941 /* It's just possible to find a matching guest
942 * session in the current host session.
945 } else if (event->type == ET_RES &&
947 event->res != NULL &&
948 strcmp(target, event->res) == 0) {
955 /* Check if a resource event was found. */
958 fprintf(stderr, "Target object not found for AVC "
964 if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL))
973 avc->uuid = copy_str(res->uuid);
974 avc->name = copy_str(res->name);
975 memcpy(avc->proof, res->proof, sizeof(avc->proof));
980 auparse_first_record(au);
981 if (auparse_find_field(au, "apparmor")) {
983 avc->avc_result = copy_str(auparse_interpret_field(au));
984 for (i = 0; avc->avc_result && avc->avc_result[i]; i++) {
985 avc->avc_result[i] = tolower(avc->avc_result[i]);
988 if (auparse_find_field(au, "operation"))
989 avc->avc_operation = copy_str(auparse_interpret_field(au));
990 avc->target = copy_str(target);
991 if (auparse_find_field(au, "comm"))
992 avc->comm = copy_str(auparse_interpret_field(au));
995 if (list_append(events, avc) == NULL) {
1002 int process_avc_apparmor_target(auparse_state_t *au)
1006 const char *profile;
1009 /* Get profile associated with the AVC record */
1010 if (auparse_find_field(au, "profile") == NULL) {
1012 auparse_first_record(au);
1013 fprintf(stderr, "AppArmor profile not found for AVC "
1015 auparse_get_record_text(au));
1019 profile = auparse_interpret_field(au);
1021 /* Break path to get just the basename */
1022 const char *basename = profile + strlen(profile);
1023 while (basename != profile && *basename != '/')
1025 if (*basename == '/')
1028 /* Check if it is an apparmor profile generated by libvirt and get the
1029 * guest UUID from it */
1030 const char *prefix = "libvirt-";
1031 if (strncmp(prefix, basename, strlen(prefix)) != 0) {
1033 fprintf(stderr, "Found a profile which is not "
1034 "generated by libvirt: %s\n", profile);
1039 /* Try to find a valid guest session */
1040 const char *uuid = basename + strlen(prefix);
1041 struct list_node_t *it;
1042 struct event *machine_id = NULL;
1043 for (it = events->tail; it; it = it->prev) {
1044 struct event *event = it->data;
1045 if (event->success) {
1046 if (event->uuid != NULL &&
1047 strcmp(event->uuid, uuid) == 0) {
1048 /* machine_id is used here instead of the start
1049 * event because it is generated before any
1050 * other event when a guest is started. So,
1051 * it's possible to correlate AVC events that
1052 * occurs during a guest start.
1054 if (event->type == ET_MACHINE_ID) {
1057 } else if (event->type == ET_STOP) {
1060 } else if (event->type == ET_DOWN) {
1065 if (machine_id == NULL) {
1067 fprintf(stderr, "Found an AVC record for an unknown "
1073 if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL))
1076 avc = event_alloc();
1082 avc->uuid = copy_str(machine_id->uuid);
1083 avc->name = copy_str(machine_id->name);
1084 memcpy(avc->proof, machine_id->proof, sizeof(avc->proof));
1089 auparse_first_record(au);
1090 if (auparse_find_field(au, "apparmor")) {
1092 avc->avc_result = copy_str(auparse_interpret_field(au));
1093 for (i = 0; avc->avc_result && avc->avc_result[i]; i++) {
1094 avc->avc_result[i] = tolower(avc->avc_result[i]);
1097 if (auparse_find_field(au, "operation"))
1098 avc->avc_operation = copy_str(auparse_interpret_field(au));
1099 if (auparse_find_field(au, "name"))
1100 avc->target = copy_str(auparse_interpret_field(au));
1101 if (auparse_find_field(au, "comm"))
1102 avc->comm = copy_str(auparse_interpret_field(au));
1105 if (list_append(events, avc) == NULL) {
1112 /* AVC records are correlated to guest through the apparmor path name. */
1113 int process_avc_apparmor(auparse_state_t *au)
1115 if (process_avc_apparmor_target(au))
1117 auparse_first_record(au);
1118 return process_avc_apparmor_source(au);
1122 int process_avc(auparse_state_t *au)
1124 /* Check if it is a SELinux AVC record */
1125 if (auparse_find_field(au, "tcontext")) {
1126 auparse_first_record(au);
1127 return process_avc_selinux(au);
1130 #ifdef WITH_APPARMOR
1131 /* Check if it is an AppArmor AVC record */
1132 auparse_first_record(au);
1133 if (auparse_find_field(au, "apparmor")) {
1134 auparse_first_record(au);
1135 return process_avc_apparmor(au);
1141 /* This function tries to correlate an anomaly record to a guest using the qemu
1142 * pid or the selinux context. */
1143 int process_anom(auparse_state_t *au)
1149 struct event *anom, *start = NULL;
1151 /* An anomaly record is correlated to a guest by the process id */
1152 if (auparse_find_field(au, "pid")) {
1153 pid = auparse_get_field_int(au);
1156 fprintf(stderr, "Found an anomaly record "
1161 /* Loop backwards to find a running guest with the same pid. */
1163 for (it = events->tail; it; it = it->next) {
1164 struct event *event = it->data;
1165 if (event->pid == pid && event->success) {
1166 if (event->type == ET_STOP) {
1168 } else if (event->type == ET_START) {
1169 if (event->end == 0)
1177 /* Try to match using selinux context */
1178 if (start == NULL) {
1179 const char *seclevel;
1180 struct event *machine_id;
1182 seclevel = get_seclevel(auparse_find_field(au, "subj"));
1183 if (seclevel == NULL) {
1185 auparse_first_record(au);
1186 const char *text = auparse_get_record_text(au);
1187 fprintf(stderr, "Security context not found "
1188 "for anomaly event: %s\n",
1193 machine_id = get_machine_id_by_seclevel(seclevel);
1194 if (machine_id == NULL) {
1196 fprintf(stderr, "Couldn't get the security "
1197 "level from the anomaly event.\n");
1202 for (it = events->tail; it; it = it->next) {
1203 struct event *event = it->data;
1204 if (event->success && machine_id->uuid && event->uuid &&
1205 strcmp(machine_id->uuid, event->uuid) == 0) {
1206 if (event->type == ET_STOP) {
1208 } else if (event->type == ET_START) {
1209 if (event->end == 0)
1217 if (start == NULL) {
1219 const char *text = auparse_get_record_text(au);
1220 fprintf(stderr, "Guest not found for "
1221 "anomaly record: %s.\n",
1227 if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL))
1230 anom = event_alloc();
1233 anom->type = ET_ANOM;
1234 anom->uuid = copy_str(start->uuid);
1235 anom->name = copy_str(start->name);
1239 memcpy(anom->proof, start->proof, sizeof(anom->proof));
1240 add_proof(anom, au);
1241 if (list_append(events, anom) == NULL) {
1248 int process_shutdown(auparse_state_t *au)
1256 if (extract_virt_fields(au, NULL, &uid, &time, NULL, &success))
1259 for (it = events->tail; it; it = it->prev) {
1260 struct event *event = it->data;
1261 if (event->success) {
1262 if (event->type == ET_START || event->type == ET_RES) {
1263 if (event->end == 0) {
1265 add_proof(event, au);
1267 } else if (event->type == ET_DOWN) {
1273 down = event_alloc();
1276 down->type = ET_DOWN;
1279 down->success = success;
1280 add_proof(down, au);
1281 if (list_append(events, down) == NULL) {
1288 /* Convert record type to a string */
1289 const char *get_rec_type(struct event *e)
1291 static char buf[64];
1310 snprintf(buf, sizeof(buf), "%d", e->type);
1314 /* Convert uid to a string */
1315 const char *get_username(struct event *e)
1318 if (!e || (int)e->uid == -1) {
1322 struct passwd *passwd = getpwuid(e->uid);
1323 if (passwd == NULL || passwd->pw_name == NULL) {
1324 snprintf(s, sizeof(s), "%d", e->uid);
1326 snprintf(s, sizeof(s), "%s", passwd->pw_name);
1332 /* Convert a time period to string */
1333 const char *get_time_period(struct event *event)
1336 static char buf[128];
1338 i += sprintf(buf + i, "%-16.16s", ctime(&event->start));
1340 time_t secs = event->end - event->start;
1341 int mins, hours, days;
1342 i += sprintf(buf + i, " - %-7.5s", ctime(&event->end) + 11);
1343 mins = (secs / 60) % 60;
1344 hours = (secs / 3600) % 24;
1345 days = secs / 86400;
1347 i += sprintf(buf + i, "(%d+%02d:%02d)", days, hours,
1350 i += sprintf(buf + i, "(%02d:%02d)", hours, mins);
1353 if (!event->success &&
1354 event->type != ET_AVC &&
1355 event->type != ET_ANOM) {
1356 i += sprintf(buf + i, " - failed");
1362 void print_event(struct event *event)
1364 /* Auxiliary macro to convert NULL to "" */
1365 #define N(str) ((str) ? str : "")
1367 /* machine id records are used just to get information about
1369 if (event->type == ET_MACHINE_ID)
1371 /* If "--all-events" is not given, only the start event is shown. */
1372 if (!all_events_flag && event->type != ET_START)
1374 /* The type of event is shown only when all records are shown */
1375 if (all_events_flag)
1376 printf("%-5.5s ", get_rec_type(event));
1378 /* Print common fields */
1379 printf("%-25.25s", N(event->name));
1381 printf("\t%-36.36s", N(event->uuid));
1382 printf("\t%-11.11s\t%-35.35s", get_username(event),
1383 get_time_period(event));
1385 /* Print type specific fields */
1386 if (event->type == ET_RES) {
1387 printf("\t%-12.12s", N(event->res_type));
1388 printf("\t%-10.10s", N(event->reason));
1389 if (strcmp("cgroup", event->res_type) != 0) {
1390 printf("\t%s", N(event->res));
1392 printf("\t%s\t%s\t%s", N(event->cgroup_class),
1393 N(event->cgroup_acl),
1394 N(event->cgroup_detail));
1396 } else if (event->type == ET_MACHINE_ID) {
1397 printf("\t%s", N(event->seclevel));
1398 } else if (event->type == ET_AVC) {
1399 printf("\t%-12.12s", N(event->avc_operation));
1400 printf("\t%-10.10s", N(event->avc_result));
1401 printf("\t%s\t%s\t%s", N(event->comm), N(event->target),
1409 int i, len = sizeof(event->proof)/sizeof(event->proof[0]);
1411 for (i = 0; i < len; i++) {
1412 if (event->proof[i].time) {
1413 printf("%s %ld.%03u:%lu",
1415 event->proof[i].time,
1416 event->proof[i].milli,
1417 event->proof[i].serial);
1425 /* Print all events */
1426 void print_events(void)
1429 for (it = events->head; it; it = it->next) {
1430 struct event *event = it->data;
1436 /* Count and print summary */
1437 void print_summary(void)
1439 /* Summary numbers */
1440 time_t start_time = 0, end_time = 0;
1441 long start = 0, stop = 0, res = 0, avc = 0, anom = 0,
1442 shutdown = 0, failure = 0;
1443 char start_buf[32], end_buf[32];
1445 /* Calculate summary */
1447 for (it = events->head; it; it = it->next) {
1448 struct event *event = it->data;
1449 if (event->success == 0 &&
1450 (event->type == ET_START ||
1451 event->type == ET_STOP ||
1452 event->type == ET_RES)) {
1455 switch (event->type) {
1477 /* Calculate time range */
1479 if (start_time == 0 || event->start < start_time) {
1480 start_time = event->start;
1482 if (end_time == 0 || event->start > end_time) {
1483 end_time = event->start;
1487 if (start_time == 0 || event->end < start_time) {
1488 start_time = event->end;
1490 if (end_time == 0 || event->end > end_time) {
1491 end_time = event->end;
1498 ctime_r(&start_time, start_buf);
1500 strcpy(start_buf, "undef");
1502 ctime_r(&end_time, end_buf);
1504 strcpy(end_buf, "undef");
1507 printf("Range of time for report: %-.16s - %-.16s\n",
1508 start_buf, end_buf);
1509 printf("Number of guest starts: %ld\n", start);
1510 printf("Number of guest stops: %ld\n", stop);
1511 printf("Number of resource assignments: %ld\n", res);
1512 printf("Number of related AVCs: %ld\n", avc);
1513 printf("Number of related anomalies: %ld\n", anom);
1514 printf("Number of host shutdowns: %ld\n", shutdown);
1515 printf("Number of failed operations: %ld\n", failure);
1518 int main(int argc, char **argv)
1521 auparse_state_t *au = NULL;
1523 setlocale(LC_ALL, "");
1524 if (parse_args(argc, argv))
1531 /* Initialize event list*/
1532 events = list_new((list_free_data_fn*) event_free);
1534 goto unexpected_error;
1536 /* Initialize auparse */
1537 au = init_auparse();
1540 if (create_search_criteria(au))
1543 while (ausearch_next_event(au) > 0) {
1546 switch(auparse_get_type(au)) {
1547 case AUDIT_VIRT_MACHINE_ID:
1548 err = process_machine_id_event(au);
1550 case AUDIT_VIRT_CONTROL:
1551 err = process_control_event(au);
1553 case AUDIT_VIRT_RESOURCE:
1554 err = process_resource_event(au);
1557 err = process_avc(au);
1559 case AUDIT_FIRST_ANOM_MSG ... AUDIT_LAST_ANOM_MSG:
1560 case AUDIT_FIRST_KERN_ANOM_MSG ... AUDIT_LAST_KERN_ANOM_MSG:
1561 err = process_anom(au);
1563 case AUDIT_SYSTEM_SHUTDOWN:
1564 err = process_shutdown(au);
1568 goto unexpected_error;
1570 auparse_next_event(au);
1584 fprintf(stderr, "Unexpected error\n");
1589 auparse_destroy(au);
1592 fprintf(stdout, "Exit code: %d\n", rc);