public inbox for linux-audit@redhat.com
 help / color / mirror / Atom feed
* [PATCH] auvirt: a new tool for reporting events related to virtual machines
@ 2011-12-15 15:56 Marcelo Cerri
  2011-12-20 18:18 ` Steve Grubb
  0 siblings, 1 reply; 21+ messages in thread
From: Marcelo Cerri @ 2011-12-15 15:56 UTC (permalink / raw)
  To: linux-audit; +Cc: gcwilson, bryntcor

This patch adds a new tool to extract information related to virtual machines
from the audit log files. It can output a summary with information about the
number of events found with details by type of record and operation. The tool
can also output the filtered records as found in the audit log.

Using the --avc option auvirt tries to correlate AVC records to the guests
based on its security context. It's also possible to select records related to
just one guest using the UUID or the guest name.

This tool is based on the proposal sent in the RFC:

https://www.redhat.com/archives/linux-audit/2011-November/msg00014.html

Signed-off-by: Marcelo Cerri <mhcerri@linux.vnet.ibm.com>
---
 audit.spec                      |    2 +
 configure.ac                    |    2 +-
 tools/Makefile.am               |    2 +-
 tools/auvirt/Makefile.am        |   34 +++
 tools/auvirt/auvirt-map-llist.c |  181 +++++++++++++
 tools/auvirt/auvirt-map.h       |   50 ++++
 tools/auvirt/auvirt.8           |  107 ++++++++
 tools/auvirt/auvirt.c           |  541 +++++++++++++++++++++++++++++++++++++++
 8 files changed, 917 insertions(+), 2 deletions(-)
 create mode 100644 tools/auvirt/Makefile.am
 create mode 100644 tools/auvirt/auvirt-map-llist.c
 create mode 100644 tools/auvirt/auvirt-map.h
 create mode 100644 tools/auvirt/auvirt.8
 create mode 100644 tools/auvirt/auvirt.c

diff --git a/audit.spec b/audit.spec
index 383bed1..a9940b4 100644
--- a/audit.spec
+++ b/audit.spec
@@ -172,6 +172,7 @@ fi
 %attr(644,root,root) %{_mandir}/man8/autrace.8.gz
 %attr(644,root,root) %{_mandir}/man8/aulast.8.gz
 %attr(644,root,root) %{_mandir}/man8/aulastlog.8.gz
+%attr(644,root,root) %{_mandir}/man8/auvirt.8.gz
 %attr(644,root,root) %{_mandir}/man8/ausyscall.8.gz
 %attr(644,root,root) %{_mandir}/man7/audit.rules.7.gz
 %attr(644,root,root) %{_mandir}/man5/auditd.conf.5.gz
@@ -186,6 +187,7 @@ fi
 %attr(755,root,root) %{_bindir}/aulast
 %attr(755,root,root) %{_bindir}/aulastlog
 %attr(755,root,root) %{_bindir}/ausyscall
+%attr(755,root,root) %{_bindir}/auvirt
 %attr(755,root,root) /etc/rc.d/init.d/auditd
 %attr(750,root,root) %dir %{_var}/log/audit
 %attr(750,root,root) %dir /etc/audit
diff --git a/configure.ac b/configure.ac
index c1cf96f..b7f7fcd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -255,7 +255,7 @@ AC_SUBST(libev_LIBS)
 AC_SUBST(LIBPRELUDE_CFLAGS)
 AC_SUBST(LIBPRELUDE_LDFLAGS)
 
-AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile)
+AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile tools/auvirt/Makefile)
 
 echo .
 echo "
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 3b7acfe..15ba254 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -22,5 +22,5 @@
 
 CONFIG_CLEAN_FILES = *.loT *.rej *.orig
 
-SUBDIRS = aulast aulastlog ausyscall
+SUBDIRS = aulast aulastlog ausyscall auvirt
 
diff --git a/tools/auvirt/Makefile.am b/tools/auvirt/Makefile.am
new file mode 100644
index 0000000..4ee2ca7
--- /dev/null
+++ b/tools/auvirt/Makefile.am
@@ -0,0 +1,34 @@
+#
+# Makefile.am --
+# 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 <mhcerri@br.ibm.com>
+#
+ 
+CONFIG_CLEAN_FILES = *.loT *.rej *.orig
+AUTOMAKE_OPTIONS = no-dependencies
+EXTRA_DIST = $(man_MANS)
+INCLUDES = -I${top_srcdir} -I${top_srcdir}/lib -I${top_srcdir}/auparse -I${top_srcdir}/src
+LIBS = -L${top_builddir}/auparse -lauparse
+AM_CFLAGS = -D_GNU_SOURCE
+bin_PROGRAMS = auvirt
+noinst_HEADERS = auvirt-map.h
+man_MANS = auvirt.8
+
+auvirt_SOURCES = auvirt.c auvirt-map-llist.c ${top_srcdir}/src/ausearch-time.c
diff --git a/tools/auvirt/auvirt-map-llist.c b/tools/auvirt/auvirt-map-llist.c
new file mode 100644
index 0000000..af55a41
--- /dev/null
+++ b/tools/auvirt/auvirt-map-llist.c
@@ -0,0 +1,181 @@
+/*
+ * auvirt-map-llist.c --
+ * 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 <mhcerri@br.ibm.com>
+ */
+
+
+#include "auvirt-map.h"
+#include <stdlib.h>
+#include <string.h>
+
+struct _map_t {
+	map_it_t *head;
+	map_it_t *tail;
+};
+
+struct _map_it_t{
+	char *key;
+	char *val;
+	map_it_t *prev;
+	map_it_t *next;
+};
+
+map_t *map_new()
+{
+	map_t *m = malloc(sizeof(map_t));
+	if (m == NULL)
+		return NULL;
+	m->head = NULL;
+	m->tail = NULL;
+	return m;
+}
+
+void free_node(map_it_t *it)
+{
+	if (it) {
+		free(it->key);
+		free(it->val);
+		free(it);
+	}
+}
+
+void map_free(map_t *m)
+{
+	if (m != NULL) {
+		map_it_t *it = m->head;
+		while (it && it->next) {
+			it = it->next;
+			free_node(it->prev);
+		}
+		free_node(it);
+		free(m);
+	}
+}
+
+int map_add(map_t *m, const char *k, const char *v)
+{
+	map_it_t *it = NULL;
+	if (m == NULL || k == NULL || v == NULL || map_get(m, k) != NULL)
+		goto error;
+
+	it = malloc(sizeof(map_it_t));
+	if (it == NULL)
+		goto error;
+	memset(it, 0, sizeof(map_it_t));
+	it->key = strdup(k);
+	if (it->key == NULL)
+		goto error;
+	it->val = strdup(v);
+	if (it->val == NULL)
+		goto error;
+
+	if (m->tail == NULL) {
+		m->head = m->tail = it;
+		return 0;
+	}
+
+	it->prev = m->tail;
+	m->tail->next = it;
+	m->tail = it;
+	return 0;
+
+error:
+	free_node(it);
+	return 1;
+}
+
+int map_set(map_t *m, map_it_t *it, const char *v)
+{
+	char *str = NULL;
+	if (m == NULL || it == NULL || v == NULL)
+		return 1;
+	if (v) {
+		str = strdup(v);
+		if (str == NULL)
+			return 1;
+	}
+	free(it->val);
+	it->val = str;
+	return 0;
+}
+
+int map_del(map_t *m, map_it_t *it)
+{
+	if (m == NULL || it == NULL)
+		return 1;
+	if (m->head == it)
+		m->head = it->next;
+	if (m->tail == it)
+		m->tail = it->prev;
+	if (it->next)
+		it->next->prev = it->prev;
+	if (it->prev)
+		it->prev->next = it->next;
+	free_node(it);
+	return 0;
+}
+
+map_it_t *map_get(map_t *m, const char *k)
+{
+	map_it_t *it = NULL;
+	if (m == NULL || k == NULL)
+		return NULL;
+	for (it = m->head; it; it = it->next)
+		if (strcmp(it->key, k) == 0)
+			return it;
+	return NULL;
+}
+
+map_it_t *map_next(const map_t *m, map_it_t *last)
+{
+	if (m == NULL)
+		return NULL;
+	if (last == NULL)
+		return m->head;
+	return last->next;
+}
+
+map_it_t *map_next_val(const map_t *m, const char *v, map_it_t *last)
+{
+	map_it_t *it = NULL;
+	if (m == NULL)
+		return NULL;
+	it = (last) ? last->next : m->head;
+	for (; it; it = it->next)
+		if (strcmp(it->val, v) == 0)
+			return it;
+	return NULL;
+}
+
+const char *map_it_key(const map_it_t *it)
+{
+	if (it)
+		return it->key;
+	return NULL;
+}
+
+const char *map_it_val(const map_it_t *it)
+{
+	if (it)
+		return it->val;
+	return NULL;
+}
+
diff --git a/tools/auvirt/auvirt-map.h b/tools/auvirt/auvirt-map.h
new file mode 100644
index 0000000..92fdb1a
--- /dev/null
+++ b/tools/auvirt/auvirt-map.h
@@ -0,0 +1,50 @@
+/*
+ * auvirt-map.h --
+ * 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 <mhcerri@br.ibm.com>
+ */
+
+
+#ifndef AUVIRTMAP_HEADER
+#define AUVIRTMAP_HEADER
+
+struct _map_t;
+typedef struct _map_t map_t;
+struct _map_it_t;
+typedef struct _map_it_t map_it_t;
+
+map_t *map_new();
+void map_free(map_t *m);
+int map_add(map_t *m, const char *k, const char *v);
+int map_set(map_t *m, map_it_t *it, const char *v);
+int map_del(map_t *m, map_it_t *it);
+map_it_t *map_get(map_t *m, const char *k);
+map_it_t *map_next(const map_t *m, map_it_t *last);
+map_it_t *map_next_val(const map_t *m, const char *v, map_it_t *last);
+const char *map_it_key(const map_it_t *it);
+const char *map_it_val(const map_it_t *it);
+
+static inline const char *map_get_val(map_t *m, const char *k)
+{
+	return map_it_val(map_get(m, k));
+}
+
+#endif
+
diff --git a/tools/auvirt/auvirt.8 b/tools/auvirt/auvirt.8
new file mode 100644
index 0000000..1fc629b
--- /dev/null
+++ b/tools/auvirt/auvirt.8
@@ -0,0 +1,107 @@
+.TH AUVIRT 8 "Dec 2011" "IBM Corp" "System Administration Utilities"
+.SH NAME
+auvirt - a program that reports data related to virtual machines
+
+.SH SYNOPSIS
+.B auvirt
+[ \fIOPTIONS\fP ]
+[ \fIUUID\fP | \fIVM-NAME\fP ]
+
+.SH DESCRIPTION
+\fBauvirt\fP is a program that prints a summary of all virtualization related
+events found in the audit logs. If a guest is specified, only the events
+related to that guest is considered. To specify a guest, both UUID or VM name
+can be given.
+
+The summary contains the number of virtualization related events
+found, the number of events by type (AVC, machine id, control and resource),
+and the number of start, stop and suspend operations found.
+
+By default, auvirt reads records from the system audit log file. But
+\fB--stdin\fP and \fB--file\fP options can be specified to change this
+behavior.
+
+.SH OPTIONS
+.TP
+\fB-a\fP, \fB--avc\fP
+When this option is given the tool tries to correlate AVC records to a
+guest based on the security context. As the security context can be
+automatically changed on guest's start, the tool just correlates an AVC record
+to a guest if the record was generated during the period time that guest was
+running. This option causes auvirt to count AVC records and show the result in
+the summary. If \fB--raw\fP is specified together with this option, the AVC
+records considered
+will be printed.
+.TP
+\fB--debug\fP
+Print debug messages to standard output.
+.TP
+\fB-f\fP, \fB--file\fP \fIfile\fP
+Reads records from the given \fIfile\fP instead from the system audit log file.
+.TP
+\fB-h\fP, \fB--help\fP
+Print help message and exit.
+.TP
+\fB--raw\fP
+Print to standard output the filtered records as shown in the audit log.
+.TP
+\fB--stdin\fP
+Reads records from the standard input instead from the system audit log file.
+This option cannot be informed with \fB--file\fP.
+.TP
+\fB--summary\fP
+Prints a summary with information about the records found. This is the default
+behavior.
+.TP
+.BR \-te ,\  \-\-end \ [\fIend-date\fP]\ [\fIend-time\fP]
+Search for events with time stamps equal to or before the given end time. The
+format of end time depends on your locale. If the date is omitted,
+.B today
+is assumed. If the time is omitted,
+.B now
+is assumed. Use 24 hour clock time rather than AM or PM to specify time.
+An example date using the en_US.utf8 locale is 09/03/2009. An example of time
+is 18:00:00. The date format accepted is influenced by the LC_TIME
+environmental variable.
+
+You may also use the word: \fBnow\fP, \fBrecent\fP, \fBtoday\fP,
+\fByesterday\fP, \fBthis\-week\fP, \fBweek\-ago\fP, \fBthis\-month\fP,
+\fBthis\-year\fP. \fBToday\fP means starting now. \fBRecent\fP is 10 minutes
+ago. \fBYesterday\fP is 1 second after midnight the previous day.
+\fBThis\-week\fP means starting 1 second after midnight on day 0 of the week
+determined by your locale (see \fBlocaltime\fP). \fBThis\-month\fP means 1
+second after midnight on day 1 of the month. \fBThis\-year\fP means the 1
+second after midnight on the first day of the first month.
+.TP
+.BR \-ts ,\  \-\-start \ [\fIstart-date\fP]\ [\fIstart-time\fP]
+Search for events with time stamps equal to or after the given end time. The
+format of end time depends on your locale. If the date is omitted,
+.B today
+is assumed. If the time is omitted,
+.B midnight
+is assumed. Use 24 hour clock time rather than AM or PM to specify time. An
+example date using the en_US.utf8 locale is 09/03/2009. An example of time is
+18:00:00. The date format accepted is influenced by the LC_TIME environmental
+variable.
+
+You may also use the word: \fBnow\fP, \fBrecent\fP, \fBtoday\fP,
+\fByesterday\fP, \fBthis\-week\fP, \fBthis\-month\fP, \fBthis\-year\fP.
+\fBToday\fP means starting at 1 second after midnight. \fBRecent\fP is 10
+minutes ago. \fBYesterday\fP is 1 second after midnight the previous day.
+\fBThis\-week\fP means starting 1 second after midnight on day 0 of the week
+determined by your locale (see \fBlocaltime\fP). \fBThis\-month\fP means 1
+second after midnight on day 1 of the month. \fBThis\-year\fP means the 1
+second after midnight on the first day of the first month.
+
+.SH EXAMPLES
+To see all the records in this month for a guest
+
+\fBauvirt --raw --avc --start this-month GuestVmName\fP
+
+.SH SEE ALSO
+.BR aulast (8),
+.BR ausearch (8),
+.BR aureport (8).
+
+.SH AUTHOR
+Marcelo Cerri
diff --git a/tools/auvirt/auvirt.c b/tools/auvirt/auvirt.c
new file mode 100644
index 0000000..3e5b630
--- /dev/null
+++ b/tools/auvirt/auvirt.c
@@ -0,0 +1,541 @@
+/*
+ * 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 <mhcerri@br.ibm.com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <locale.h>
+#include <string.h>
+#include <regex.h>
+#include <time.h>
+#include "auparse.h"
+#include "libaudit.h"
+#include "auvirt-map.h"
+#include "ausearch-time.h"
+
+/* command line parameters */
+static int help_flag = 0;
+static int stdin_flag = 0;
+static int raw_flag = 0;
+static int summary_flag = 0;
+static int avc_flag = 0;
+/* id stores the given UUID or VM name. */
+static const char *id = NULL;
+static const char *file = NULL;
+static enum { UUID=0, VMNAME } id_type;
+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;
+ */
+
+/* state variables */
+static map_t *seclevel_map = NULL;
+static struct {
+	long n_records;
+	long n_virt_records;
+	long n_res_records;
+	long n_mach_id_records;
+	long n_avc_records;
+	long n_errors;
+	time_t start_time;
+	time_t end_time;
+	struct {
+		long n_start;
+		long n_stop;
+	} by_type;
+} summary =  { 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, { 0L, 0L } };
+
+char *rstrip(char *s)
+{
+	size_t i;
+	if (s) {
+		for (i = 0; s[i] && s[i + 1]; i++);
+		for (; isspace(s[i]); i--) {
+			s[i] = 0;
+			if (i == 0)
+				break;
+		}
+	}
+	return s;
+}
+
+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 *arg = argv[i];
+		if (arg[0] != '-') {
+			/* parse UUID or VM name */
+			if (id != NULL) {
+				fprintf(stderr, "Only one UUID or VM name "
+						"must be specified.\n");
+				goto error;
+			}
+			if (regexec(&uuid_regex, arg, 0, NULL, 0) == 0) {
+				id_type = UUID;
+			} else {
+				id_type = VMNAME;
+			}
+			id = arg;
+		} else if (strcmp("--raw", arg) == 0 ||
+			   strcmp("-r", arg) == 0) {
+			raw_flag = 1;
+		} else if (strcmp("--summary", arg) == 0 ||
+			   strcmp("-s", arg) == 0) {
+			summary_flag = 1;
+		} else if (strcmp("--file", arg) == 0 ||
+			   strcmp("-f", arg) == 0) {
+			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
+				fprintf(stderr, "\"%s\" option requires "
+						"an argument.\n", arg);
+				goto error;
+			}
+			file = argv[++i];
+		} else if (strcmp("--stdin", arg) == 0) {
+			stdin_flag = 1;
+		} else if (strcmp("--help", arg) == 0 ||
+			   strcmp("-h", arg) == 0) {
+			help_flag = 1;
+			goto exit;
+		} else if (strcmp("--start", arg) == 0 ||
+			   strcmp("-ts", arg) == 0) {
+			const char *date, *time = NULL;
+			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
+				fprintf(stderr, "\"%s\" option requires at "
+						"least one argument.\n", arg);
+				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", arg) == 0 ||
+			   strcmp("-te", arg) == 0) {
+			const char *date, *time = NULL;
+			if ((i + 1) >= argc || argv[i + 1][0] == '-') {
+				fprintf(stderr, "\"%s\" option requires at "
+						"least one argument.\n", arg);
+				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("--avc", arg) == 0 ||
+			   strcmp("-a", arg) == 0) {
+			avc_flag = 1;
+		} else if (strcmp("--debug", arg) == 0) {
+			debug = 1;
+		} else {
+			fprintf(stderr, "Unknown option \"%s\".\n", arg);
+			goto error;
+		}
+	}
+
+	/* validate conflicting options */
+	if (stdin_flag && file) {
+		fprintf(stderr, "\"--sdtin\" and \"--file\" options "
+				"must not be specified together.\n");
+		goto error;
+	}
+	if (raw_flag && summary_flag) {
+		fprintf(stderr, "\"--raw\" and \"--summary\" options "
+				"must not be specified together.\n");
+		goto error;
+	}
+
+	/* summary is default */
+	if (!raw_flag && !summary_flag)
+		summary_flag = 1;
+
+	if (debug) {
+		fprintf(stdout, "help_flag='%i'\n", help_flag);
+		fprintf(stdout, "stdin_flag='%i'\n", stdin_flag);
+		fprintf(stdout, "raw_flag='%i'\n", raw_flag);
+		fprintf(stdout, "summary_flag='%i'\n", summary_flag);
+		fprintf(stdout, "avc_flag='%i'\n", avc_flag);
+		fprintf(stdout, "id='%s'\n", id);
+		fprintf(stdout, "file='%s'\n", file );
+		fprintf(stdout, "id_type='%i'\n", id_type);
+		fprintf(stdout, "start_time='%s'\n", (start_time == 0L) ?
+				"" : rstrip(ctime(&start_time)));
+		fprintf(stdout, "end_time='%s'\n", (end_time == 0L) ?
+				"" : rstrip(ctime(&end_time)));
+	}
+
+exit:
+	regfree(&uuid_regex);
+	return rc;
+error:
+	rc = 1;
+	goto exit;
+}
+
+auparse_state_t *init_auparse()
+{
+	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];
+	sprintf(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 (id) {
+		char tmp[1024];
+		char *field_name = (id_type == UUID) ? "uuid" : "vm";
+		/*
+		 * If a field has its value quoted in the audit log, for
+		 * example:
+		 *	vm="guest-name"
+		 *
+		 * auparse will consider the field value with quotes when
+		 * matching a rule. For example, using the example above the
+		 * following rule will not match:
+		 *	ausearch_add_item(au, "vm", "=", "guest-name", how);
+		 *
+		 * But this rule will match:
+		 *	ausearch_add_item(au, "vm", "=", "\"guest-name\", how);
+		 *
+		 * TODO use a better approach for this problem...
+		 */
+		if (id_type == VMNAME)
+			snprintf(tmp, sizeof(tmp), "\"%s\"", id);
+		if (ausearch_add_item(au, field_name, "=", tmp,
+					AUSEARCH_RULE_AND)) {
+			fprintf(stderr, "Criteria error: id\n");
+			return 1;
+		}
+	}
+	if (avc_flag) {
+		if (ausearch_add_item(au, "type", "=", "AVC",
+					AUSEARCH_RULE_OR)) {
+			fprintf(stderr, "Criteria error: AVC\n");
+			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;
+}
+
+void usage(FILE *output)
+{
+	fprintf(output, "usage: auvirt [--stdin] [--avc] [--raw|--summary] "
+			"[--start start-date [start-time]] "
+			"[--end end-date [end-time]] [--file file-name] "
+			"[uuid|vm-name]\n");
+}
+
+/* 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;
+}
+
+/* keep track of which security level is associated to each uuid */
+int update_seclevel(auparse_state_t *au)
+{
+	const char *uuid, *seclabel, *seclevel;
+
+	uuid = auparse_find_field(au, "uuid");
+	if (uuid == NULL) {
+		if (debug)
+			fprintf(stdout, "Warning: record without UUID\n");
+		return 0;
+	}
+
+	seclabel = auparse_find_field(au, "vm-ctx");
+	seclevel = get_seclevel(seclabel);
+	if (seclevel == NULL) {
+		if (debug)
+			fprintf(stdout, "Invalid security level for label: "
+					"'%s'.\n", seclabel);
+		return 0;
+	}
+	if (debug)
+		fprintf(stdout, "Using seclevel '%s' for '%s'\n",
+				seclevel, uuid);
+
+	map_it_t *it = map_get(seclevel_map, uuid);
+	if (it) {
+		if (debug)
+			fprintf(stdout, "Warning: overwriting seclevel for id "
+					"'%s'\n", uuid);
+		return map_set(seclevel_map, it, seclevel);
+	}
+	return map_add(seclevel_map, uuid, seclevel);
+}
+
+/* check and discard sec level associated to a stopped guest. */
+int remove_seclevel(auparse_state_t *au)
+{
+	const char *uuid, *op;
+
+	uuid = auparse_find_field(au, "uuid");
+	if (uuid == NULL) {
+		if (debug)
+			fprintf(stdout, "Warning: record without UUID\n");
+		return 0;
+	}
+
+	auparse_first_field(au);
+	op = auparse_find_field(au, "op");
+	if (op == NULL) {
+		if (debug)
+			fprintf(stdout, "Warning: record without 'op' "
+					"field\n");
+		return 0;
+	}
+	if (strcmp("stop", op) == 0) {
+		map_it_t *it = map_get(seclevel_map, uuid);
+		if (it) {
+			if (debug)
+				fprintf(stdout, "Discarding seclevel '%s'\n",
+						map_it_val(it));
+			map_del(seclevel_map, it);
+		} else if (debug)
+			fprintf(stdout, "No seclevel to discard\n");
+	}
+	return 0;
+}
+
+void process_record(auparse_state_t *au)
+{
+	if (raw_flag)
+		printf("%s\n", auparse_get_record_text(au));
+
+	if (summary_flag) {
+		const char *op;
+		int type = auparse_get_type(au);
+		switch (type) {
+		case AUDIT_FIRST_VIRT_MSG ... AUDIT_LAST_VIRT_MSG:
+			summary.n_virt_records += 1;
+			/* update time period from records if not informed */
+			time_t t = auparse_get_time(au);
+			if (summary.start_time == 0L ||
+			    summary.start_time > t) {
+				summary.start_time = t;
+			}
+			if (summary.end_time == 0L ||
+			    summary.end_time < t) {
+				summary.end_time = t;
+			}
+			/* count start and stop */
+			switch (type) {
+			case AUDIT_VIRT_RESOURCE:
+				summary.n_res_records += 1;
+				break;
+			case AUDIT_VIRT_MACHINE_ID:
+				summary.n_mach_id_records += 1;
+				break;
+			case AUDIT_VIRT_CONTROL:
+				op = auparse_find_field(au, "op");
+				if (strcmp("start", op) == 0)
+					summary.by_type.n_start += 1;
+				else if (strcmp("stop", op) == 0)
+					summary.by_type.n_stop += 1;
+				break;
+			}
+			break;
+		case AUDIT_AVC:
+			summary.n_avc_records += 1;
+			break;
+		}
+		summary.n_records += 1;
+	}
+}
+
+void print_summary()
+{
+	time_t time = 0;
+	printf("Total records:      %ld\n", summary.n_records);
+	printf("Virt records:       %ld\n", summary.n_virt_records);
+	printf("Resource records:   %ld\n", summary.n_res_records);
+	printf("Machine ID records: %ld\n", summary.n_mach_id_records);
+	printf("AVC records:        %ld\n", summary.n_avc_records);
+	printf("Operations:\n");
+	printf("  Start:            %ld\n", summary.by_type.n_start);
+	printf("  Stop:             %ld\n", summary.by_type.n_stop);
+	printf("Considered time:\n");
+	time = (start_time) ? start_time : summary.start_time;
+	printf("  Start:            %s\n", (time) ? rstrip(ctime(&time)) : "-");
+	time = (end_time) ? end_time : summary.end_time;
+	printf("  End:              %s\n", (time) ? rstrip(ctime(&time)) : "-");
+}
+
+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 auparse */
+	au = init_auparse();
+	if (au == NULL)
+		goto error;
+	if (create_search_criteria(au))
+		goto error;
+
+	if (avc_flag) {
+		seclevel_map = map_new();
+		if (seclevel_map == NULL)
+			goto unexpected_error;
+	}
+
+	while (ausearch_next_event(au) > 0) {
+		const char *seclabel = NULL, *seclevel = NULL;
+		int type = auparse_get_type(au);
+		switch (type) {
+		case AUDIT_FIRST_VIRT_MSG ... AUDIT_LAST_VIRT_MSG:
+			/* when the --avc option is given the tool will keep
+			 * track of the security label associated to each guest
+			 * to be able to filter the AVC records. */
+			if (avc_flag) {
+				if (type == AUDIT_VIRT_MACHINE_ID) {
+					/* When a start event is read, an entry
+					 * is added or update in a map for that
+					 * guest. */
+					if (update_seclevel(au))
+						goto unexpected_error;
+				} else if (type == AUDIT_VIRT_CONTROL) {
+					/* When a stop event is read, the entry
+					 * associated to that guest is removed
+					 * from the map. */
+					if (remove_seclevel(au))
+						goto unexpected_error;
+				}
+			}
+			process_record(au);
+			break;
+		case AUDIT_AVC:
+			/* process AVC records with security level used by
+			 * a known guest. */
+			if (avc_flag) {
+				seclabel = auparse_find_field(au, "tcontext");
+				seclevel = get_seclevel(seclabel);
+			}
+			if (seclevel) {
+				if (map_next_val(seclevel_map, seclevel, NULL))
+					process_record(au);
+			}
+			break;
+		}
+		auparse_next_event(au);
+	}
+
+	if (summary_flag)
+		print_summary();
+
+exit:
+	if (au)
+		auparse_destroy(au);
+	map_free(seclevel_map);
+	seclevel_map = NULL;
+	if (debug)
+		fprintf(stdout, "Exit code: %d\n", rc);
+	return rc;
+
+unexpected_error:
+	fprintf(stderr, "Unexpected error\n");
+error:
+	rc = 1;
+	goto exit;
+}
+
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 21+ messages in thread
* [PATCH] auvirt: a new tool for reporting events related to virtual machines
@ 2012-02-01 17:16 Marcelo Cerri
  2012-02-03 18:52 ` Steve Grubb
  0 siblings, 1 reply; 21+ messages in thread
From: Marcelo Cerri @ 2012-02-01 17:16 UTC (permalink / raw)
  To: linux-audit; +Cc: gcwilson, bryntcor

This patch adds a new tool to extract information related to virtual machines
from the audit log files.

This tool is based on the proposal sent in the RFC:

https://www.redhat.com/archives/linux-audit/2011-November/msg00014.html
---
 audit.spec                 |    2 +
 configure.ac               |    2 +-
 tools/Makefile.am          |    2 +-
 tools/auvirt/Makefile.am   |   40 ++
 tools/auvirt/auvirt-list.c |  105 ++++
 tools/auvirt/auvirt-list.h |   31 +
 tools/auvirt/auvirt.8      |  121 ++++
 tools/auvirt/auvirt.c      | 1308 ++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 1609 insertions(+), 2 deletions(-)
 create mode 100644 tools/auvirt/Makefile.am
 create mode 100644 tools/auvirt/auvirt-list.c
 create mode 100644 tools/auvirt/auvirt-list.h
 create mode 100644 tools/auvirt/auvirt.8
 create mode 100644 tools/auvirt/auvirt.c

diff --git a/audit.spec b/audit.spec
index 383bed1..a9940b4 100644
--- a/audit.spec
+++ b/audit.spec
@@ -172,6 +172,7 @@ fi
 %attr(644,root,root) %{_mandir}/man8/autrace.8.gz
 %attr(644,root,root) %{_mandir}/man8/aulast.8.gz
 %attr(644,root,root) %{_mandir}/man8/aulastlog.8.gz
+%attr(644,root,root) %{_mandir}/man8/auvirt.8.gz
 %attr(644,root,root) %{_mandir}/man8/ausyscall.8.gz
 %attr(644,root,root) %{_mandir}/man7/audit.rules.7.gz
 %attr(644,root,root) %{_mandir}/man5/auditd.conf.5.gz
@@ -186,6 +187,7 @@ fi
 %attr(755,root,root) %{_bindir}/aulast
 %attr(755,root,root) %{_bindir}/aulastlog
 %attr(755,root,root) %{_bindir}/ausyscall
+%attr(755,root,root) %{_bindir}/auvirt
 %attr(755,root,root) /etc/rc.d/init.d/auditd
 %attr(750,root,root) %dir %{_var}/log/audit
 %attr(750,root,root) %dir /etc/audit
diff --git a/configure.ac b/configure.ac
index c1cf96f..b7f7fcd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -255,7 +255,7 @@ AC_SUBST(libev_LIBS)
 AC_SUBST(LIBPRELUDE_CFLAGS)
 AC_SUBST(LIBPRELUDE_LDFLAGS)
 
-AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile)
+AC_OUTPUT(Makefile lib/Makefile lib/test/Makefile auparse/Makefile auparse/test/Makefile src/Makefile src/mt/Makefile src/libev/Makefile src/test/Makefile swig/Makefile docs/Makefile init.d/Makefile audisp/Makefile audisp/plugins/Makefile audisp/plugins/builtins/Makefile audisp/plugins/prelude/Makefile audisp/plugins/remote/Makefile audisp/plugins/zos-remote/Makefile bindings/Makefile bindings/python/Makefile tools/Makefile tools/aulast/Makefile tools/aulastlog/Makefile tools/ausyscall/Makefile tools/auvirt/Makefile)
 
 echo .
 echo "
diff --git a/tools/Makefile.am b/tools/Makefile.am
index 3b7acfe..15ba254 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -22,5 +22,5 @@
 
 CONFIG_CLEAN_FILES = *.loT *.rej *.orig
 
-SUBDIRS = aulast aulastlog ausyscall
+SUBDIRS = aulast aulastlog ausyscall auvirt
 
diff --git a/tools/auvirt/Makefile.am b/tools/auvirt/Makefile.am
new file mode 100644
index 0000000..0e9ec5b
--- /dev/null
+++ b/tools/auvirt/Makefile.am
@@ -0,0 +1,40 @@
+#
+# Makefile.am --
+# 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 <mhcerri@br.ibm.com>
+#
+ 
+CONFIG_CLEAN_FILES = *.loT *.rej *.orig
+AUTOMAKE_OPTIONS = no-dependencies
+EXTRA_DIST = $(man_MANS)
+INCLUDES = -I${top_srcdir} \
+		   -I${top_srcdir}/lib \
+		   -I${top_srcdir}/auparse \
+		   -I${top_srcdir}/src
+LIBS = -L${top_builddir}/auparse \
+	   -lauparse
+AM_CFLAGS = -D_GNU_SOURCE
+bin_PROGRAMS = auvirt
+noinst_HEADERS = auvirt-list.h
+man_MANS = auvirt.8
+
+auvirt_SOURCES = auvirt.c \
+				 auvirt-list.c \
+				 ${top_srcdir}/src/ausearch-time.c
diff --git a/tools/auvirt/auvirt-list.c b/tools/auvirt/auvirt-list.c
new file mode 100644
index 0000000..7502188
--- /dev/null
+++ b/tools/auvirt/auvirt-list.c
@@ -0,0 +1,105 @@
+#include "auvirt-list.h"
+#include <stdlib.h>
+
+list_t *list_init(list_t *list, list_free_data_fn *free_data_fn)
+{
+	if (list == NULL)
+		return NULL;
+	list->head = list->tail = NULL;
+	list->free_data_fn = free_data_fn;
+	return list;
+}
+
+list_t *list_new(list_free_data_fn *free_data_fn)
+{
+	return list_init(malloc(sizeof(list_t)), free_data_fn);
+}
+
+void list_free_node(list_node_t *node, list_free_data_fn *free_data_fn)
+{
+	if (node) {
+		if (free_data_fn)
+			free_data_fn(node->data);
+		free(node);
+	}
+}
+
+void list_free_(list_t *list, list_free_data_fn *free_data_fn)
+{
+	if (list != NULL) {
+		list_node_t *it = list->head;
+		while (it && it->next) {
+			it = it->next;
+			list_free_node(it->prev, free_data_fn);
+		}
+		list_free_node(it, free_data_fn);
+		free(list);
+	}
+}
+
+void list_free(list_t *list)
+{
+	if (list)
+		list_free_(list, list->free_data_fn);
+}
+
+list_node_t *list_insert_after(list_t *list, list_node_t *it,
+		void *data)
+{
+	list_node_t *node = NULL;
+	if (list == NULL)
+		return NULL;
+
+	/* allocate node */
+	node = malloc(sizeof(list_node_t));
+	if (node == NULL)
+		return NULL;
+	node->data = data;
+
+	/* insert the new node after it */
+	node->prev = it;
+	if (it) {
+		node->next = it->next;
+		it->next = node;
+	}
+	else
+		node->next = list->head;
+	if (node->next)
+		node->next->prev = node;
+
+	/* update list's head and tail */
+	if (it == list->tail)
+		list->tail = node;
+	if (it == NULL)
+		list->head = node;
+
+	return node;
+}
+
+list_node_t *list_append(list_t *list, void *data)
+{
+	return list_insert_after(list, list->tail, data);
+}
+
+int list_remove_(list_t *list, list_node_t *it,
+		list_free_data_fn *free_data_fn)
+{
+	if (list == NULL || it == NULL)
+		return 1;
+	if (list->head == it)
+		list->head = it->next;
+	if (list->tail == it)
+		list->tail = it->prev;
+	if (it->next)
+		it->next->prev = it->prev;
+	if (it->prev)
+		it->prev->next = it->next;
+	list_free_node(it, free_data_fn);
+	return 0;
+}
+
+int list_remove(list_t *list, list_node_t *it)
+{
+	return list_remove_(list, it, list->free_data_fn);
+}
+
diff --git a/tools/auvirt/auvirt-list.h b/tools/auvirt/auvirt-list.h
new file mode 100644
index 0000000..fb58746
--- /dev/null
+++ b/tools/auvirt/auvirt-list.h
@@ -0,0 +1,31 @@
+
+#ifndef AUVIRT_LIST_HEADER
+#define AUVIRT_LIST_HEADER
+
+typedef void (list_free_data_fn)(void *data);
+
+typedef struct list_node_t {
+	void *data;
+	struct list_node_t *prev;
+	struct list_node_t *next;
+} list_node_t;
+
+typedef struct list_t {
+	list_node_t *head;
+	list_node_t *tail;
+	list_free_data_fn *free_data_fn;
+} list_t;
+
+list_t *list_init(list_t *list, list_free_data_fn *free_data_fn);
+list_t *list_new(list_free_data_fn *free_data_fn);
+void list_free_(list_t *list, list_free_data_fn *free_data_fn);
+void list_free(list_t *list);
+list_node_t *list_insert_after(list_t *list, list_node_t *it,
+		void *data);
+list_node_t *list_append(list_t *list, void *data);
+int list_remove_(list_t *list, list_node_t *it,
+		list_free_data_fn *free_data_fn);
+int list_remove(list_t *list, list_node_t *it);
+
+#endif
+
diff --git a/tools/auvirt/auvirt.8 b/tools/auvirt/auvirt.8
new file mode 100644
index 0000000..86266f7
--- /dev/null
+++ b/tools/auvirt/auvirt.8
@@ -0,0 +1,121 @@
+.TH AUVIRT 8 "Dec 2011" "IBM Corp" "System Administration Utilities"
+.SH NAME
+auvirt - a program that shows data related to virtual machines
+
+.SH SYNOPSIS
+.B auvirt
+[ \fIOPTIONS\fP ]
+
+.SH DESCRIPTION
+\fBauvirt\fP shows a list of guest sessions found in the audit logs. If a guest
+is specified, only the events related to that guest is considered. To specify a
+guest, both UUID or VM name can be given.
+
+For each guest session the tool prints a record with the domain name, the user
+that started the guest, the time when the guest was started and the time when
+the guest was stoped.
+
+If the option "--all-events" is given a more detailed output is shown. In this
+mode other records are shown for guest's stops, resource
+assignments, host shutdowns and AVC and anomaly events. The first field
+indicates the event type and can have the following values: start, stop,
+res, avc, anom and down (for host shutdowns).
+
+Resource assignments have the additional fields: resource type, reason and
+resource. And AVC records have the following additional fields: operation,
+result, command and target.
+
+By default, auvirt reads records from the system audit log file. But
+\fB--stdin\fP and \fB--file\fP options can be specified to change this
+behavior.
+
+.SH OPTIONS
+.TP
+\fB--all-events\fP
+Show records for all virtualization related events.
+.TP
+\fB--debug\fP
+Print debug messages to standard output.
+.TP
+\fB-f\fP, \fB--file\fP \fIfile\fP
+Read records from the given \fIfile\fP instead from the system audit log file.
+.TP
+\fB-h\fP, \fB--help\fP
+Print help message and exit.
+.TP
+\fB--proof\fP
+Add after each event a line containing all the identifiers of the audit records
+used to calculate the event. Each identifier consists of unix time,
+milliseconds and serial number.
+.TP
+\fB--show-uuid\fP
+Add the guest's UUID to each record.
+.TP
+\fB--stdin\fP
+Read records from the standard input instead from the system audit log file.
+This option cannot be specified with \fB--file\fP.
+.TP
+\fB--summary\fP
+Print a summary with information about the events found. The summary contains
+the considered range of time, the number of guest starts and stops, the number
+of resource assignments, the number of AVC and anomaly events, the number of
+host shutdowns and the number of failed operations.
+.TP
+.BR \-te ,\  \-\-end \ [\fIend-date\fP]\ [\fIend-time\fP]
+Search for events with time stamps equal to or before the given end time. The
+format of end time depends on your locale. If the date is omitted,
+.B today
+is assumed. If the time is omitted,
+.B now
+is assumed. Use 24 hour clock time rather than AM or PM to specify time.
+An example date using the en_US.utf8 locale is 09/03/2009. An example of time
+is 18:00:00. The date format accepted is influenced by the LC_TIME
+environmental variable.
+
+You may also use the word: \fBnow\fP, \fBrecent\fP, \fBtoday\fP,
+\fByesterday\fP, \fBthis\-week\fP, \fBweek\-ago\fP, \fBthis\-month\fP,
+\fBthis\-year\fP. \fBToday\fP means starting now. \fBRecent\fP is 10 minutes
+ago. \fBYesterday\fP is 1 second after midnight the previous day.
+\fBThis\-week\fP means starting 1 second after midnight on day 0 of the week
+determined by your locale (see \fBlocaltime\fP). \fBThis\-month\fP means 1
+second after midnight on day 1 of the month. \fBThis\-year\fP means the 1
+second after midnight on the first day of the first month.
+.TP
+.BR \-ts ,\  \-\-start \ [\fIstart-date\fP]\ [\fIstart-time\fP]
+Search for events with time stamps equal to or after the given end time. The
+format of end time depends on your locale. If the date is omitted,
+.B today
+is assumed. If the time is omitted,
+.B midnight
+is assumed. Use 24 hour clock time rather than AM or PM to specify time. An
+example date using the en_US.utf8 locale is 09/03/2009. An example of time is
+18:00:00. The date format accepted is influenced by the LC_TIME environmental
+variable.
+
+You may also use the word: \fBnow\fP, \fBrecent\fP, \fBtoday\fP,
+\fByesterday\fP, \fBthis\-week\fP, \fBthis\-month\fP, \fBthis\-year\fP.
+\fBToday\fP means starting at 1 second after midnight. \fBRecent\fP is 10
+minutes ago. \fBYesterday\fP is 1 second after midnight the previous day.
+\fBThis\-week\fP means starting 1 second after midnight on day 0 of the week
+determined by your locale (see \fBlocaltime\fP). \fBThis\-month\fP means 1
+second after midnight on day 1 of the month. \fBThis\-year\fP means the 1
+second after midnight on the first day of the first month.
+.TP
+\fB-u\fP, \fB--uuid\fP \ \fIUUID\fP
+Only show events related to the guest with the given UUID.
+.TP
+\fB-v\fP, \fB--vm\fP \ \fIname\fP
+Only show events related to the guest with the given name.
+
+.SH EXAMPLES
+To see all the records in this month for a guest
+
+\fBauvirt --start this-month --vm GuestVmName --all-events\fP
+
+.SH SEE ALSO
+.BR aulast (8),
+.BR ausearch (8),
+.BR aureport (8).
+
+.SH AUTHOR
+Marcelo Cerri
diff --git a/tools/auvirt/auvirt.c b/tools/auvirt/auvirt.c
new file mode 100644
index 0000000..c04780a
--- /dev/null
+++ b/tools/auvirt/auvirt.c
@@ -0,0 +1,1308 @@
+/*
+ * 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 <mhcerri@br.ibm.com>
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <locale.h>
+#include <string.h>
+#include <regex.h>
+#include <time.h>
+#include <sys/types.h>
+#include <pwd.h>
+#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 machine id events: */
+	char *seclevel;
+	/* Fields specific for avc events: */
+	char *target;
+	char *comm;
+	char *seresult;
+	char *seperms;
+	/* 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()
+{
+	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->seclevel);
+		free(event->target);
+		free(event->comm);
+		free(event->seresult);
+		free(event->seperms);
+		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()
+{
+	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 a field has its value quoted in the audit log, for
+		 * example:
+		 *	vm="guest-name"
+		 *
+		 * auparse will consider the field value with quotes when
+		 * matching a rule. For example, using the example above the
+		 * following rule will not match:
+		 *     ausearch_add_item(au, "vm", "=", "guest-name", how);
+		 *
+		 * But this rule will match:
+		 *     ausearch_add_item(au, "vm", "=", "\"guest-name\"", how);
+		 *
+		 * TODO use a better approach for this problem...
+		 */
+		snprintf(expr, sizeof(expr), "\"%s\"", vm);
+		if (ausearch_add_item(au, "vm", "=", expr,
+					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_get_field_str(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 i"
+					"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);
+	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.\n");
+		}
+		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("net", res_type) == 0) {
+		const char *res;
+		/* Resource removed */
+		snprintf(field, sizeof(field), "old-%s", res_type);
+		res = auparse_find_field(au, field);
+		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 */
+		snprintf(field, sizeof(field), "new-%s", res_type);
+		res = auparse_find_field(au, field);
+		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 = auparse_find_field(au, "cgroup");
+		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;
+}
+
+/* AVC records are correlated to guest through the selinux context. */
+int process_avc(auparse_state_t *au)
+{
+	const char *target, *seclevel;
+	struct event *machine_id, *avc;
+	uid_t uid;
+	time_t time;
+
+	seclevel = get_seclevel(auparse_find_field(au, "tcontext"));
+	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->seresult = copy_str(auparse_find_field(au, "seresult"));
+	avc->seperms = copy_str(auparse_find_field(au, "seperms"));
+	avc->comm = copy_str(auparse_find_field(au, "comm"));
+	avc->target = copy_str(auparse_find_field(au, "name"));
+	add_proof(avc, au);
+	if (list_append(events, avc) == NULL) {
+		event_free(avc);
+		return 1;
+	}
+	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 &&
+			    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 || e->uid < 0) {
+		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));
+		printf("\t%s", N(event->res));
+	} 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->seperms));
+		printf("\t%-10.10s", N(event->seresult));
+		printf("\t%s\t%s", N(event->comm), N(event->target));
+	}
+	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()
+{
+	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()
+{
+	/* 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;
+
+	/* 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;
+			}
+		}
+
+	}
+
+	/* Print summary */
+	printf("Range of time for report:       %-.16s - %-.16s\n",
+			(start_time) ? ctime(&start_time) : "undef",
+			(end_time) ? ctime(&end_time) : "undef");
+	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) {
+		const char *op;
+		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;
+}
+
-- 
1.7.1

^ permalink raw reply related	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2012-02-03 18:52 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2011-12-15 15:56 [PATCH] auvirt: a new tool for reporting events related to virtual machines Marcelo Cerri
2011-12-20 18:18 ` Steve Grubb
2012-01-05 16:44   ` Marcelo Cerri
2012-01-09 17:00     ` Marcelo Cerri
2012-01-11 21:48       ` Steve Grubb
2012-01-13 17:25         ` Marcelo Cerri
2012-01-13 19:23           ` Steve Grubb
2012-01-13 19:45             ` Marcelo Cerri
2012-01-13 20:56               ` Steve Grubb
2012-01-16 13:05             ` Marcelo Cerri
2012-01-16 15:36               ` Steve Grubb
2012-01-11 21:20     ` Steve Grubb
2012-01-24 18:08       ` Marcelo Cerri
2012-01-24 18:33         ` Marcelo Cerri
2012-01-24 20:27         ` Steve Grubb
2012-01-25 12:56           ` Marcelo Cerri
2012-01-27 16:37             ` Marcelo Cerri
2012-01-27 17:21               ` Steve Grubb
2012-01-27 17:31                 ` Marcelo Cerri
  -- strict thread matches above, loose matches on Subject: below --
2012-02-01 17:16 Marcelo Cerri
2012-02-03 18:52 ` Steve Grubb

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox