2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
* AppArmor security driver for libvirt
|
2010-09-30 20:54:56 +00:00
|
|
|
* Copyright (C) 2009-2010 Canonical Ltd.
|
2009-10-08 14:34:22 +00:00
|
|
|
*
|
|
|
|
* 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"
|
2010-09-30 20:54:56 +00:00
|
|
|
#include "pci.h"
|
|
|
|
#include "hostusb.h"
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
#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"
|
|
|
|
|
2010-09-30 20:54:56 +00:00
|
|
|
/* Data structure to pass to *FileIterate so we have everything we need */
|
|
|
|
struct SDPDOP {
|
|
|
|
virSecurityDriverPtr drv;
|
|
|
|
virDomainObjPtr vm;
|
|
|
|
};
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
/*
|
|
|
|
* 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 */
|
2009-11-08 21:08:54 +00:00
|
|
|
if (virAsprintf(&tmp, "%s ", str) == -1) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
2009-11-08 21:08:54 +00:00
|
|
|
}
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
if (check_enforcing != 0) {
|
|
|
|
/* create string that is '<str> (enforce)\0' for accurate matching */
|
|
|
|
if (virAsprintf(&etmp, "%s (enforce)", str) == -1) {
|
|
|
|
VIR_FREE(tmp);
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virFileReadAll(APPARMOR_PROFILES_PATH, MAX_FILE_LEN, &content) < 0) {
|
2010-02-04 20:02:58 +00:00
|
|
|
virReportSystemError(errno,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("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);
|
2009-11-13 14:27:43 +00:00
|
|
|
VIR_FREE(etmp);
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
{
|
2009-11-13 14:27:43 +00:00
|
|
|
char *profile = NULL;
|
2009-10-08 14:34:22 +00:00
|
|
|
char *content = NULL;
|
|
|
|
char *tmp = NULL;
|
|
|
|
int rc = -1;
|
|
|
|
int len;
|
|
|
|
|
2009-11-13 14:27:43 +00:00
|
|
|
if (virAsprintf(&profile, "%s/%s", APPARMOR_DIR "/libvirt", str) == -1) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2009-11-13 14:27:43 +00:00
|
|
|
if (!virFileExists(profile))
|
|
|
|
goto failed;
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
if ((len = virFileReadAll(profile, MAX_FILE_LEN, &content)) < 0) {
|
2010-02-04 20:02:58 +00:00
|
|
|
virReportSystemError(errno,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("Failed to read \'%s\'"), profile);
|
2009-11-13 14:27:43 +00:00
|
|
|
goto failed;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* create string that is ' <str> flags=(complain)\0' */
|
|
|
|
if (virAsprintf(&tmp, " %s flags=(complain)", str) == -1) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-11-13 14:27:43 +00:00
|
|
|
goto failed;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (strstr(content, tmp) != NULL)
|
|
|
|
rc = 0;
|
|
|
|
else
|
|
|
|
rc = 1;
|
|
|
|
|
2009-11-13 14:27:43 +00:00
|
|
|
failed:
|
2009-10-08 14:34:22 +00:00
|
|
|
VIR_FREE(tmp);
|
2009-11-13 14:27:43 +00:00
|
|
|
VIR_FREE(profile);
|
2009-10-08 14:34:22 +00:00
|
|
|
VIR_FREE(content);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* load (add) a profile. Will create one if necessary
|
|
|
|
*/
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
load_profile(virSecurityDriverPtr drv,
|
2010-09-30 20:54:56 +00:00
|
|
|
const char *profile,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
const char *fn,
|
|
|
|
bool append)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
int rc = -1, status, ret;
|
|
|
|
bool create = true;
|
|
|
|
char *xml = NULL;
|
|
|
|
int pipefd[2];
|
|
|
|
pid_t child;
|
2010-06-15 16:58:58 +00:00
|
|
|
const char *probe = virSecurityDriverGetAllowDiskFormatProbing(drv)
|
|
|
|
? "1" : "0";
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
if (pipe(pipefd) < -1) {
|
2010-02-04 20:02:58 +00:00
|
|
|
virReportSystemError(errno, "%s", _("unable to create pipe"));
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-02-09 19:18:21 +00:00
|
|
|
xml = virDomainDefFormat(vm->def, VIR_DOMAIN_XML_SECURE);
|
2009-10-08 14:34:22 +00:00
|
|
|
if (!xml)
|
2009-11-13 14:27:43 +00:00
|
|
|
goto clean;
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
if (profile_status_file(profile) >= 0)
|
|
|
|
create = false;
|
|
|
|
|
|
|
|
if (create) {
|
|
|
|
const char *const argv[] = {
|
2010-06-15 16:58:58 +00:00
|
|
|
VIRT_AA_HELPER, "-p", probe, "-c", "-u", profile, NULL
|
2009-10-08 14:34:22 +00:00
|
|
|
};
|
2010-02-04 22:41:52 +00:00
|
|
|
ret = virExec(argv, NULL, NULL, &child,
|
2010-04-06 14:05:47 +00:00
|
|
|
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
|
2010-09-30 20:54:56 +00:00
|
|
|
} 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);
|
2010-06-04 16:20:29 +00:00
|
|
|
} else if (fn) {
|
2009-10-08 14:34:22 +00:00
|
|
|
const char *const argv[] = {
|
2010-06-15 16:58:58 +00:00
|
|
|
VIRT_AA_HELPER, "-p", probe, "-r", "-u", profile, "-f", fn, NULL
|
2009-10-08 14:34:22 +00:00
|
|
|
};
|
2010-02-04 22:41:52 +00:00
|
|
|
ret = virExec(argv, NULL, NULL, &child,
|
2010-04-06 14:05:47 +00:00
|
|
|
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
|
2009-10-08 14:34:22 +00:00
|
|
|
} else {
|
|
|
|
const char *const argv[] = {
|
2010-06-15 16:58:58 +00:00
|
|
|
VIRT_AA_HELPER, "-p", probe, "-r", "-u", profile, NULL
|
2009-10-08 14:34:22 +00:00
|
|
|
};
|
2010-02-04 22:41:52 +00:00
|
|
|
ret = virExec(argv, NULL, NULL, &child,
|
2010-04-06 14:05:47 +00:00
|
|
|
pipefd[0], NULL, NULL, VIR_EXEC_NONE);
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
if (ret < 0)
|
|
|
|
goto clean;
|
|
|
|
|
|
|
|
/* parent continues here */
|
|
|
|
if (safewrite(pipefd[1], xml, strlen(xml)) < 0) {
|
2010-02-04 20:02:58 +00:00
|
|
|
virReportSystemError(errno, "%s", _("unable to write to pipe"));
|
2009-10-08 14:34:22 +00:00
|
|
|
goto clean;
|
|
|
|
}
|
|
|
|
close(pipefd[1]);
|
|
|
|
rc = 0;
|
|
|
|
|
|
|
|
rewait:
|
|
|
|
if (waitpid(child, &status, 0) != child) {
|
|
|
|
if (errno == EINTR)
|
|
|
|
goto rewait;
|
|
|
|
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("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
|
|
|
|
};
|
|
|
|
|
2010-02-04 22:41:52 +00:00
|
|
|
if (virRun(argv, NULL) == 0)
|
2009-10-08 14:34:22 +00:00
|
|
|
rc = 0;
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *
|
2010-02-04 18:19:08 +00:00
|
|
|
get_profile_name(virDomainObjPtr vm)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
char uuidstr[VIR_UUID_STRING_BUFLEN];
|
|
|
|
char *name = NULL;
|
|
|
|
|
|
|
|
virUUIDFormat(vm->def->uuid, uuidstr);
|
|
|
|
if (virAsprintf(&name, "%s%s", AA_PREFIX, uuidstr) < 0) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
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;
|
2010-01-20 21:12:43 +00:00
|
|
|
char *libvirt_daemon = NULL;
|
2009-10-08 14:34:22 +00:00
|
|
|
|
2010-01-20 21:12:43 +00:00
|
|
|
if (virFileResolveLink("/proc/self/exe", &libvirt_daemon) < 0) {
|
2010-02-11 23:09:59 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
"%s", _("could not find libvirtd"));
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (access(APPARMOR_PROFILES_PATH, R_OK) != 0)
|
2010-01-20 21:12:43 +00:00
|
|
|
goto cleanup;
|
|
|
|
|
|
|
|
rc = profile_status(libvirt_daemon, 1);
|
2009-10-08 14:34:22 +00:00
|
|
|
|
2010-01-20 21:12:43 +00:00
|
|
|
cleanup:
|
|
|
|
VIR_FREE(libvirt_daemon);
|
|
|
|
return rc;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
2010-06-04 16:20:29 +00:00
|
|
|
/* reload the profile, adding read/write file specified by fn if it is not
|
|
|
|
* NULL.
|
|
|
|
*/
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
reload_profile(virSecurityDriverPtr drv,
|
2010-09-30 20:54:56 +00:00
|
|
|
virDomainObjPtr vm,
|
|
|
|
const char *fn,
|
|
|
|
bool append)
|
2010-06-04 16:20:29 +00:00
|
|
|
{
|
|
|
|
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) {
|
2010-09-30 20:54:56 +00:00
|
|
|
if (load_profile(drv, secdef->imagelabel, vm, fn, append) < 0) {
|
2010-06-04 16:20:29 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("cannot update AppArmor profile "
|
|
|
|
"\'%s\'"),
|
|
|
|
secdef->imagelabel);
|
|
|
|
goto clean;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rc = 0;
|
|
|
|
clean:
|
|
|
|
VIR_FREE(profile_name);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-09-30 20:54:56 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
/* Called on libvirtd startup to see if AppArmor is available */
|
|
|
|
static int
|
|
|
|
AppArmorSecurityDriverProbe(void)
|
|
|
|
{
|
2009-11-13 14:27:43 +00:00
|
|
|
char *template = NULL;
|
|
|
|
int rc = SECURITY_DRIVER_DISABLE;
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
if (use_apparmor() < 0)
|
2009-11-13 14:27:43 +00:00
|
|
|
return rc;
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
/* see if template file exists */
|
2009-11-13 14:27:43 +00:00
|
|
|
if (virAsprintf(&template, "%s/TEMPLATE",
|
|
|
|
APPARMOR_DIR "/libvirt") == -1) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-11-13 14:27:43 +00:00
|
|
|
return rc;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!virFileExists(template)) {
|
2010-02-11 23:09:59 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("template \'%s\' does not exist"), template);
|
2009-11-13 14:27:43 +00:00
|
|
|
goto clean;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
2009-11-13 14:27:43 +00:00
|
|
|
rc = SECURITY_DRIVER_ENABLE;
|
2009-10-08 14:34:22 +00:00
|
|
|
|
2009-11-13 14:27:43 +00:00
|
|
|
clean:
|
|
|
|
VIR_FREE(template);
|
|
|
|
|
|
|
|
return rc;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Security driver initialization. DOI is for 'Domain of Interpretation' and is
|
|
|
|
* currently not used.
|
|
|
|
*/
|
|
|
|
static int
|
2010-06-15 16:58:58 +00:00
|
|
|
AppArmorSecurityDriverOpen(virSecurityDriverPtr drv,
|
|
|
|
bool allowDiskFormatProbing)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityDriverSetDOI(drv, SECURITY_APPARMOR_VOID_DOI);
|
2010-06-15 16:58:58 +00:00
|
|
|
virSecurityDriverSetAllowDiskFormatProbing(drv, allowDiskFormatProbing);
|
2009-10-08 14:34:22 +00:00
|
|
|
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
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorGenSecurityLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
|
|
|
|
virDomainObjPtr vm)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
int rc = -1;
|
|
|
|
char *profile_name = NULL;
|
|
|
|
|
2010-01-13 14:03:04 +00:00
|
|
|
if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_STATIC)
|
|
|
|
return 0;
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
if ((vm->def->seclabel.label) ||
|
|
|
|
(vm->def->seclabel.model) || (vm->def->seclabel.imagelabel)) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
"%s",
|
|
|
|
_("security label already defined for VM"));
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-02-04 18:19:08 +00:00
|
|
|
if ((profile_name = get_profile_name(vm)) == NULL)
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
vm->def->seclabel.label = strndup(profile_name, strlen(profile_name));
|
|
|
|
if (!vm->def->seclabel.label) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
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) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
goto err;
|
|
|
|
}
|
|
|
|
|
|
|
|
vm->def->seclabel.model = strdup(SECURITY_APPARMOR_NAME);
|
|
|
|
if (!vm->def->seclabel.model) {
|
2010-02-04 18:19:08 +00:00
|
|
|
virReportOOMError();
|
2009-10-08 14:34:22 +00:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2010-01-11 11:04:40 +00:00
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorSetSecurityAllLabel(virSecurityDriverPtr drv,
|
|
|
|
virDomainObjPtr vm, const char *stdin_path)
|
2010-01-11 11:04:40 +00:00
|
|
|
{
|
|
|
|
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) {
|
2010-09-30 20:54:56 +00:00
|
|
|
if (load_profile(drv, vm->def->seclabel.label, vm, stdin_path,
|
|
|
|
false) < 0) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2010-01-11 11:04:40 +00:00
|
|
|
_("cannot generate AppArmor profile "
|
|
|
|
"\'%s\'"), vm->def->seclabel.label);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
/* Seen with 'virsh dominfo <vm>'. This function only called if the VM is
|
|
|
|
* running.
|
|
|
|
*/
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorGetSecurityProcessLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
virSecurityLabelPtr sec)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
int rc = -1;
|
|
|
|
char *profile_name = NULL;
|
|
|
|
|
2010-02-04 18:19:08 +00:00
|
|
|
if ((profile_name = get_profile_name(vm)) == NULL)
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
if (virStrcpy(sec->label, profile_name,
|
|
|
|
VIR_SECURITY_LABEL_BUFLEN) == NULL) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
"%s", _("error copying profile name"));
|
|
|
|
goto clean;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((sec->enforcing = profile_status(profile_name, 1)) < 0) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
"%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
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorReleaseSecurityLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
|
|
|
|
virDomainObjPtr vm)
|
2010-01-11 11:04:40 +00:00
|
|
|
{
|
|
|
|
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
|
|
|
|
|
|
|
|
VIR_FREE(secdef->model);
|
|
|
|
VIR_FREE(secdef->label);
|
|
|
|
VIR_FREE(secdef->imagelabel);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorRestoreSecurityAllLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
|
|
|
|
virDomainObjPtr vm,
|
Don't reset user/group/security label on shared filesystems during migrate
When QEMU runs with its disk on NFS, and as a non-root user, the
disk is chownd to that non-root user. When migration completes
the last step is shutting down the QEMU on the source host. THis
normally resets user/group/security label. This is bad when the
VM was just migrated because the file is still in use on the dest
host. It is thus neccessary to skip the reset step for any files
found to be on a shared filesystem
* src/libvirt_private.syms: Export virStorageFileIsSharedFS
* src/util/storage_file.c, src/util/storage_file.h: Add a new
method virStorageFileIsSharedFS() to determine if a file is
on a shared filesystem (NFS, GFS, OCFS2, etc)
* src/qemu/qemu_driver.c: Tell security driver not to reset
disk labels on migration completion
* src/qemu/qemu_security_dac.c, src/qemu/qemu_security_stacked.c,
src/security/security_selinux.c, src/security/security_driver.h,
src/security/security_apparmor.c: Add ability to skip disk
restore step for files on shared filesystems.
2010-05-13 15:49:22 +00:00
|
|
|
int migrated ATTRIBUTE_UNUSED)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
|
|
|
|
int rc = 0;
|
|
|
|
|
2010-01-13 14:03:04 +00:00
|
|
|
if (secdef->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
|
2009-10-08 14:34:22 +00:00
|
|
|
if ((rc = remove_profile(secdef->label)) != 0) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("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
|
2010-02-09 19:18:21 +00:00
|
|
|
AppArmorSetSecurityProcessLabel(virSecurityDriverPtr drv, virDomainObjPtr vm)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
|
|
|
|
int rc = -1;
|
|
|
|
char *profile_name = NULL;
|
|
|
|
|
2010-02-04 18:19:08 +00:00
|
|
|
if ((profile_name = get_profile_name(vm)) == NULL)
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
if (STRNEQ(drv->name, secdef->model)) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("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) {
|
2010-04-05 23:38:53 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
2009-10-08 14:34:22 +00:00
|
|
|
_("error calling aa_change_profile()"));
|
|
|
|
goto clean;
|
|
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
|
|
|
|
clean:
|
|
|
|
VIR_FREE(profile_name);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Called when hotplugging */
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorRestoreSecurityImageLabel(virSecurityDriverPtr drv,
|
|
|
|
virDomainObjPtr vm,
|
2009-10-08 14:34:22 +00:00
|
|
|
virDomainDiskDefPtr disk ATTRIBUTE_UNUSED)
|
|
|
|
{
|
2010-09-30 20:54:56 +00:00
|
|
|
return reload_profile(drv, vm, NULL, false);
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Called when hotplugging */
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorSetSecurityImageLabel(virSecurityDriverPtr drv,
|
|
|
|
virDomainObjPtr vm, virDomainDiskDefPtr disk)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
|
|
|
|
int rc = -1;
|
|
|
|
char *profile_name;
|
|
|
|
|
2010-01-13 14:03:04 +00:00
|
|
|
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
|
|
|
|
return 0;
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
if (!disk->src)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (secdef->imagelabel) {
|
|
|
|
/* if the device doesn't exist, error out */
|
|
|
|
if (!virFileExists(disk->src)) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("\'%s\' does not exist"), disk->src);
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
2010-02-04 18:19:08 +00:00
|
|
|
if ((profile_name = get_profile_name(vm)) == NULL)
|
2009-10-08 14:34:22 +00:00
|
|
|
return rc;
|
|
|
|
|
|
|
|
/* update the profile only if it is loaded */
|
|
|
|
if (profile_loaded(secdef->imagelabel) >= 0) {
|
2010-09-30 20:54:56 +00:00
|
|
|
if (load_profile(drv, secdef->imagelabel, vm, disk->src,
|
|
|
|
false) < 0) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_INTERNAL_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("cannot update AppArmor profile "
|
|
|
|
"\'%s\'"),
|
|
|
|
secdef->imagelabel);
|
|
|
|
goto clean;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rc = 0;
|
|
|
|
|
|
|
|
clean:
|
|
|
|
VIR_FREE(profile_name);
|
|
|
|
|
|
|
|
return rc;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2010-02-09 19:18:21 +00:00
|
|
|
AppArmorSecurityVerify(virDomainDefPtr def)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
const virSecurityLabelDefPtr secdef = &def->seclabel;
|
|
|
|
|
|
|
|
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) {
|
|
|
|
if (use_apparmor() < 0 || profile_status(secdef->label, 0) < 0) {
|
2010-02-09 19:18:21 +00:00
|
|
|
virSecurityReportError(VIR_ERR_XML_ERROR,
|
2009-10-08 14:34:22 +00:00
|
|
|
_("Invalid security label \'%s\'"),
|
|
|
|
secdef->label);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorReserveSecurityLabel(virSecurityDriverPtr drv ATTRIBUTE_UNUSED,
|
|
|
|
virDomainObjPtr vm ATTRIBUTE_UNUSED)
|
2009-10-08 14:34:22 +00:00
|
|
|
{
|
|
|
|
/* NOOP. Nothing to reserve with AppArmor */
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
2010-09-30 20:54:56 +00:00
|
|
|
AppArmorSetSecurityHostdevLabel(virSecurityDriverPtr drv,
|
2010-06-15 16:44:19 +00:00
|
|
|
virDomainObjPtr vm,
|
2010-09-30 20:54:56 +00:00
|
|
|
virDomainHostdevDefPtr dev)
|
2009-10-08 14:34:22 +00:00
|
|
|
|
|
|
|
{
|
2010-01-13 14:03:04 +00:00
|
|
|
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
|
2010-09-30 20:54:56 +00:00
|
|
|
struct SDPDOP *ptr;
|
|
|
|
int ret = -1;
|
2010-01-13 14:03:04 +00:00
|
|
|
|
|
|
|
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
|
|
|
|
return 0;
|
|
|
|
|
2010-09-30 20:54:56 +00:00
|
|
|
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;
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
2010-09-30 20:54:56 +00:00
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
static int
|
2010-09-30 20:54:56 +00:00
|
|
|
AppArmorRestoreSecurityHostdevLabel(virSecurityDriverPtr drv,
|
2010-06-15 16:44:19 +00:00
|
|
|
virDomainObjPtr vm,
|
2009-10-08 14:34:22 +00:00
|
|
|
virDomainHostdevDefPtr dev ATTRIBUTE_UNUSED)
|
|
|
|
|
|
|
|
{
|
2010-01-13 14:03:04 +00:00
|
|
|
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
|
|
|
|
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC)
|
|
|
|
return 0;
|
|
|
|
|
2010-09-30 20:54:56 +00:00
|
|
|
return reload_profile(drv, vm, NULL, false);
|
2009-10-08 14:34:22 +00:00
|
|
|
}
|
|
|
|
|
2010-06-04 16:20:29 +00:00
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorSetSavedStateLabel(virSecurityDriverPtr drv,
|
|
|
|
virDomainObjPtr vm,
|
|
|
|
const char *savefile)
|
2010-06-04 16:20:29 +00:00
|
|
|
{
|
2010-09-30 20:54:56 +00:00
|
|
|
return reload_profile(drv, vm, savefile, true);
|
2010-06-04 16:20:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2010-06-15 16:44:19 +00:00
|
|
|
AppArmorRestoreSavedStateLabel(virSecurityDriverPtr drv,
|
|
|
|
virDomainObjPtr vm,
|
2010-06-04 16:20:29 +00:00
|
|
|
const char *savefile ATTRIBUTE_UNUSED)
|
|
|
|
{
|
2010-09-30 20:54:56 +00:00
|
|
|
return reload_profile(drv, vm, NULL, false);
|
2010-06-04 16:20:29 +00:00
|
|
|
}
|
|
|
|
|
2009-10-08 14:34:22 +00:00
|
|
|
virSecurityDriver virAppArmorSecurityDriver = {
|
|
|
|
.name = SECURITY_APPARMOR_NAME,
|
|
|
|
.probe = AppArmorSecurityDriverProbe,
|
|
|
|
.open = AppArmorSecurityDriverOpen,
|
|
|
|
.domainSecurityVerify = AppArmorSecurityVerify,
|
|
|
|
.domainSetSecurityImageLabel = AppArmorSetSecurityImageLabel,
|
|
|
|
.domainRestoreSecurityImageLabel = AppArmorRestoreSecurityImageLabel,
|
|
|
|
.domainGenSecurityLabel = AppArmorGenSecurityLabel,
|
|
|
|
.domainReserveSecurityLabel = AppArmorReserveSecurityLabel,
|
2010-01-11 11:04:40 +00:00
|
|
|
.domainReleaseSecurityLabel = AppArmorReleaseSecurityLabel,
|
|
|
|
.domainGetSecurityProcessLabel = AppArmorGetSecurityProcessLabel,
|
|
|
|
.domainSetSecurityProcessLabel = AppArmorSetSecurityProcessLabel,
|
|
|
|
.domainRestoreSecurityAllLabel = AppArmorRestoreSecurityAllLabel,
|
|
|
|
.domainSetSecurityAllLabel = AppArmorSetSecurityAllLabel,
|
2009-10-08 14:34:22 +00:00
|
|
|
.domainSetSecurityHostdevLabel = AppArmorSetSecurityHostdevLabel,
|
|
|
|
.domainRestoreSecurityHostdevLabel = AppArmorRestoreSecurityHostdevLabel,
|
2010-06-04 16:20:29 +00:00
|
|
|
.domainSetSavedStateLabel = AppArmorSetSavedStateLabel,
|
|
|
|
.domainRestoreSavedStateLabel = AppArmorRestoreSavedStateLabel,
|
2009-10-08 14:34:22 +00:00
|
|
|
};
|