[PATCH] auvirt: a new tool for reporting events related to virtual machines

Marcelo Cerri mhcerri at linux.vnet.ibm.com
Thu Dec 15 15:56:51 UTC 2011


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 at 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 at 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 at 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 at 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 at 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




More information about the Linux-audit mailing list