libvirt/src/security/security_apparmor.c
Jamie Strandboge 593e0072eb implement usb and pci hot attach in AppArmor driver
Description: Implement AppArmorSetSecurityHostdevLabel() and
AppArmorRestoreSecurityHostdevLabel() for hostdev and pcidev attach.

virt-aa-helper also has to be adjusted because *FileIterate() is used for pci
and usb devices and the corresponding XML for hot attached hostdev and pcidev
is not in the XML passed to virt-aa-helper. The new '-F filename' option is
added to append a rule to the profile as opposed to the existing '-f
filename', which rewrites the libvirt-<uuid>.files file anew. This new '-F'
option will append a rule to an existing libvirt-<uuid>.files if it exists,
otherwise it acts the same as '-f'.

load_profile() and reload_profile() have been adjusted to add an 'append'
argument, which when true will use '-F' instead of '-f' when executing
virt-aa-helper.

All existing calls to load_profile() and reload_profile() have been adjusted
to use the old behavior (ie append==false) except AppArmorSetSavedStateLabel()
where it made sense to use the new behavior.

This patch also adds tests for '-F'.

Bug-Ubuntu: https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/640993
2010-09-30 14:54:56 -06:00

791 lines
22 KiB
C

/*
* AppArmor security driver for libvirt
* Copyright (C) 2009-2010 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@canonical.com>
* Based on security_selinux.c by James Morris <jmorris@namei.org>
*
* 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 <wait.h>
#include <stdbool.h>
#include "internal.h"
#include "security_driver.h"
#include "security_apparmor.h"
#include "util.h"
#include "memory.h"
#include "virterror_internal.h"
#include "datatypes.h"
#include "uuid.h"
#include "pci.h"
#include "hostusb.h"
#define VIR_FROM_THIS VIR_FROM_SECURITY
#define SECURITY_APPARMOR_VOID_DOI "0"
#define SECURITY_APPARMOR_NAME "apparmor"
#define VIRT_AA_HELPER BINDIR "/virt-aa-helper"
/* Data structure to pass to *FileIterate so we have everything we need */
struct SDPDOP {
virSecurityDriverPtr drv;
virDomainObjPtr vm;
};
/*
* 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 *content = NULL;
char *tmp = NULL;
char *etmp = NULL;
int rc = -1;
/* create string that is '<str> \0' for accurate matching */
if (virAsprintf(&tmp, "%s ", str) == -1) {
virReportOOMError();
return rc;
}
if (check_enforcing != 0) {
/* create string that is '<str> (enforce)\0' for accurate matching */
if (virAsprintf(&etmp, "%s (enforce)", str) == -1) {
VIR_FREE(tmp);
virReportOOMError();
return rc;
}
}
if (virFileReadAll(APPARMOR_PROFILES_PATH, MAX_FILE_LEN, &content) < 0) {
virReportSystemError(errno,
_("Failed to read AppArmor profiles list "
"\'%s\'"), APPARMOR_PROFILES_PATH);
goto clean;
}
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(content);
clean:
VIR_FREE(tmp);
VIR_FREE(etmp);
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' if file on disk is in enforcing mode
*/
static int
profile_status_file(const char *str)
{
char *profile = NULL;
char *content = NULL;
char *tmp = NULL;
int rc = -1;
int len;
if (virAsprintf(&profile, "%s/%s", APPARMOR_DIR "/libvirt", str) == -1) {
virReportOOMError();
return rc;
}
if (!virFileExists(profile))
goto failed;
if ((len = virFileReadAll(profile, MAX_FILE_LEN, &content)) < 0) {
virReportSystemError(errno,
_("Failed to read \'%s\'"), profile);
goto failed;
}
/* create string that is ' <str> flags=(complain)\0' */
if (virAsprintf(&tmp, " %s flags=(complain)", str) == -1) {
virReportOOMError();
goto failed;
}
if (strstr(content, tmp) != NULL)
rc = 0;
else
rc = 1;
failed:
VIR_FREE(tmp);
VIR_FREE(profile);
VIR_FREE(content);
return rc;
}
/*
* load (add) a profile. Will create one if necessary
*/
static int
load_profile(virSecurityDriverPtr drv,
const char *profile,
virDomainObjPtr vm,
const char *fn,
bool append)
{
int rc = -1, status, ret;
bool create = true;
char *xml = NULL;
int pipefd[2];
pid_t child;
const char *probe = virSecurityDriverGetAllowDiskFormatProbing(drv)
? "1" : "0";
if (pipe(pipefd) < -1) {
virReportSystemError(errno, "%s", _("unable to create pipe"));
return rc;
}
xml = virDomainDefFormat(vm->def, VIR_DOMAIN_XML_SECURE);
if (!xml)
goto clean;
if (profile_status_file(profile) >= 0)
create = false;
if (create) {
const char *const argv[] = {
VIRT_AA_HELPER, "-p", probe, "-c", "-u", profile, NULL
};
ret = virExec(argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
} else if (fn && append) {
const char *const argv[] = {
VIRT_AA_HELPER, "-p", probe, "-r", "-u", profile, "-F", fn, NULL
};
ret = virExec(argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
} else if (fn) {
const char *const argv[] = {
VIRT_AA_HELPER, "-p", probe, "-r", "-u", profile, "-f", fn, NULL
};
ret = virExec(argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
} else {
const char *const argv[] = {
VIRT_AA_HELPER, "-p", probe, "-r", "-u", profile, NULL
};
ret = virExec(argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
}
if (ret < 0)
goto clean;
/* parent continues here */
if (safewrite(pipefd[1], xml, strlen(xml)) < 0) {
virReportSystemError(errno, "%s", _("unable to write to pipe"));
goto clean;
}
close(pipefd[1]);
rc = 0;
rewait:
if (waitpid(child, &status, 0) != child) {
if (errno == EINTR)
goto rewait;
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("Unexpected exit status from virt-aa-helper "
"%d pid %lu"),
WEXITSTATUS(status), (unsigned long)child);
rc = -1;
}
clean:
VIR_FREE(xml);
if (pipefd[0] > 0)
close(pipefd[0]);
if (pipefd[1] > 0)
close(pipefd[1]);
return rc;
}
static int
remove_profile(const char *profile)
{
int rc = -1;
const char * const argv[] = {
VIRT_AA_HELPER, "-R", "-u", profile, NULL
};
if (virRun(argv, NULL) == 0)
rc = 0;
return rc;
}
static char *
get_profile_name(virDomainObjPtr vm)
{
char uuidstr[VIR_UUID_STRING_BUFLEN];
char *name = NULL;
virUUIDFormat(vm->def->uuid, uuidstr);
if (virAsprintf(&name, "%s%s", AA_PREFIX, uuidstr) < 0) {
virReportOOMError();
return NULL;
}
return name;
}
/* returns -1 on error or profile for libvirtd is unconfined, 0 if complain
* mode and 1 if enforcing. This is required because at present you cannot
* aa_change_profile() from a process that is unconfined.
*/
static int
use_apparmor(void)
{
int rc = -1;
char *libvirt_daemon = NULL;
if (virFileResolveLink("/proc/self/exe", &libvirt_daemon) < 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("could not find libvirtd"));
return rc;
}
if (access(APPARMOR_PROFILES_PATH, R_OK) != 0)
goto cleanup;
rc = profile_status(libvirt_daemon, 1);
cleanup:
VIR_FREE(libvirt_daemon);
return rc;
}
/* reload the profile, adding read/write file specified by fn if it is not
* NULL.
*/
static int
reload_profile(virSecurityDriverPtr drv,
virDomainObjPtr vm,
const char *fn,
bool append)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = -1;
char *profile_name = NULL;
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
if ((profile_name = get_profile_name(vm)) == NULL)
return rc;
/* Update the profile only if it is loaded */
if (profile_loaded(secdef->imagelabel) >= 0) {
if (load_profile(drv, secdef->imagelabel, vm, fn, append) < 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot update AppArmor profile "
"\'%s\'"),
secdef->imagelabel);
goto clean;
}
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
static int
AppArmorSetSecurityUSBLabel(usbDevice *dev ATTRIBUTE_UNUSED,
const char *file, void *opaque)
{
struct SDPDOP *ptr = opaque;
virDomainObjPtr vm = ptr->vm;
if (reload_profile(ptr->drv, vm, file, true) < 0) {
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot update AppArmor profile "
"\'%s\'"),
secdef->imagelabel);
return -1;
}
return 0;
}
static int
AppArmorSetSecurityPCILabel(pciDevice *dev ATTRIBUTE_UNUSED,
const char *file, void *opaque)
{
struct SDPDOP *ptr = opaque;
virDomainObjPtr vm = ptr->vm;
if (reload_profile(ptr->drv, vm, file, true) < 0) {
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot update AppArmor profile "
"\'%s\'"),
secdef->imagelabel);
return -1;
}
return 0;
}
/* Called on libvirtd startup to see if AppArmor is available */
static int
AppArmorSecurityDriverProbe(void)
{
char *template = NULL;
int rc = SECURITY_DRIVER_DISABLE;
if (use_apparmor() < 0)
return rc;
/* see if template file exists */
if (virAsprintf(&template, "%s/TEMPLATE",
APPARMOR_DIR "/libvirt") == -1) {
virReportOOMError();
return rc;
}
if (!virFileExists(template)) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("template \'%s\' does not exist"), template);
goto clean;
}
rc = SECURITY_DRIVER_ENABLE;
clean:
VIR_FREE(template);
return rc;
}
/* Security driver initialization. DOI is for 'Domain of Interpretation' and is
* currently not used.
*/
static int
AppArmorSecurityDriverOpen(virSecurityDriverPtr drv,
bool allowDiskFormatProbing)
{
virSecurityDriverSetDOI(drv, SECURITY_APPARMOR_VOID_DOI);
virSecurityDriverSetAllowDiskFormatProbing(drv, allowDiskFormatProbing);
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(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
virDomainObjPtr vm)
{
int rc = -1;
char *profile_name = NULL;
if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
if ((vm->def->seclabel.label) ||
(vm->def->seclabel.model) || (vm->def->seclabel.imagelabel)) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
"%s",
_("security label already defined for VM"));
return rc;
}
if ((profile_name = get_profile_name(vm)) == NULL)
return rc;
vm->def->seclabel.label = strndup(profile_name, strlen(profile_name));
if (!vm->def->seclabel.label) {
virReportOOMError();
goto clean;
}
/* 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) {
virReportOOMError();
goto err;
}
vm->def->seclabel.model = strdup(SECURITY_APPARMOR_NAME);
if (!vm->def->seclabel.model) {
virReportOOMError();
goto err;
}
rc = 0;
goto clean;
err:
VIR_FREE(vm->def->seclabel.label);
VIR_FREE(vm->def->seclabel.imagelabel);
VIR_FREE(vm->def->seclabel.model);
clean:
VIR_FREE(profile_name);
return rc;
}
static int
AppArmorSetSecurityAllLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm, const char *stdin_path)
{
if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
/* if the profile is not already loaded, then load one */
if (profile_loaded(vm->def->seclabel.label) < 0) {
if (load_profile(drv, vm->def->seclabel.label, vm, stdin_path,
false) < 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot generate AppArmor profile "
"\'%s\'"), vm->def->seclabel.label);
return -1;
}
}
return 0;
}
/* Seen with 'virsh dominfo <vm>'. This function only called if the VM is
* running.
*/
static int
AppArmorGetSecurityProcessLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
virDomainObjPtr vm,
virSecurityLabelPtr sec)
{
int rc = -1;
char *profile_name = NULL;
if ((profile_name = get_profile_name(vm)) == NULL)
return rc;
if (virStrcpy(sec->label, profile_name,
VIR_SECURITY_LABEL_BUFLEN) == NULL) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("error copying profile name"));
goto clean;
}
if ((sec->enforcing = profile_status(profile_name, 1)) < 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
"%s", _("error calling profile_status()"));
goto clean;
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
/* Called on VM shutdown and destroy. See AppArmorGenSecurityLabel (above) for
* more details. Currently called via qemudShutdownVMDaemon.
*/
static int
AppArmorReleaseSecurityLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
virDomainObjPtr vm)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
VIR_FREE(secdef->model);
VIR_FREE(secdef->label);
VIR_FREE(secdef->imagelabel);
return 0;
}
static int
AppArmorRestoreSecurityAllLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
virDomainObjPtr vm,
int migrated ATTRIBUTE_UNUSED)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = 0;
if (secdef->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
if ((rc = remove_profile(secdef->label)) != 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("could not remove profile for \'%s\'"),
secdef->label);
}
}
return rc;
}
/* Called via virExecWithHook. Output goes to
* LOCAL_STATE_DIR/log/libvirt/qemu/<vm name>.log
*/
static int
AppArmorSetSecurityProcessLabel(virSecurityDriverPtr drv, virDomainObjPtr vm)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = -1;
char *profile_name = NULL;
if ((profile_name = get_profile_name(vm)) == NULL)
return rc;
if (STRNEQ(drv->name, secdef->model)) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"\'%s\' model configured for domain, but "
"hypervisor driver is \'%s\'."),
secdef->model, drv->name);
if (use_apparmor() > 0)
goto clean;
}
if (aa_change_profile(profile_name) < 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("error calling aa_change_profile()"));
goto clean;
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
/* Called when hotplugging */
static int
AppArmorRestoreSecurityImageLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm,
virDomainDiskDefPtr disk ATTRIBUTE_UNUSED)
{
return reload_profile(drv, vm, NULL, false);
}
/* Called when hotplugging */
static int
AppArmorSetSecurityImageLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm, virDomainDiskDefPtr disk)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = -1;
char *profile_name;
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
if (!disk->src)
return 0;
if (secdef->imagelabel) {
/* if the device doesn't exist, error out */
if (!virFileExists(disk->src)) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("\'%s\' does not exist"), disk->src);
return rc;
}
if ((profile_name = get_profile_name(vm)) == NULL)
return rc;
/* update the profile only if it is loaded */
if (profile_loaded(secdef->imagelabel) >= 0) {
if (load_profile(drv, secdef->imagelabel, vm, disk->src,
false) < 0) {
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot update AppArmor profile "
"\'%s\'"),
secdef->imagelabel);
goto clean;
}
}
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
static int
AppArmorSecurityVerify(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(VIR_ERR_XML_ERROR,
_("Invalid security label \'%s\'"),
secdef->label);
return -1;
}
}
return 0;
}
static int
AppArmorReserveSecurityLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
virDomainObjPtr vm ATTRIBUTE_UNUSED)
{
/* NOOP. Nothing to reserve with AppArmor */
return 0;
}
static int
AppArmorSetSecurityHostdevLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm,
virDomainHostdevDefPtr dev)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
struct SDPDOP *ptr;
int ret = -1;
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
if (dev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS)
return 0;
if (profile_loaded(secdef->imagelabel) < 0)
return 0;
if (VIR_ALLOC(ptr) < 0)
return -1;
ptr->drv = drv;
ptr->vm = vm;
switch (dev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
usbDevice *usb = usbGetDevice(dev->source.subsys.u.usb.bus,
dev->source.subsys.u.usb.device);
if (!usb)
goto done;
ret = usbDeviceFileIterate(usb, AppArmorSetSecurityUSBLabel, ptr);
usbFreeDevice(usb);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
pciDevice *pci = pciGetDevice(dev->source.subsys.u.pci.domain,
dev->source.subsys.u.pci.bus,
dev->source.subsys.u.pci.slot,
dev->source.subsys.u.pci.function);
if (!pci)
goto done;
ret = pciDeviceFileIterate(pci, AppArmorSetSecurityPCILabel, ptr);
pciFreeDevice(pci);
break;
}
default:
ret = 0;
break;
}
done:
VIR_FREE(ptr);
return ret;
}
static int
AppArmorRestoreSecurityHostdevLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm,
virDomainHostdevDefPtr dev ATTRIBUTE_UNUSED)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
return reload_profile(drv, vm, NULL, false);
}
static int
AppArmorSetSavedStateLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm,
const char *savefile)
{
return reload_profile(drv, vm, savefile, true);
}
static int
AppArmorRestoreSavedStateLabel(virSecurityDriverPtr drv,
virDomainObjPtr vm,
const char *savefile ATTRIBUTE_UNUSED)
{
return reload_profile(drv, vm, NULL, false);
}
virSecurityDriver virAppArmorSecurityDriver = {
.name = SECURITY_APPARMOR_NAME,
.probe = AppArmorSecurityDriverProbe,
.open = AppArmorSecurityDriverOpen,
.domainSecurityVerify = AppArmorSecurityVerify,
.domainSetSecurityImageLabel = AppArmorSetSecurityImageLabel,
.domainRestoreSecurityImageLabel = AppArmorRestoreSecurityImageLabel,
.domainGenSecurityLabel = AppArmorGenSecurityLabel,
.domainReserveSecurityLabel = AppArmorReserveSecurityLabel,
.domainReleaseSecurityLabel = AppArmorReleaseSecurityLabel,
.domainGetSecurityProcessLabel = AppArmorGetSecurityProcessLabel,
.domainSetSecurityProcessLabel = AppArmorSetSecurityProcessLabel,
.domainRestoreSecurityAllLabel = AppArmorRestoreSecurityAllLabel,
.domainSetSecurityAllLabel = AppArmorSetSecurityAllLabel,
.domainSetSecurityHostdevLabel = AppArmorSetSecurityHostdevLabel,
.domainRestoreSecurityHostdevLabel = AppArmorRestoreSecurityHostdevLabel,
.domainSetSavedStateLabel = AppArmorSetSavedStateLabel,
.domainRestoreSavedStateLabel = AppArmorRestoreSavedStateLabel,
};