This patch adds support for matching AVC records generated by AppArmor. With
this patch auvirt matches AVC records based on AppArmor profile name generated
by libvirt, which contains the guest's UUID, and based on target name
("name"
field), which auvirt tries to correlate to resources assigned to the guests.
---
tools/auvirt/auvirt.c | 226 ++++++++++++++++++++++++++++++++++++++++++++++++-
1 files changed, 225 insertions(+), 1 deletions(-)
diff --git a/tools/auvirt/auvirt.c b/tools/auvirt/auvirt.c
index a49a8b8..7c0e769 100644
--- a/tools/auvirt/auvirt.c
+++ b/tools/auvirt/auvirt.c
@@ -894,7 +894,7 @@ int process_avc_selinux_context(auparse_state_t *au, const char
*context)
}
/* AVC records are correlated to guest through the selinux context. */
-int process_avc(auparse_state_t *au)
+int process_avc_selinux(auparse_state_t *au)
{
const char **context;
const char *contexts[] = { "tcontext", "scontext", NULL };
@@ -906,6 +906,230 @@ int process_avc(auparse_state_t *au)
return 0;
}
+int process_avc_apparmor_source(auparse_state_t *au)
+{
+ uid_t uid = -1;
+ time_t time = 0;
+ struct event *avc;
+ const char *target;
+
+ /* Get the target object. */
+ if (auparse_find_field(au, "name") == NULL) {
+ if (debug) {
+ auparse_first_record(au);
+ fprintf(stderr, "Couldn't get the resource name from "
+ "the AVC record: %s\n",
+ auparse_get_record_text(au));
+ }
+ return 0;
+ }
+ target = auparse_interpret_field(au);
+
+ /* Loop backwards to find a guest session with the target object
+ * assigned to. */
+ struct list_node_t *it;
+ struct event *res = NULL;
+ for (it = events->tail; it; it = it->prev) {
+ struct event *event = it->data;
+ if (event->success) {
+ if (event->type == ET_DOWN) {
+ /* It's just possible to find a matching guest
+ * session in the current host session.
+ */
+ break;
+ } else if (event->type == ET_RES &&
+ event->end == 0 &&
+ event->res != NULL &&
+ strcmp(target, event->res) == 0) {
+ res = event;
+ break;
+ }
+ }
+ }
+
+ /* Check if a resource event was found. */
+ if (res == NULL) {
+ if (debug) {
+ fprintf(stderr, "Target object not found for AVC "
+ "event.\n");
+ }
+ return 0;
+ }
+
+ if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL))
+ return 0;
+
+ avc = event_alloc();
+ if (avc == NULL)
+ return 1;
+ avc->type = ET_AVC;
+
+ /* Guest info */
+ avc->uuid = copy_str(res->uuid);
+ avc->name = copy_str(res->name);
+ memcpy(avc->proof, res->proof, sizeof(avc->proof));
+
+ /* AVC info */
+ avc->start = time;
+ avc->uid = uid;
+ auparse_first_record(au);
+ if (auparse_find_field(au, "apparmor")) {
+ int i;
+ avc->avc_result = copy_str(auparse_interpret_field(au));
+ for (i = 0; avc->avc_result && avc->avc_result[i]; i++) {
+ avc->avc_result[i] = tolower(avc->avc_result[i]);
+ }
+ }
+ if (auparse_find_field(au, "operation"))
+ avc->avc_operation = copy_str(auparse_interpret_field(au));
+ avc->target = copy_str(target);
+ if (auparse_find_field(au, "comm"))
+ avc->comm = copy_str(auparse_interpret_field(au));
+
+ add_proof(avc, au);
+ if (list_append(events, avc) == NULL) {
+ event_free(avc);
+ return 1;
+ }
+ return 0;
+}
+
+int process_avc_apparmor_target(auparse_state_t *au)
+{
+ uid_t uid;
+ time_t time;
+ const char *profile;
+ struct event *avc;
+
+ /* Get profile associated with the AVC record */
+ if (auparse_find_field(au, "profile") == NULL) {
+ if (debug) {
+ auparse_first_record(au);
+ fprintf(stderr, "AppArmor profile not found for AVC "
+ "record: %s\n",
+ auparse_get_record_text(au));
+ }
+ return 0;
+ }
+ profile = auparse_interpret_field(au);
+
+ /* Break path to get just the basename */
+ const char *basename = profile + strlen(profile);
+ while (basename != profile && *basename != '/')
+ basename--;
+ if (*basename == '/')
+ basename++;
+
+ /* Check if it is an apparmor profile generated by libvirt and get the
+ * guest UUID from it */
+ const char *prefix = "libvirt-";
+ if (strncmp(prefix, basename, strlen(prefix)) != 0) {
+ if (debug) {
+ fprintf(stderr, "Found a profile which is not "
+ "generated by libvirt: %s\n", profile);
+ }
+ return 0;
+ }
+
+ /* Try to find a valid guest session */
+ const char *uuid = basename + strlen(prefix);
+ struct list_node_t *it;
+ struct event *machine_id = NULL;
+ for (it = events->tail; it; it = it->prev) {
+ struct event *event = it->data;
+ if (event->success) {
+ if (event->uuid != NULL &&
+ strcmp(event->uuid, uuid) == 0) {
+ /* machine_id is used here instead of the start
+ * event because it is generated before any
+ * other event when a guest is started. So,
+ * it's possible to correlate AVC events that
+ * occurs during a guest start.
+ */
+ if (event->type == ET_MACHINE_ID) {
+ machine_id = event;
+ break;
+ } else if (event->type == ET_STOP) {
+ break;
+ }
+ } else if (event->type == ET_DOWN) {
+ break;
+ }
+ }
+ }
+ if (machine_id == NULL) {
+ if (debug) {
+ fprintf(stderr, "Found an AVC record for an unknown "
+ "guest.\n");
+ }
+ return 0;
+ }
+
+ if (extract_virt_fields(au, NULL, &uid, &time, NULL, NULL))
+ return 0;
+
+ avc = event_alloc();
+ if (avc == NULL)
+ return 1;
+ avc->type = ET_AVC;
+
+ /* Guest info */
+ avc->uuid = copy_str(machine_id->uuid);
+ avc->name = copy_str(machine_id->name);
+ memcpy(avc->proof, machine_id->proof, sizeof(avc->proof));
+
+ /* AVC info */
+ avc->start = time;
+ avc->uid = uid;
+ auparse_first_record(au);
+ if (auparse_find_field(au, "apparmor")) {
+ int i;
+ avc->avc_result = copy_str(auparse_interpret_field(au));
+ for (i = 0; avc->avc_result && avc->avc_result[i]; i++) {
+ avc->avc_result[i] = tolower(avc->avc_result[i]);
+ }
+ }
+ if (auparse_find_field(au, "operation"))
+ avc->avc_operation = copy_str(auparse_interpret_field(au));
+ if (auparse_find_field(au, "name"))
+ avc->target = copy_str(auparse_interpret_field(au));
+ if (auparse_find_field(au, "comm"))
+ avc->comm = copy_str(auparse_interpret_field(au));
+
+ add_proof(avc, au);
+ if (list_append(events, avc) == NULL) {
+ event_free(avc);
+ return 1;
+ }
+ return 0;
+}
+
+/* AVC records are correlated to guest through the apparmor path name. */
+int process_avc_apparmor(auparse_state_t *au)
+{
+ if (process_avc_apparmor_target(au))
+ return 1;
+ auparse_first_record(au);
+ return process_avc_apparmor_source(au);
+}
+
+int process_avc(auparse_state_t *au)
+{
+ /* Check if it is a SELinux AVC record */
+ if (auparse_find_field(au, "tcontext")) {
+ auparse_first_record(au);
+ return process_avc_selinux(au);
+ }
+
+ /* Check if it is an AppArmor AVC record */
+ auparse_first_record(au);
+ if (auparse_find_field(au, "apparmor")) {
+ auparse_first_record(au);
+ return process_avc_apparmor(au);
+ }
+ 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)
--
1.7.1