On Tue, 08 Sep 2009, Jamie Strandboge wrote: > > [PATCH 3] > > patch_3_security_apparmor.patch: > > Adds security_apparmor.c, security_apparmor.h, virt-aa-helper.c and > > updates po/POTFILES.in. virt-aa-helper.c is a new binary which is used > > exclusively by the AppArmor security driver to manipulate AppArmor. > > These files compile without warning and pass syntax-check. > > -- Jamie Strandboge | http://www.canonical.com
diff -Nurp ./libvirt.orig/po/POTFILES.in ./libvirt/po/POTFILES.in --- ./libvirt.orig/po/POTFILES.in 2009-09-08 12:59:59.000000000 -0500 +++ ./libvirt/po/POTFILES.in 2009-09-08 15:32:22.000000000 -0500 @@ -31,6 +31,7 @@ src/qemu_conf.c src/qemu_driver.c src/remote_internal.c src/security.c +src/security_apparmor.c src/security_selinux.c src/storage_backend.c src/storage_backend_disk.c @@ -50,6 +51,7 @@ src/uuid.c src/vbox/vbox_driver.c src/vbox/vbox_tmpl.c src/virsh.c +src/virt-aa-helper.c src/virterror.c src/xen_inotify.c src/xen_internal.c diff -Nurp ./libvirt.orig/src/security_apparmor.c ./libvirt/src/security_apparmor.c --- ./libvirt.orig/src/security_apparmor.c 1969-12-31 18:00:00.000000000 -0600 +++ ./libvirt/src/security_apparmor.c 2009-09-08 15:32:42.000000000 -0500 @@ -0,0 +1,625 @@ + +/* + * AppArmor security driver for libvirt + * Copyright (C) 2009 Canonical Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Author: + * Jamie Strandboge <jamie@xxxxxxxxxxxxx> + * Based on security_selinux.c by James Morris <jmorris@xxxxxxxxx> + * + * AppArmor security driver. + */ + +#include <config.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/apparmor.h> +#include <errno.h> +#include <unistd.h> + +#include "internal.h" + +#include "security.h" +#include "security_apparmor.h" +#include "util.h" +#include "memory.h" +#include "virterror_internal.h" +#include "datatypes.h" + +#define VIR_FROM_THIS VIR_FROM_SECURITY +#define SECURITY_APPARMOR_VOID_DOI "0" +#define SECURITY_APPARMOR_NAME "apparmor" + +/* + * profile_status returns '-1' on error, '0' if loaded + * + * If check_enforcing is set to '1', then returns '-1' on error, '0' if + * loaded in complain mode, and '1' if loaded in enforcing mode. + */ +static int +profile_status(const char *str, const int check_enforcing) +{ + char ebuf[1024]; + char *content = NULL; + char *tmp = NULL; + char *etmp = NULL; + int rc = -1; + + /* create string that is '<str> \0' for accurate matching */ + if (VIR_ALLOC_N(tmp, strlen(str) + 2) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _ + ("%s: could not allocate memory for string"), + __func__); + return rc; + } + sprintf(tmp, "%s ", str); + + if (check_enforcing != 0) { + /* create string that is '<str> (enforce)\0' for accurate matching */ + if (VIR_ALLOC_N(etmp, strlen(str) + 11) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _("%s: could not allocate memory for " + "string"), __func__); + VIR_FREE(tmp); + return rc; + } + sprintf(etmp, "%s (enforce)", str); + } + + if (virFileReadAll(APPARMOR_PROFILES_PATH, MAX_FILE_LEN, &content) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _ + ("%s: Failed to read AppArmor profiles list " + "%s: %s"), __func__, + APPARMOR_PROFILES_PATH, virStrerror(errno, + ebuf, + sizeof + ebuf)); + if (check_enforcing != 0) + VIR_FREE(etmp); + VIR_FREE(tmp); + return rc; + } + + if (strstr(content, tmp) != NULL) + rc = 0; + if (check_enforcing != 0) { + if (rc == 0 && strstr(content, etmp) != NULL) + rc = 1; /* return '1' if loaded and enforcing */ + VIR_FREE(etmp); + } + + VIR_FREE(tmp); + VIR_FREE(content); + + return rc; +} + +static int +profile_loaded(const char *str) +{ + return profile_status(str, 0); +} + +/* + * profile_status_file returns '-1' on error, '0' if file on disk is in + * complain mode and '1' file on disk is in enforcing mode + */ +static int +profile_status_file(const char *str) +{ + char ebuf[1024]; + char profile[PATH_MAX]; + char *content = NULL; + char *tmp = NULL; + int rc = -1; + int len; + + if (snprintf(profile, PATH_MAX, "%s/%s", + APPARMOR_DIR "/libvirt", str) > PATH_MAX - 1) { + virSecurityReportError(NULL, VIR_ERR_ERROR, _("%s: profile name " + "exceeds maximum length"), + __func__); + } + + if (!virFileExists(profile)) { + return rc; + } + + if ((len = virFileReadAll(profile, MAX_FILE_LEN, &content)) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _("%s: Failed to read '%s': %s"), + __func__, profile, + virStrerror(errno, ebuf, sizeof ebuf)); + return rc; + } + + /* create string that is ' <str> flags=(complain)\0' */ + if (VIR_ALLOC_N(tmp, strlen(str) + 19) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _ + ("%s: could not allocate memory for string"), + __func__); + VIR_FREE(content); + return rc; + } + sprintf(tmp, " %s flags=(complain)", str); + + if (strstr(content, tmp) != NULL) + rc = 0; + else + rc = 1; + + VIR_FREE(tmp); + VIR_FREE(content); + + return rc; +} + +/* + * load (add) a profile. Will create one if necessary. If skip_disk is not + * NULL, then don't include the skipped disk. + */ +static int +load_profile(const char *profile, virDomainObjPtr vm, + const char *skip_disk) +{ + const char **argv; + int rc = -1; + int offset; + size_t i; + size_t len; + int create; + int j; + + create = 1; + offset = 6; + if (profile_status_file(profile) >= 0) + create = 0; + len = offset + vm->def->ndisks + 1; + + if (VIR_ALLOC_N(argv, len) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _("%s: could not allocate memory"), + __func__); + return rc; + } + + argv[0] = VIRT_AA_HELPER_PATH; + argv[2] = (char *) "-u"; + argv[3] = profile; + argv[4] = (char *) "-n"; + argv[5] = vm->def->name; + + if (create == 0) + argv[1] = (char *) "-r"; + else + argv[1] = (char *) "-c"; + + /* add disks to argv, skipping skip_disk if it is defined. */ + j = 0; + for (i = 0; i < vm->def->ndisks; i++) { + if (vm->def->disks[i]->src == NULL || (skip_disk != NULL && + STREQ(skip_disk, + vm->def-> + disks[i]->src))) { + continue; + } + if (offset + j >= len - 1) + break; + argv[offset + j] = vm->def->disks[i]->src; + j++; + } + argv[offset + j] = NULL; + + if (virRun(NULL, argv, NULL) == 0) + rc = 0; + + VIR_FREE(argv); + return rc; +} + +static int +remove_profile(const char *profile) +{ + const char *argv[5]; + int rc = -1; + + argv[0] = VIRT_AA_HELPER_PATH; + argv[1] = (char *) "-R"; + argv[2] = (char *) "-u"; + argv[3] = profile; + argv[4] = NULL; + + if (virRun(NULL, argv, NULL) == 0) + rc = 0; + + return rc; +} + +/* + * profile_name is buffer to hold name and len is how many bytes in the + * buffer + */ +static int +get_profile_name(virConnectPtr conn, + virDomainObjPtr vm, char *profile_name, const size_t len) +{ + virDomainPtr dom = NULL; + int rc = -1; + char *prefix = (char *) "libvirt-"; + + if (len < PROFILE_NAME_SIZE) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _("%s: profile_name has wrong size"), + __func__); + return rc; + } + profile_name[0] = '\0'; + strcat(profile_name, prefix); + + /* generate the profile name */ + dom = virGetDomain(conn, vm->def->name, vm->def->uuid); + if (!dom) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _("%s: could not get domain for VM"), + __func__); + return rc; + } + dom->id = vm->def->id; + + if (virDomainGetUUIDString(dom, &profile_name[strlen(profile_name)]) != + 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _("%s: could not find uuid for VM"), + __func__); + return rc; + } + + return 0; +} + +/* returns -1 on error or profile for libvirtd is unconfined, 0 if complain + * mode and 1 if enforcing + */ +static int +use_apparmor(void) +{ + char libvirt_daemon[PATH_MAX]; + int rc = -1; + ssize_t len = 0; + + if ((len = + readlink("/proc/self/exe", libvirt_daemon, PATH_MAX - 1)) < 0) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _("%s: path to libvirt " + "daemon could not be found"), __func__); + return rc; + } + libvirt_daemon[len] = '\0'; + + if (access(APPARMOR_PROFILES_PATH, R_OK) != 0) + return rc; + + return profile_status(libvirt_daemon, 1); +} + +/* Called on libvirtd startup to see if AppArmor is available */ +static int +AppArmorSecurityDriverProbe(void) +{ + char template[PATH_MAX]; + + if (use_apparmor() < 0) + return SECURITY_DRIVER_DISABLE; + + /* see if template file exists */ + if (snprintf(template, PATH_MAX, "%s/TEMPLATE", + APPARMOR_DIR "/libvirt") > PATH_MAX - 1) { + virSecurityReportError(NULL, VIR_ERR_ERROR, _("%s: template " + "exceeds maximum length"), + __func__); + return SECURITY_DRIVER_DISABLE; + } + + if (!virFileExists(template)) { + virSecurityReportError(NULL, VIR_ERR_ERROR, + _("%s: template '%s' does not exist"), + __func__, template); + return SECURITY_DRIVER_DISABLE; + } + + return SECURITY_DRIVER_ENABLE; +} + +/* Security driver initialization. DOI is for 'Domain of Interpretation' and is + * currently not used. + */ +static int +AppArmorSecurityDriverOpen(virConnectPtr conn, virSecurityDriverPtr drv) +{ + virSecurityDriverSetDOI(conn, drv, SECURITY_APPARMOR_VOID_DOI); + return 0; +} + +/* Currently called in qemudStartVMDaemon to setup a 'label'. We look for and + * use a profile based on the UUID, otherwise create one based on a template. + * Keep in mind that this is called on 'start' with RestoreSecurityLabel being + * called on shutdown. +*/ +static int +AppArmorGenSecurityLabel(virConnectPtr conn, virDomainObjPtr vm) +{ + int rc = -1; + + char profile_name[PROFILE_NAME_SIZE]; + + profile_name[0] = '\0'; + + if ((vm->def->seclabel.label) || + (vm->def->seclabel.model) || (vm->def->seclabel.imagelabel)) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _ + ("%s: security label already defined for VM"), + __func__); + return rc; + } + + if (get_profile_name(conn, vm, profile_name, sizeof(profile_name)) < 0) + return rc; + + /* if the profile is not already loaded, then load one */ + if (profile_loaded(profile_name) < 0) { + if (load_profile(profile_name, vm, NULL) < 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _ + ("%s: cannot generate AppArmor profile '%s'"), + __func__, profile_name); + return rc; + } + } + + vm->def->seclabel.label = strndup(profile_name, strlen(profile_name)); + if (!vm->def->seclabel.label) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _ + ("%s: cannot generate AppArmor profile name " + "(label)"), __func__); + goto err; + } + + /* set imagelabel the same as label (but we won't use it) */ + vm->def->seclabel.imagelabel = + strndup(profile_name, strlen(profile_name)); + if (!vm->def->seclabel.imagelabel) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _ + ("%s: cannot generate AppArmor profile name " + "(imagelabel)"), __func__); + goto err; + } + + vm->def->seclabel.model = strdup(SECURITY_APPARMOR_NAME); + if (!vm->def->seclabel.model) { + virReportOOMError(conn); + goto err; + } + + rc = 0; + goto done; + + err: + remove_profile(profile_name); + VIR_FREE(vm->def->seclabel.label); + VIR_FREE(vm->def->seclabel.imagelabel); + VIR_FREE(vm->def->seclabel.model); + done: + return rc; +} + +/* Seen with 'virsh dominfo <vm>'. This function only called if the VM is + * running. + */ +static int +AppArmorGetSecurityLabel(virConnectPtr conn, + virDomainObjPtr vm, virSecurityLabelPtr sec) +{ + int rc = -1; + char profile_name[PROFILE_NAME_SIZE]; + + profile_name[0] = '\0'; + + if (get_profile_name(conn, vm, profile_name, sizeof(profile_name)) < 0) + return rc; + + strncpy(sec->label, profile_name, VIR_SECURITY_LABEL_BUFLEN); + sec->label[VIR_SECURITY_LABEL_BUFLEN - 1] = '\0'; + + if ((sec->enforcing = profile_status(profile_name, 1)) < 0) { + char ebuf[1024]; + + virSecurityReportError(conn, VIR_ERR_ERROR, _("%s: error calling " + "profile_status(): %s"), + __func__, virStrerror(errno, ebuf, + sizeof ebuf)); + return rc; + } + + return 0; +} + +/* Called on VM shutdown and destroy. See AppArmorGenSecurityLabel (above) for + * more details. Currently called via qemudShutdownVMDaemon. + */ +static int +AppArmorRestoreSecurityLabel(virConnectPtr conn, virDomainObjPtr vm) +{ + const virSecurityLabelDefPtr secdef = &vm->def->seclabel; + char ebuf[1024]; + int rc = 0; + + if (secdef->imagelabel) { + if ((rc = remove_profile(secdef->label)) != 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, _("%s: could not " + "remove profile for '%s': %s"), + __func__, secdef->label, + virStrerror(errno, ebuf, sizeof ebuf)); + } + VIR_FREE(secdef->model); + VIR_FREE(secdef->label); + VIR_FREE(secdef->imagelabel); + } + return rc; +} + +/* Called via virExecWithHook. Output goes to + * LOCAL_STATE_DIR/log/libvirt/qemu/<vm name>.log + */ +static int +AppArmorSetSecurityLabel(virConnectPtr conn, + virSecurityDriverPtr drv, virDomainObjPtr vm) +{ + const virSecurityLabelDefPtr secdef = &vm->def->seclabel; + char ebuf[1024]; + int rc = -1; + char profile_name[PROFILE_NAME_SIZE]; + + profile_name[0] = '\0'; + + if (get_profile_name(conn, vm, profile_name, sizeof(profile_name)) < 0) + return rc; + + if (STRNEQ(drv->name, secdef->model)) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _("%s: security label driver mismatch: " + "\'%s\' model configured for domain, but " + "hypervisor driver is \'%s\'."), + __func__, secdef->model, drv->name); + if (use_apparmor() > 0) + return rc; + } + + if (aa_change_profile(profile_name) < 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, _("%s: error calling " + "aa_change_profile(): %s"), + __func__, virStrerror(errno, ebuf, + sizeof ebuf)); + return rc; + } + + return 0; +} + + +/* Called when hotplugging */ +static int +AppArmorRestoreSecurityImageLabel(virConnectPtr conn, + virDomainObjPtr vm, + virDomainDiskDefPtr disk) +{ + const virSecurityLabelDefPtr secdef = &vm->def->seclabel; + int rc = -1; + char profile_name[PROFILE_NAME_SIZE]; + + profile_name[0] = '\0'; + + if (secdef->imagelabel) { + if (get_profile_name(conn, vm, profile_name, sizeof(profile_name)) + < 0) + return rc; + + /* Update the profile only if it is loaded */ + if (profile_loaded(secdef->imagelabel) >= 0) { + if (load_profile(secdef->imagelabel, vm, disk->src) < 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _ + ("%s: cannot update AppArmor profile " + "'%s'"), __func__, + secdef->imagelabel); + return rc; + } + } + } + + return 0; +} + +/* Called when hotplugging */ +static int +AppArmorSetSecurityImageLabel(virConnectPtr conn, + virDomainObjPtr vm, virDomainDiskDefPtr disk) +{ + const virSecurityLabelDefPtr secdef = &vm->def->seclabel; + int rc = -1; + char profile_name[PROFILE_NAME_SIZE]; + + profile_name[0] = '\0'; + + if (!disk->src) + return 0; + + if (secdef->imagelabel) { + /* if the device doesn't exist, error out */ + if (!virFileExists(disk->src)) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _("%s: '%s' does not exist"), __func__, + disk->src); + return rc; + } + + if (get_profile_name(conn, vm, profile_name, sizeof(profile_name)) + < 0) + return rc; + + /* update the profile only if it is loaded */ + if (profile_loaded(secdef->imagelabel) >= 0) { + if (load_profile(secdef->imagelabel, vm, NULL) < 0) { + virSecurityReportError(conn, VIR_ERR_ERROR, + _ + ("%s: cannot update AppArmor profile " + "'%s'"), __func__, + secdef->imagelabel); + return rc; + } + } + } + + return 0; +} + +static int +AppArmorSecurityVerify(virConnectPtr conn, virDomainDefPtr def) +{ + const virSecurityLabelDefPtr secdef = &def->seclabel; + + if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) { + if (use_apparmor() < 0 || profile_status(secdef->label, 0) < 0) { + virSecurityReportError(conn, VIR_ERR_XML_ERROR, + _("Invalid security label %s"), + secdef->label); + return -1; + } + } + return 0; +} + +virSecurityDriver virAppArmorSecurityDriver = { + .name = SECURITY_APPARMOR_NAME, + .probe = AppArmorSecurityDriverProbe, + .open = AppArmorSecurityDriverOpen, + .domainSecurityVerify = AppArmorSecurityVerify, + .domainSetSecurityImageLabel = AppArmorSetSecurityImageLabel, + .domainRestoreSecurityImageLabel = AppArmorRestoreSecurityImageLabel, + .domainGenSecurityLabel = AppArmorGenSecurityLabel, + .domainGetSecurityLabel = AppArmorGetSecurityLabel, + .domainRestoreSecurityLabel = AppArmorRestoreSecurityLabel, + .domainSetSecurityLabel = AppArmorSetSecurityLabel, +}; diff -Nurp ./libvirt.orig/src/security_apparmor.h ./libvirt/src/security_apparmor.h --- ./libvirt.orig/src/security_apparmor.h 1969-12-31 18:00:00.000000000 -0600 +++ ./libvirt/src/security_apparmor.h 2009-09-08 15:32:22.000000000 -0500 @@ -0,0 +1,22 @@ + +/* + * Copyright (C) 2009 Canonical Ltd. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * Author: + * Jamie Strandboge <jamie@xxxxxxxxxxxxx> + * + */ +#ifndef __VIR_SECURITY_APPARMOR_H__ +#define __VIR_SECURITY_APPARMOR_H__ + +extern virSecurityDriver virAppArmorSecurityDriver; + +#define PROFILE_NAME_SIZE 8 + VIR_UUID_STRING_BUFLEN /* libvirt-<uuid> */ +#define MAX_FILE_LEN (1024*1024*10) /* 10MB limit for sanity check */ + +#endif /* __VIR_SECURITY_APPARMOR_H__ */ diff -Nurp ./libvirt.orig/src/virt-aa-helper.c ./libvirt/src/virt-aa-helper.c --- ./libvirt.orig/src/virt-aa-helper.c 1969-12-31 18:00:00.000000000 -0600 +++ ./libvirt/src/virt-aa-helper.c 2009-09-08 15:32:42.000000000 -0500 @@ -0,0 +1,819 @@ + +/* + * virt-aa-helper: wrapper program used by AppArmor security driver. + * Copyright (C) 2009 Canonical Ltd. + * + * See COPYING.LIB for the License of this software + * + * Author: + * Jamie Strandboge <jamie@xxxxxxxxxxxxx> + * + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdarg.h> +#include <unistd.h> +#include <errno.h> +#include <sys/types.h> +#include <fcntl.h> +#include <getopt.h> +#include <stdbool.h> + +#include "internal.h" + +#include "c-ctype.h" +#include "virterror_internal.h" +#include "memory.h" +#include "util.h" +#include "security.h" +#include "security_apparmor.h" + +static char *progname; + +typedef struct { + char uuid[PROFILE_NAME_SIZE]; /* UUID of vm */ + char name[PATH_MAX]; /* name of vm */ + bool dryrun; /* dry run */ + char cmd; /* 'c' create + * 'a' add (load) + * 'r' replace + * 'R' remove */ + int ndisks; /* number of disks */ + char **disks; /* list of disks */ +} vahControl; + +static int +vahDeinit(vahControl * ctl) +{ + int i; + + if (ctl->disks != NULL) { + for (i = 0; i < ctl->ndisks; i++) { + if (ctl->disks[i] == NULL) + continue; + free(ctl->disks[i]); + } + VIR_FREE(ctl->disks); + } + return 0; +} + +static void +vah_error(vahControl * ctl, int doexit, const char *str) +{ + fprintf(stderr, _("%s: error: %s\n"), progname, str); + + if (doexit) { + if (ctl != NULL) + vahDeinit(ctl); + exit(EXIT_FAILURE); + } +} + +static void +vah_warning(const char *str) +{ + fprintf(stderr, _("%s: warning: %s\n"), progname, str); +} + +static void +vah_info(const char *str) +{ + fprintf(stderr, _("%s:\n%s\n"), progname, str); +} + +/* + * Replace @oldstr in @orig with @repstr + * @len is number of bytes allocated for @orig. Assumes @orig, @oldstr and + * @repstr are null terminated + */ +static int +replace_string(char *orig, const size_t len, const char *oldstr, + const char *repstr) +{ + int idx; + char *pos = NULL; + char *tmp = NULL; + + if ((pos = strstr(orig, oldstr)) == NULL) { + vah_error(NULL, 0, "could not find replacement string"); + return -1; + } + + if (VIR_ALLOC_N(tmp, len) < 0) { + vah_error(NULL, 0, "could not allocate memory for string"); + return -1; + } + tmp[0] = '\0'; + + idx = abs(pos - orig); + + /* copy everything up to oldstr */ + strncat(tmp, orig, idx); + + /* add the replacement string */ + if (strlen(tmp) + strlen(repstr) > len - 1) { + vah_error(NULL, 0, "not enough space in target buffer"); + VIR_FREE(tmp); + return -1; + } + strcat(tmp, repstr); + + /* add everything after oldstr */ + if (strlen(tmp) + strlen(orig) - (idx + strlen(oldstr)) > len - 1) { + vah_error(NULL, 0, "not enough space in target buffer"); + VIR_FREE(tmp); + return -1; + } + strncat(tmp, orig + idx + strlen(oldstr), + strlen(orig) - (idx + strlen(oldstr))); + + strncpy(orig, tmp, len); + orig[len - 1] = '\0'; + VIR_FREE(tmp); + + return 0; +} + +/* + * run an apparmor_parser command + */ +static int +parserCommand(const char *profile_name, const char cmd) +{ + const char *argv[4]; + char flag[3]; + char profile[PATH_MAX]; + + if (strchr("arR", cmd) == NULL) { + vah_error(NULL, 0, "invalid flag"); + return -1; + } + + snprintf(flag, 3, "-%c", cmd); + + if (snprintf(profile, PATH_MAX, "%s/%s", + APPARMOR_DIR "/libvirt", profile_name) > PATH_MAX - 1) { + vah_error(NULL, 0, "profile name exceeds maximum length"); + return -1; + } + + if (!virFileExists(profile)) { + vah_error(NULL, 0, "profile does not exist"); + return -1; + } + + argv[0] = (char *) "/sbin/apparmor_parser"; + argv[1] = flag; + argv[2] = profile; + argv[3] = NULL; + + if (virRun(NULL, argv, NULL) != 0) { + vah_error(NULL, 0, "failed to run apparmor_parser"); + return -1; + } + + return 0; +} + +/* + * Update the disks file + */ +static int +update_include_file(const char *include_file, const char *included_files) +{ + int plen; + int fd; + char *pcontent = NULL; + char *existing = NULL; + char *warning = (char *) + "# DO NOT EDIT THIS FILE DIRECTLY. IT IS MANAGED BY LIBVIRT.\n"; + + plen = strlen(warning) + strlen(included_files) + 1; + if (plen > MAX_FILE_LEN) { + vah_error(NULL, 0, "invalid length for new profile"); + return -1; + } + + if (VIR_ALLOC_N(pcontent, plen) < 0) { + vah_error(NULL, 0, "could not allocate memory for profile"); + return -1; + } + pcontent[0] = '\0'; + + if (snprintf(pcontent, plen, "%s%s", warning, included_files) > + plen - 1) { + vah_error(NULL, 0, "profile content exceeds maximum length"); + VIR_FREE(pcontent); + return -1; + } + + /* only update the disk profile if it is different */ + if (virFileExists(include_file)) { + int flen = virFileReadAll(include_file, MAX_FILE_LEN, &existing); + if (flen < 0) { + VIR_FREE(pcontent); + return -1; + } + if (flen == plen - 1) { + if (STREQLEN(existing, pcontent, plen - 1)) { + VIR_FREE(pcontent); + VIR_FREE(existing); + return 0; + } + } + VIR_FREE(existing); + } + + /* write the file */ + if ((fd = + open(include_file, O_CREAT | O_TRUNC | O_WRONLY, 0644)) == -1) { + vah_error(NULL, 0, "failed to create disks profile"); + VIR_FREE(pcontent); + return -1; + } + + if (safewrite(fd, pcontent, plen - 1) < 0) { /* don't write the '\0' */ + close(fd); + vah_error(NULL, 0, "failed to write to profile"); + VIR_FREE(pcontent); + return -1; + } + + VIR_FREE(pcontent); + if (close(fd) != 0) { + vah_error(NULL, 0, "failed to close or write to profile"); + return -1; + } + return 0; +} + +/* + * Create a profile based on a template + */ +static int +create_profile(const char *profile, const char *profile_name, + const char *profile_files) +{ + char template[PATH_MAX]; + char *tcontent = NULL; + char *pcontent = NULL; + char *replace_name = NULL; + char *replace_files = NULL; + const char *template_name = "\nprofile LIBVIRT_TEMPLATE"; + const char *template_end = "\n}"; + int tlen, plen, rnlen, rflen; + int fd; + int rc = -1; + + if (virFileExists(profile)) { + vah_error(NULL, 0, "profile exists"); + goto end; + } + + if (snprintf(template, PATH_MAX, "%s/TEMPLATE", + APPARMOR_DIR "/libvirt") > PATH_MAX - 1) { + vah_error(NULL, 0, "template name exceeds maximum length"); + goto end; + } + + if (!virFileExists(template)) { + vah_error(NULL, 0, "template does not exist"); + goto end; + } + + if ((tlen = virFileReadAll(template, MAX_FILE_LEN, &tcontent)) < 0) { + vah_error(NULL, 0, "failed to read AppArmor template"); + goto end; + } + + if (strstr(tcontent, template_name) == NULL) { + vah_error(NULL, 0, + "failed to find replacement string in template"); + goto clean_tcontent; + } + + if (strstr(tcontent, template_end) == NULL) { + vah_error(NULL, 0, + "failed to find replacement string in template"); + goto clean_tcontent; + } + + /* '\nprofile <profile_name>\0' */ + rnlen = strlen(profile_name) * sizeof(char) + 10; + if (VIR_ALLOC_N(replace_name, rnlen) < 0) { + vah_error(NULL, 0, "could not allocate memory for profile name"); + goto clean_tcontent; + } + if (snprintf(replace_name, rnlen, "\nprofile %s", profile_name) > + rnlen - 1) { + vah_error(NULL, 0, "profile name exceeds maximum length"); + VIR_FREE(replace_name); + goto clean_tcontent; + } + + /* '\n<profile_files>\n}\0' */ + rflen = strlen(profile_files) * sizeof(char) + 4; + if (VIR_ALLOC_N(replace_files, rflen) < 0) { + vah_error(NULL, 0, "could not allocate memory for profile disks"); + VIR_FREE(replace_name); + goto clean_tcontent; + } + if (snprintf(replace_files, rflen, "\n%s\n}", profile_files) > + rflen - 1) { + vah_error(NULL, 0, "profile disks exceed maximum length"); + goto clean_replace; + } + + plen = tlen + strlen(replace_name) - strlen(template_name) + + strlen(replace_files) - strlen(template_end) + 1; + if (plen > MAX_FILE_LEN || plen < tlen) { + vah_error(NULL, 0, "invalid length for new profile"); + goto clean_replace; + } + + if (VIR_ALLOC_N(pcontent, plen) < 0) { + vah_error(NULL, 0, "could not allocate memory for profile"); + goto clean_replace; + } + pcontent[0] = '\0'; + strcpy(pcontent, tcontent); + + if (replace_string(pcontent, plen, template_name, replace_name) < 0) + goto clean_all; + + if (replace_string(pcontent, plen, template_end, replace_files) < 0) + goto clean_all; + + /* write the file */ + if ((fd = open(profile, O_CREAT | O_EXCL | O_WRONLY, 0644)) == -1) { + vah_error(NULL, 0, "failed to create profile"); + goto clean_all; + } + + if (safewrite(fd, pcontent, plen - 1) < 0) { /* don't write the '\0' */ + close(fd); + vah_error(NULL, 0, "failed to write to profile"); + goto clean_all; + } + + if (close(fd) != 0) { + vah_error(NULL, 0, "failed to close or write to profile"); + goto clean_all; + } + rc = 0; + + clean_all: + VIR_FREE(pcontent); + clean_replace: + VIR_FREE(replace_name); + VIR_FREE(replace_files); + clean_tcontent: + VIR_FREE(tcontent); + end: + return rc; +} + +/* + * Load an existing profile + */ +static int +parserLoad(const char *profile_name) +{ + return parserCommand(profile_name, 'a'); +} + +/* + * Remove an existing profile + */ +static int +parserRemove(const char *profile_name) +{ + return parserCommand(profile_name, 'R'); +} + +/* + * Replace an existing profile + */ +static int +parserReplace(const char *profile_name) +{ + return parserCommand(profile_name, 'r'); +} + +static int +valid_uuid(const char *uuid) +{ + int i; + char *prefix = (char *) "libvirt-"; + + if (strlen(uuid) != PROFILE_NAME_SIZE - 1) + return -1; + + if (STRNEQLEN(prefix, uuid, strlen(prefix))) + return -1; + + for (i = strlen(prefix); i < PROFILE_NAME_SIZE - 1; i++) { + if (uuid[i] == '-') + continue; + if (!c_isxdigit(uuid[i])) + return -1; + } + return 0; +} + +static int +valid_name(const char *name) +{ + /* just try to filter out any dangerous characters in the name that can be + * used to subvert the profile */ + char *bad = (char *) " /[]*"; + int i; + + if (strlen(name) == 0 || strlen(name) > PATH_MAX - 1) + return -1; + + for (i = 0; i < strlen(bad); i++) + if (strchr(name, bad[i]) != NULL) + return -1; + + return 0; +} + + +/* + * Don't allow disks to special files or restricted paths such as /bin, /sbin, + * /usr/bin, /usr/sbin and /etc. This is in an effort to prevent read/write + * access to system files which could be used to elevate privileges. This is a + * safety measure in case libvirtd is under a restrictive profile and is + * subverted and trying to escape confinement. + * + * Note that we cannot exclude block devices because they are valid devices. + * The TEMPLATE file can be adjusted to explicitly disallow these if needed. + */ +static int +valid_disk_path(const char *path) +{ + struct stat sb; + int i; + int npaths = 20; + char **restricted; + int rc = -1; + + if (path == NULL || strlen(path) > PATH_MAX - 1) + return rc; + + if (VIR_ALLOC_N(restricted, npaths) < 0) { + vah_error(NULL, 0, "could not allocate memory for paths"); + return rc; + } + + restricted[0] = (char *) "/bin/"; + restricted[1] = (char *) "/boot/"; + restricted[2] = (char *) "/etc/"; + restricted[3] = (char *) "/initrd/"; + restricted[4] = (char *) "/initrd.img"; + restricted[5] = (char *) "/lib"; + restricted[6] = (char *) "/lost+found/"; + restricted[7] = (char *) "/proc/"; + restricted[8] = (char *) "/sbin/"; + restricted[9] = (char *) "/selinux/"; + restricted[10] = (char *) "/sys/"; + restricted[11] = (char *) "/usr/bin/"; + restricted[12] = (char *) "/usr/lib"; + restricted[13] = (char *) "/usr/sbin/"; + restricted[14] = (char *) "/usr/share/"; + restricted[15] = (char *) "/usr/local/bin/"; + restricted[16] = (char *) "/usr/local/etc/"; + restricted[17] = (char *) "/usr/local/lib"; + restricted[18] = (char *) "/usr/local/sbin/"; + restricted[19] = (char *) "/vmlinuz"; + + if (stat(path, &sb) == -1) + goto end; + + switch (sb.st_mode & S_IFMT) { + case S_IFCHR: + goto end; + break; + case S_IFDIR: + goto end; + break; + case S_IFIFO: + goto end; + break; + case S_IFSOCK: + goto end; + break; + default: + break; + } + + for (i = 0; i < npaths; i++) { + if (strlen(path) < strlen(restricted[i])) + continue; + + if (STREQLEN(path, restricted[i], strlen(restricted[i]))) { + rc = 1; + goto end; + } + } + rc = 0; + + end: + VIR_FREE(restricted); + return rc; +} + +/* + * Print usage + */ +static void +vah_usage(void) +{ + fprintf(stdout, "\n%s [options] disk1 disk2 ...\n\n" + " Options:\n" + " -a | --add load profile\n" + " -c | --create create profile from template\n" + " -D | --delete unload and delete profile\n" + " -r | --replace reload profile\n" + " -R | --remove unload profile\n" + " -h | --help this help\n" + " -n | --name <domain name> domain name\n" + " -u | --uuid <uuid> uuid (profile name)\n" + "\n", progname); + + fprintf(stdout, "This command is intended to be used by libvirtd " + "and not used directly.\n"); + return; +} + +static int +vahParseArgv(vahControl * ctl, int argc, char **argv) +{ + int arg, idx = 0; + int i; + struct option opt[] = { + {"add", 0, 0, 'a'}, + {"create", 0, 0, 'c'}, + {"dryrun", 0, 0, 'd'}, + {"delete", 0, 0, 'D'}, + {"help", 0, 0, 'h'}, + {"replace", 0, 0, 'r'}, + {"remove", 0, 0, 'R'}, + {"uuid", 1, 0, 'u'}, + {"name", 1, 0, 'n'}, + {0, 0, 0, 0} + }; + + while ((arg = getopt_long(argc, argv, "acdDhrRn:u:", opt, &idx)) != -1) { + switch (arg) { + case 'a': + ctl->cmd = 'a'; + break; + case 'c': + ctl->cmd = 'c'; + break; + case 'd': + ctl->dryrun = true; + break; + case 'D': + ctl->cmd = 'D'; + break; + case 'h': + vah_usage(); + exit(EXIT_SUCCESS); + break; + case 'r': + ctl->cmd = 'r'; + break; + case 'R': + ctl->cmd = 'R'; + break; + case 'u': + if (strlen(optarg) > PROFILE_NAME_SIZE - 1) + vah_error(ctl, 1, "invalid UUID"); + strncpy((char *) ctl->uuid, optarg, PROFILE_NAME_SIZE); + ctl->uuid[PROFILE_NAME_SIZE - 1] = '\0'; + break; + case 'n': + strncpy((char *) ctl->name, optarg, PATH_MAX); + ctl->name[PATH_MAX - 1] = '\0'; + break; + default: + vah_error(ctl, 1, "unsupported option"); + break; + } + } + if (strchr("acDrR", ctl->cmd) == NULL) + vah_error(ctl, 1, "bad command"); + + if (valid_uuid(ctl->uuid) != 0) + vah_error(ctl, 1, "invalid UUID"); + + if (ctl->cmd == 'c' || ctl->cmd == 'r') { + if (strlen(ctl->name) == 0) + vah_error(ctl, 1, "name is required"); + + if (valid_name(ctl->name) != 0) + vah_error(ctl, 1, "invalid name"); + + if (optind >= argc) { + ctl->ndisks = 0; + vah_warning("profile has 0 disks"); + } else { + ctl->ndisks = 0; + for (i = optind; i < argc; i++) { + if (virFileExists(argv[i])) + ctl->ndisks++; + else + vah_error(NULL, 0, "skipping disk (does not exist)"); + } + if (ctl->ndisks == 0) { + vah_warning("profile has 0 disks"); + } else { + if (VIR_ALLOC_N(ctl->disks, ctl->ndisks) < 0) + vah_error(ctl, 1, + "could not allocate memory for disks"); + + for (i = 0; i < ctl->ndisks; i++) { + if ((ctl->disks[i] = realpath(argv[optind + i], NULL)) + == NULL) + vah_error(ctl, 1, "could not disk, skipping"); + if (valid_disk_path(ctl->disks[i]) != 0) + vah_error(ctl, 1, "invalid disk"); + } + } + } + } + return 0; +} + +/* + * virt-aa-helper -c -u UUID -n name disk1, disk2, ... + * virt-aa-helper -r -u UUID -n name disk1, disk2, ... + * virt-aa-helper -a -u UUID + * virt-aa-helper -R -u UUID + * virt-aa-helper -D -u UUID + */ +int +main(int argc, char **argv) +{ + vahControl _ctl, *ctl = &_ctl; + int rc = -1; + int i, tlen = 0, ilen = 0; + char profile[PATH_MAX]; + char include_file[PATH_MAX]; + char tmp[3 * PATH_MAX]; + char *included_files = NULL; + + /* clear the environment */ + environ = NULL; + if (setenv("PATH", "/sbin:/usr/sbin", 1) != 0) { + vah_error(ctl, 1, "could not set PATH"); + } + if (setenv("IFS", " \t\n", 1) != 0) { + vah_error(ctl, 1, "could not set IFS"); + } + + if (!(progname = strrchr(argv[0], '/'))) + progname = argv[0]; + else + progname++; + + memset(ctl, 0, sizeof(vahControl)); + + if (vahParseArgv(ctl, argc, argv) != 0) + vah_error(ctl, 1, "could not parse arguments"); + + if (snprintf(profile, PATH_MAX, "%s/%s", + APPARMOR_DIR "/libvirt", ctl->uuid) > PATH_MAX - 1) + vah_error(ctl, 1, "profile name exceeds maximum length"); + + if (snprintf(include_file, PATH_MAX, "%s/%s.files", + APPARMOR_DIR "/libvirt", ctl->uuid) > PATH_MAX - 1) + vah_error(ctl, 1, "disk profile name exceeds maximum length"); + + if (ctl->cmd == 'a') + rc = parserLoad(ctl->uuid); + else if (ctl->cmd == 'R' || ctl->cmd == 'D') { + rc = parserRemove(ctl->uuid); + if (ctl->cmd == 'D') { + unlink(include_file); + unlink(profile); + } + } else if (ctl->cmd == 'c' || ctl->cmd == 'r') { + if (ctl->cmd == 'c' && virFileExists(profile)) + vah_error(ctl, 1, "profile exists"); + + /* build up included_files */ + for (i = 0; i < ctl->ndisks; i++) { + /* ' %s rw,\n' */ + ilen += strlen(ctl->disks[i]) + strlen(" rw,\n"); + } + ilen += strlen(" /log/libvirt/**/.log w,\n" + " /lib/libvirt/**/.monitor rw,\n" + " /run/libvirt/**/.pid rwk,\n"); + ilen += strlen(LOCAL_STATE_DIR) * 3 + strlen(ctl->name) * 3 + 1; + + if (VIR_ALLOC_N(included_files, ilen) < 0) + vah_error(ctl, 1, "could not allocate memory for profile"); + + included_files[0] = '\0'; + tmp[0] = '\0'; + for (i = 0; i < ctl->ndisks; i++) { + if (snprintf(tmp, PATH_MAX, " %s rw,\n", ctl->disks[i]) > + PATH_MAX - 1) { + vah_error(ctl, 0, "disk line exceeds maximum length"); + goto clean; + } + if (strlen(included_files) + strlen(tmp) > ilen - 1) { + vah_error(ctl, 0, "disk lines exceed maximum length"); + goto clean; + } + strcat(included_files, tmp); + } + + /* While 3*PATH_MAX is not accurate when thinking about PATH_MAX, + * it is good enough since the #include line is a relative path + * and we are just trying to keep tmp from overflowing here anyway + */ + if (snprintf(tmp, 3 * PATH_MAX, + " %s/log/libvirt/**/%s.log w,\n" + " %s/lib/libvirt/**/%s.monitor rw,\n" + " %s/run/libvirt/**/%s.pid rwk,\n", + LOCAL_STATE_DIR, ctl->name, LOCAL_STATE_DIR, ctl->name, + LOCAL_STATE_DIR, ctl->name) > 3 * PATH_MAX - 1) { + vah_error(ctl, 0, "added rules exceed maximum length"); + goto clean; + } + + if (strlen(included_files) + strlen(tmp) > ilen - 1) { + vah_error(ctl, 0, "added rules exceed maximum length"); + goto clean; + } + strcat(included_files, tmp); + + /* (re)create the include file using included_files */ + if (ctl->dryrun) { + vah_info(include_file); + vah_info(included_files); + rc = 0; + } else if ((rc = update_include_file(include_file, included_files)) + != 0) + goto clean; + + + /* create the profile from TEMPLATE */ + if (ctl->cmd == 'c') { + tlen = strlen(" #include <libvirt/.files>\n"); + tlen += PROFILE_NAME_SIZE; + + /* While PATH_MAX + 14 is not accurate when thinking about + * PATH_MAX, it is good enough since the #include line is a + * relative path and we are just trying to keep tmp from + * overflowing here anyway + */ + if (snprintf + (tmp, PATH_MAX + 14, " #include <libvirt/%s.files>\n", + ctl->uuid) > PATH_MAX - 13) { + vah_error(ctl, 0, "added include exceeds maximum length"); + goto clean; + } + + if (ctl->dryrun) { + vah_info(profile); + vah_info(ctl->uuid); + vah_info(tmp); + rc = 0; + } else if ((rc = create_profile(profile, ctl->uuid, tmp)) != 0) { + vah_error(ctl, 0, "could not create profile"); + unlink(include_file); + } + } + + if (rc == 0 && !ctl->dryrun) { + if (ctl->cmd == 'c') + rc = parserLoad(ctl->uuid); + else + rc = parserReplace(ctl->uuid); + + /* cleanup */ + if (rc != 0) { + unlink(include_file); + if (ctl->cmd == 'c') + unlink(profile); + } + } + } + + clean: + VIR_FREE(included_files); + vahDeinit(ctl); + + exit(rc == 0 ? EXIT_SUCCESS : EXIT_FAILURE); +}
Attachment:
signature.asc
Description: Digital signature
-- Libvir-list mailing list Libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list