提交 bbaecd6a 编写于 作者: J Jamie Strandboge 提交者: Daniel Veillard

sVirt AppArmor security driver

* configure.in: look for AppArmor and devel
* src/security/security_apparmor.[ch] src/security/security_driver.c
  src/Makefile.am: add and plug the new driver
* src/security/virt-aa-helper.c: new binary which is used exclusively by
  the AppArmor security driver to manipulate AppArmor.
* po/POTFILES.in: registers the new files
* tests/Makefile.am tests/secaatest.c tests/virt-aa-helper-test:
  tests for virt-aa-helper and the security driver, secaatest.c is
  identical to seclabeltest.c except it initializes the 'apparmor'
  driver instead of 'selinux'
上级 f5c65fa1
......@@ -799,6 +799,83 @@ fi
AM_CONDITIONAL([WITH_SECDRIVER_SELINUX], [test "$with_secdriver_selinux" != "no"])
dnl AppArmor
AC_ARG_WITH([apparmor],
[ --with-apparmor use AppArmor to manage security],
[],
[with_apparmor=check])
APPARMOR_CFLAGS=
APPARMOR_LIBS=
if test "$with_apparmor" != "no"; then
old_cflags="$CFLAGS"
old_libs="$LIBS"
if test "$with_apparmor" = "check"; then
AC_CHECK_HEADER([sys/apparmor.h],[],[with_apparmor=no])
AC_CHECK_LIB([apparmor], [aa_change_profile],[],[with_apparmor=no])
AC_CHECK_LIB([apparmor], [aa_change_hat],[],[with_apparmor=no])
if test "$with_apparmor" != "no"; then
with_apparmor="yes"
fi
else
fail=0
AC_CHECK_HEADER([sys/apparmor.h],[],[fail=1])
AC_CHECK_LIB([apparmor], [aa_change_profile],[],[fail=1])
AC_CHECK_LIB([apparmor], [aa_change_hat],[],[fail=1])
test $fail = 1 &&
AC_MSG_ERROR([You must install the AppArmor development package in order to compile libvirt])
fi
CFLAGS="$old_cflags"
LIBS="$old_libs"
fi
if test "$with_apparmor" = "yes"; then
APPARMOR_LIBS="-lapparmor"
AC_DEFINE_UNQUOTED([HAVE_APPARMOR], 1, [whether AppArmor is available for security])
AC_DEFINE_UNQUOTED([APPARMOR_DIR], "/etc/apparmor.d", [path to apparmor directory])
AC_DEFINE_UNQUOTED([APPARMOR_PROFILES_PATH], "/sys/kernel/security/apparmor/profiles", [path to kernel profiles])
fi
AM_CONDITIONAL([HAVE_APPARMOR], [test "$with_apparmor" != "no"])
AC_SUBST([APPARMOR_CFLAGS])
AC_SUBST([APPARMOR_LIBS])
AC_ARG_WITH([secdriver-apparmor],
[ --with-secdriver-apparmor use AppArmor security driver],
[],
[with_secdriver_apparmor=check])
if test "$with_apparmor" != "yes" ; then
if test "$with_secdriver_apparmor" = "check" ; then
with_secdriver_apparmor=no
else
AC_MSG_ERROR([You must install the AppArmor development package in order to compile libvirt])
fi
else
old_cflags="$CFLAGS"
old_libs="$LIBS"
CFLAGS="$CFLAGS $APPARMOR_CFLAGS"
LIBS="$CFLAGS $APPARMOR_LIBS"
fail=0
AC_CHECK_FUNC([change_hat], [], [fail=1])
AC_CHECK_FUNC([aa_change_profile], [], [fail=1])
CFLAGS="$old_cflags"
LIBS="$old_libs"
if test "$fail" = "1" ; then
if test "$with_secdriver_apparmor" = "check" ; then
with_secdriver_apparmor=no
else
AC_MSG_ERROR([You must install the AppArmor development package in order to compile libvirt])
fi
else
with_secdriver_apparmor=yes
AC_DEFINE_UNQUOTED([WITH_SECDRIVER_APPARMOR], 1, [whether AppArmor security driver is available])
fi
fi
AM_CONDITIONAL([WITH_SECDRIVER_APPARMOR], [test "$with_secdriver_apparmor" != "no"])
dnl NUMA lib
AC_ARG_WITH([numactl],
......@@ -1745,6 +1822,7 @@ AC_MSG_NOTICE([])
AC_MSG_NOTICE([Security Drivers])
AC_MSG_NOTICE([])
AC_MSG_NOTICE([ SELinux: $with_secdriver_selinux])
AC_MSG_NOTICE([ AppArmor: $with_secdriver_apparmor])
AC_MSG_NOTICE([])
AC_MSG_NOTICE([Driver Loadable Modules])
AC_MSG_NOTICE([])
......@@ -1792,6 +1870,11 @@ AC_MSG_NOTICE([ selinux: $SELINUX_CFLAGS $SELINUX_LIBS])
else
AC_MSG_NOTICE([ selinux: no])
fi
if test "$with_apparmor" = "yes" ; then
AC_MSG_NOTICE([ apparmor: $APPARMOR_CFLAGS $APPARMOR_LIBS])
else
AC_MSG_NOTICE([ apparmor: no])
fi
if test "$with_numactl" = "yes" ; then
AC_MSG_NOTICE([ numactl: $NUMACTL_CFLAGS $NUMACTL_LIBS])
else
......
......@@ -30,7 +30,9 @@ src/qemu/qemu_monitor_text.c
src/remote/remote_driver.c
src/secret/secret_driver.c
src/security/security_driver.c
src/security/security_apparmor.c
src/security/security_selinux.c
src/security/virt-aa-helper.c
src/storage/storage_backend.c
src/storage/storage_backend_disk.c
src/storage/storage_backend_fs.c
......
......@@ -153,6 +153,9 @@ LXC_CONTROLLER_SOURCES = \
lxc/lxc_controller.c \
lxc/veth.c lxc/veth.h
SECURITY_DRIVER_APPARMOR_HELPER_SOURCES = \
security/virt-aa-helper.c
PHYP_DRIVER_SOURCES = \
phyp/phyp_driver.c phyp/phyp_driver.h
......@@ -238,6 +241,9 @@ SECURITY_DRIVER_SOURCES = \
SECURITY_DRIVER_SELINUX_SOURCES = \
security/security_selinux.h security/security_selinux.c
SECURITY_DRIVER_APPARMOR_SOURCES = \
security/security_apparmor.h security/security_apparmor.c
NODE_DEVICE_DRIVER_SOURCES = \
node_device/node_device_driver.c node_device/node_device_driver.h
......@@ -641,9 +647,15 @@ noinst_LTLIBRARIES += libvirt_driver_security.la
libvirt_la_LIBADD += libvirt_driver_security.la
libvirt_driver_security_la_CFLAGS = \
-I@top_srcdir@/src/conf
libvirt_driver_security_la_LDFLAGS =
if WITH_SECDRIVER_SELINUX
libvirt_driver_security_la_SOURCES += $(SECURITY_DRIVER_SELINUX_SOURCES)
endif
if WITH_SECDRIVER_APPARMOR
libvirt_driver_security_la_SOURCES += $(SECURITY_DRIVER_APPARMOR_SOURCES)
libvirt_driver_security_la_CFLAGS += $(APPARMOR_CFLAGS)
libvirt_driver_security_la_LDFLAGS += $(APPARMOR_LIBS)
endif
# Add all conditional sources just in case...
EXTRA_DIST += \
......@@ -671,6 +683,7 @@ EXTRA_DIST += \
$(NODE_DEVICE_DRIVER_HAL_SOURCES) \
$(NODE_DEVICE_DRIVER_DEVKIT_SOURCES) \
$(SECURITY_DRIVER_SELINUX_SOURCES) \
$(SECURITY_DRIVER_APPARMOR_SOURCES) \
$(SECRET_DRIVER_SOURCES) \
$(VBOX_DRIVER_EXTRA_DIST)
......@@ -795,6 +808,26 @@ endif
endif
EXTRA_DIST += $(LXC_CONTROLLER_SOURCES)
if WITH_SECDRIVER_APPARMOR
if WITH_LIBVIRTD
libexec_PROGRAMS += virt-aa-helper
virt_aa_helper_SOURCES = $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES)
virt_aa_helper_LDFLAGS = $(WARN_CFLAGS)
virt_aa_helper_LDADD = \
$(WARN_CFLAGS) \
$(LIBXML_LIBS) \
@top_srcdir@/gnulib/lib/libgnu.la \
@top_srcdir@/src/libvirt_conf.la \
@top_srcdir@/src/libvirt_util.la
virt_aa_helper_CFLAGS = \
-I@top_srcdir@/src/conf \
-I@top_srcdir@/src/security
endif
endif
EXTRA_DIST += $(SECURITY_DRIVER_APPARMOR_HELPER_SOURCES)
install-data-local:
$(MKDIR_P) "$(DESTDIR)$(localstatedir)/cache/libvirt"
$(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/images"
......
/*
* 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@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"
#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"
/*
* 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)
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);
return rc;
}
}
if (virFileReadAll(APPARMOR_PROFILES_PATH, MAX_FILE_LEN, &content) < 0) {
virReportSystemError(NULL, errno,
_("Failed to read AppArmor profiles list "
"\'%s\'"), APPARMOR_PROFILES_PATH);
if (check_enforcing != 0)
VIR_FREE(etmp);
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(etmp);
}
VIR_FREE(content);
clean:
VIR_FREE(tmp);
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[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"));
}
if (!virFileExists(profile)) {
return rc;
}
if ((len = virFileReadAll(profile, MAX_FILE_LEN, &content)) < 0) {
virReportSystemError(NULL, errno,
_("Failed to read \'%s\'"), profile);
return rc;
}
/* create string that is ' <str> flags=(complain)\0' */
if (virAsprintf(&tmp, " %s flags=(complain)", str) == -1) {
virReportOOMError(NULL);
goto clean;
}
if (strstr(content, tmp) != NULL)
rc = 0;
else
rc = 1;
VIR_FREE(tmp);
clean:
VIR_FREE(content);
return rc;
}
/*
* load (add) a profile. Will create one if necessary
*/
static int
load_profile(virConnectPtr conn, const char *profile, virDomainObjPtr vm,
virDomainDiskDefPtr disk)
{
int rc = -1, status, ret;
bool create = true;
char *xml = NULL;
int pipefd[2];
pid_t child;
if (pipe(pipefd) < -1) {
virReportSystemError(conn, errno, "%s", _("unable to create pipe"));
return rc;
}
xml = virDomainDefFormat(conn, vm->def, VIR_DOMAIN_XML_SECURE);
if (!xml)
goto failed;
if (profile_status_file(profile) >= 0)
create = false;
if (create) {
const char *const argv[] = {
VIRT_AA_HELPER, "-c", "-u", profile, NULL
};
ret = virExec(conn, argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS);
} else if (disk && disk->src) {
const char *const argv[] = {
VIRT_AA_HELPER, "-r", "-u", profile, "-f", disk->src, NULL
};
ret = virExec(conn, argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS);
} else {
const char *const argv[] = {
VIRT_AA_HELPER, "-r", "-u", profile, NULL
};
ret = virExec(conn, argv, NULL, NULL, &child,
pipefd[0], NULL, NULL, VIR_EXEC_CLEAR_CAPS);
}
if (ret < 0)
goto clean;
/* parent continues here */
if (safewrite(pipefd[1], xml, strlen(xml)) < 0) {
virReportSystemError(conn, 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(conn, VIR_ERR_ERROR,
_("Unexpected exit status from virt-aa-helper "
"%d pid %lu"),
WEXITSTATUS(status), (unsigned long)child);
rc = -1;
}
clean:
VIR_FREE(xml);
failed:
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(NULL, argv, NULL) == 0)
rc = 0;
return rc;
}
static char *
get_profile_name(virConnectPtr conn, 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(conn);
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)
{
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", _("could not find libvirtd"));
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 too large"));
return SECURITY_DRIVER_DISABLE;
}
if (!virFileExists(template)) {
virSecurityReportError(NULL, VIR_ERR_ERROR,
_("template \'%s\' does not exist"), 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 = NULL;
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"));
return rc;
}
if ((profile_name = get_profile_name(conn, vm)) == NULL)
return rc;
/* if the profile is not already loaded, then load one */
if (profile_loaded(profile_name) < 0) {
if (load_profile(conn, profile_name, vm, NULL) < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("cannot generate AppArmor profile "
"\'%s\'"), profile_name);
goto clean;
}
}
vm->def->seclabel.label = strndup(profile_name, strlen(profile_name));
if (!vm->def->seclabel.label) {
virReportOOMError(NULL);
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(NULL);
goto err;
}
vm->def->seclabel.model = strdup(SECURITY_APPARMOR_NAME);
if (!vm->def->seclabel.model) {
virReportOOMError(conn);
goto err;
}
rc = 0;
goto clean;
err:
remove_profile(profile_name);
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;
}
/* 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 = NULL;
if ((profile_name = get_profile_name(conn, vm)) == NULL)
return rc;
if (virStrcpy(sec->label, profile_name,
VIR_SECURITY_LABEL_BUFLEN) == NULL) {
virSecurityReportError(conn, VIR_ERR_ERROR,
"%s", _("error copying profile name"));
goto clean;
}
if ((sec->enforcing = profile_status(profile_name, 1)) < 0) {
virSecurityReportError(conn, VIR_ERR_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
AppArmorRestoreSecurityLabel(virConnectPtr conn, virDomainObjPtr vm)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = 0;
if (secdef->imagelabel) {
if ((rc = remove_profile(secdef->label)) != 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("could not remove profile for \'%s\'"),
secdef->label);
}
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;
int rc = -1;
char *profile_name = NULL;
if ((profile_name = get_profile_name(conn, vm)) == NULL)
return rc;
if (STRNEQ(drv->name, secdef->model)) {
virSecurityReportError(conn, VIR_ERR_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(conn, VIR_ERR_ERROR,
_("error calling aa_change_profile()"));
goto clean;
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
/* Called when hotplugging */
static int
AppArmorRestoreSecurityImageLabel(virConnectPtr conn,
virDomainObjPtr vm,
virDomainDiskDefPtr disk ATTRIBUTE_UNUSED)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = -1;
char *profile_name = NULL;
if (secdef->imagelabel) {
if ((profile_name = get_profile_name(conn, vm)) == NULL)
return rc;
/* Update the profile only if it is loaded */
if (profile_loaded(secdef->imagelabel) >= 0) {
if (load_profile(conn, secdef->imagelabel, vm, NULL) < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("cannot update AppArmor profile "
"\'%s\'"),
secdef->imagelabel);
goto clean;
}
}
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
/* Called when hotplugging */
static int
AppArmorSetSecurityImageLabel(virConnectPtr conn,
virDomainObjPtr vm, virDomainDiskDefPtr disk)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int rc = -1;
char *profile_name;
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\' does not exist"), disk->src);
return rc;
}
if ((profile_name = get_profile_name(conn, vm)) == NULL)
return rc;
/* update the profile only if it is loaded */
if (profile_loaded(secdef->imagelabel) >= 0) {
if (load_profile(conn, secdef->imagelabel, vm, disk) < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("cannot update AppArmor profile "
"\'%s\'"),
secdef->imagelabel);
goto clean;
}
}
}
rc = 0;
clean:
VIR_FREE(profile_name);
return rc;
}
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;
}
static int
AppArmorReserveSecurityLabel(virConnectPtr conn ATTRIBUTE_UNUSED,
virDomainObjPtr vm ATTRIBUTE_UNUSED)
{
/* NOOP. Nothing to reserve with AppArmor */
return 0;
}
static int
AppArmorSetSecurityHostdevLabel(virConnectPtr conn ATTRIBUTE_UNUSED,
virDomainObjPtr vm ATTRIBUTE_UNUSED,
virDomainHostdevDefPtr dev ATTRIBUTE_UNUSED)
{
/* TODO: call load_profile with an update vm->def */
return 0;
}
static int
AppArmorRestoreSecurityHostdevLabel(virConnectPtr conn ATTRIBUTE_UNUSED,
virDomainHostdevDefPtr dev ATTRIBUTE_UNUSED)
{
/* TODO: call load_profile (needs virDomainObjPtr vm) */
return 0;
}
virSecurityDriver virAppArmorSecurityDriver = {
.name = SECURITY_APPARMOR_NAME,
.probe = AppArmorSecurityDriverProbe,
.open = AppArmorSecurityDriverOpen,
.domainSecurityVerify = AppArmorSecurityVerify,
.domainSetSecurityImageLabel = AppArmorSetSecurityImageLabel,
.domainRestoreSecurityImageLabel = AppArmorRestoreSecurityImageLabel,
.domainGenSecurityLabel = AppArmorGenSecurityLabel,
.domainReserveSecurityLabel = AppArmorReserveSecurityLabel,
.domainGetSecurityLabel = AppArmorGetSecurityLabel,
.domainRestoreSecurityLabel = AppArmorRestoreSecurityLabel,
.domainSetSecurityLabel = AppArmorSetSecurityLabel,
.domainSetSecurityHostdevLabel = AppArmorSetSecurityHostdevLabel,
.domainRestoreSecurityHostdevLabel = AppArmorRestoreSecurityHostdevLabel,
};
/*
* 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@canonical.com>
*
*/
#ifndef __VIR_SECURITY_APPARMOR_H__
#define __VIR_SECURITY_APPARMOR_H__
extern virSecurityDriver virAppArmorSecurityDriver;
#define AA_PREFIX "libvirt-"
#define PROFILE_NAME_SIZE 8 + VIR_UUID_STRING_BUFLEN /* AA_PREFIX + uuid */
#define MAX_FILE_LEN (1024*1024*10) /* 10MB limit for sanity check */
#endif /* __VIR_SECURITY_APPARMOR_H__ */
......@@ -20,9 +20,16 @@
#include "security_selinux.h"
#endif
#ifdef WITH_SECDRIVER_APPARMOR
#include "security_apparmor.h"
#endif
static virSecurityDriverPtr security_drivers[] = {
#ifdef WITH_SECDRIVER_SELINUX
&virSELinuxSecurityDriver,
#endif
#ifdef WITH_SECDRIVER_APPARMOR
&virAppArmorSecurityDriver,
#endif
NULL
};
......
/*
* 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@canonical.com>
*
*/
#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 <sys/utsname.h>
#include "internal.h"
#include "buf.h"
#include "util.h"
#include "memory.h"
#include "security_driver.h"
#include "security_apparmor.h"
#include "domain_conf.h"
#include "xml.h"
#include "uuid.h"
#include "hostusb.h"
#include "pci.h"
static char *progname;
typedef struct {
char uuid[PROFILE_NAME_SIZE]; /* UUID of vm */
bool dryrun; /* dry run */
char cmd; /* 'c' create
* 'a' add (load)
* 'r' replace
* 'R' remove */
char *files; /* list of files */
virDomainDefPtr def; /* VM definition */
virCapsPtr caps; /* VM capabilities */
char *hvm; /* type of hypervisor (eg hvm, xen) */
int bits; /* bits in the guest */
char *newdisk; /* newly added disk */
} vahControl;
static int
vahDeinit(vahControl * ctl)
{
if (ctl == NULL)
return -1;
VIR_FREE(ctl->def);
if (ctl->caps)
virCapabilitiesFree(ctl->caps);
free(ctl->files);
free(ctl->hvm);
free(ctl->newdisk);
return 0;
}
/*
* Print usage
*/
static void
vah_usage(void)
{
fprintf(stdout, "\n%s [options] [< def.xml]\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"
" -u | --uuid <uuid> uuid (profile name)\n"
" -H | --hvm <hvm> hypervisor type\n"
" -b | --bits <bits> architecture bits\n"
"\n", progname);
fprintf(stdout, "This command is intended to be used by libvirtd "
"and not used directly.\n");
return;
}
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)));
if (virStrcpy(orig, tmp, len) == NULL) {
vah_error(NULL, 0, "error replacing string");
VIR_FREE(tmp);
return -1;
}
VIR_FREE(tmp);
return 0;
}
/*
* run an apparmor_parser command
*/
static int
parserCommand(const char *profile_name, const char cmd)
{
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;
} else {
const char * const argv[] = {
"/sbin/apparmor_parser", flag, profile, NULL
};
if (virRun(NULL, argv, NULL) != 0) {
vah_error(NULL, 0, "failed to run apparmor_parser");
return -1;
}
}
return 0;
}
/*
* Update the dynamic files
*/
static int
update_include_file(const char *include_file, const char *included_files)
{
int rc = -1;
int plen;
int fd;
char *pcontent = NULL;
const char *warning =
"# DO NOT EDIT THIS FILE DIRECTLY. IT IS MANAGED BY LIBVIRT.\n";
if (virAsprintf(&pcontent, "%s%s", warning, included_files) == -1) {
vah_error(NULL, 0, "could not allocate memory for profile");
return rc;
}
plen = strlen(pcontent);
if (plen > MAX_FILE_LEN) {
vah_error(NULL, 0, "invalid length for new profile");
goto clean;
}
/* only update the disk profile if it is different */
if (virFileExists(include_file)) {
char *existing = NULL;
int flen = virFileReadAll(include_file, MAX_FILE_LEN, &existing);
if (flen < 0)
goto clean;
if (flen == plen) {
if (STREQLEN(existing, pcontent, plen)) {
rc = 0;
VIR_FREE(existing);
goto clean;
}
}
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 include file");
goto clean;
}
if (safewrite(fd, pcontent, plen) < 0) { /* don't write the '\0' */
close(fd);
vah_error(NULL, 0, "failed to write to profile");
goto clean;
}
if (close(fd) != 0) {
vah_error(NULL, 0, "failed to close or write to profile");
goto clean;
}
rc = 0;
clean:
VIR_FREE(pcontent);
return rc;
}
/*
* 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;
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, "no replacement string in template");
goto clean_tcontent;
}
if (strstr(tcontent, template_end) == NULL) {
vah_error(NULL, 0, "no replacement string in template");
goto clean_tcontent;
}
/* '\nprofile <profile_name>\0' */
if (virAsprintf(&replace_name, "\nprofile %s", profile_name) == -1) {
vah_error(NULL, 0, "could not allocate memory for profile name");
goto clean_tcontent;
}
/* '\n<profile_files>\n}\0' */
if (virAsprintf(&replace_files, "\n%s\n}", profile_files) == -1) {
vah_error(NULL, 0, "could not allocate memory for profile files");
VIR_FREE(replace_name);
goto clean_tcontent;
}
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)
{
unsigned char rawuuid[VIR_UUID_BUFLEN];
if (strlen(uuid) != PROFILE_NAME_SIZE - 1)
return -1;
if (!STRPREFIX(uuid, AA_PREFIX))
return -1;
if (virUUIDParse(uuid + strlen(AA_PREFIX), rawuuid) < 0)
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 */
const char *bad = " /[]*";
if (strlen(name) == 0 || strlen(name) > PATH_MAX - 1)
return -1;
if (strcspn(name, bad) != strlen(name))
return -1;
return 0;
}
/* see if one of the strings in arr starts with str */
static int
array_starts_with(const char *str, const char * const *arr, const long size)
{
int i;
for (i = 0; i < size; i++) {
if (strlen(str) < strlen(arr[i]))
continue;
if (STRPREFIX(str, arr[i]))
return 0;
}
return 1;
}
/*
* Don't allow access 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.
*
* RETURN: -1 on error, 0 if ok, 1 if blocked
*/
static int
valid_path(const char *path, const bool readonly)
{
struct stat sb;
int npaths;
const char * const restricted[] = {
"/bin/",
"/etc/",
"/lib",
"/lost+found/",
"/proc/",
"/sbin/",
"/selinux/",
"/sys/",
"/usr/bin/",
"/usr/lib",
"/usr/sbin/",
"/usr/share/",
"/usr/local/bin/",
"/usr/local/etc/",
"/usr/local/lib",
"/usr/local/sbin/"
};
/* these paths are ok for readonly, but not read/write */
const char * const restricted_rw[] = {
"/boot/",
"/vmlinuz",
"/initrd",
"/initrd.img"
};
if (path == NULL || strlen(path) > PATH_MAX - 1) {
vah_error(NULL, 0, "bad pathname");
return -1;
}
/* Don't allow double quotes, since we use them to quote the filename
* and this will confuse the apparmor parser.
*/
if (strchr(path, '"') != NULL)
return 1;
if (!virFileExists(path))
vah_warning("path does not exist, skipping file type checks");
else {
if (stat(path, &sb) == -1)
return -1;
switch (sb.st_mode & S_IFMT) {
case S_IFDIR:
return 1;
break;
case S_IFIFO:
return 1;
break;
case S_IFSOCK:
return 1;
break;
default:
break;
}
}
npaths = sizeof(restricted)/sizeof *(restricted);
if (array_starts_with(path, restricted, npaths) == 0)
return 1;
npaths = sizeof(restricted_rw)/sizeof *(restricted_rw);
if (!readonly) {
if (array_starts_with(path, restricted_rw, npaths) == 0)
return 1;
}
return 0;
}
static int
get_definition(vahControl * ctl, const char *xmlStr)
{
int rc = -1;
struct utsname utsname;
virCapsGuestPtr guest; /* this is freed when caps is freed */
/*
* mock up some capabilities. We don't currently use these explicitly,
* but need them for virDomainDefParseString().
*/
/* Really, this never fails - look at the man-page. */
uname (&utsname);
/* set some defaults if not specified */
if (!ctl->bits)
ctl->bits = 32;
if (!ctl->hvm)
ctl->hvm = strdup("hvm");
if ((ctl->caps = virCapabilitiesNew(utsname.machine, 1, 1)) == NULL) {
vah_error(ctl, 0, "could not allocate memory");
goto exit;
}
if ((guest = virCapabilitiesAddGuest(ctl->caps,
ctl->hvm,
utsname.machine,
ctl->bits,
NULL,
NULL,
0,
NULL)) == NULL) {
vah_error(ctl, 0, "could not allocate memory");
goto exit;
}
ctl->def = virDomainDefParseString(NULL, ctl->caps, xmlStr, 0);
if (ctl->def == NULL) {
vah_error(ctl, 0, "could not parse XML");
goto exit;
}
if (!ctl->def->name) {
vah_error(ctl, 0, "could not find name in XML");
goto exit;
}
if (valid_name(ctl->def->name) != 0) {
vah_error(ctl, 0, "bad name");
goto exit;
}
rc = 0;
exit:
return rc;
}
static int
vah_add_file(virBufferPtr buf, const char *path, const char *perms)
{
char *tmp = NULL;
int rc = -1;
bool readonly = true;
if (path == NULL)
return rc;
if (virFileExists(path)) {
if ((tmp = realpath(path, NULL)) == NULL) {
vah_error(NULL, 0, path);
vah_error(NULL, 0, " could not find realpath for disk");
return rc;
}
} else
if ((tmp = strdup(path)) == NULL)
return rc;
if (strchr(perms, 'w') != NULL)
readonly = false;
rc = valid_path(tmp, readonly);
if (rc != 0) {
if (rc > 0) {
vah_error(NULL, 0, path);
vah_error(NULL, 0, " skipped restricted file");
}
goto clean;
}
virBufferVSprintf(buf, " \"%s\" %s,\n", tmp, perms);
clean:
free(tmp);
return rc;
}
static int
file_iterate_cb(virConnectPtr conn ATTRIBUTE_UNUSED,
usbDevice *dev ATTRIBUTE_UNUSED,
const char *file, void *opaque)
{
virBufferPtr buf = opaque;
return vah_add_file(buf, file, "rw");
}
static int
get_files(vahControl * ctl)
{
virBuffer buf = VIR_BUFFER_INITIALIZER;
int rc = -1;
int i;
char *uuid;
char uuidstr[VIR_UUID_STRING_BUFLEN];
/* verify uuid is same as what we were given on the command line */
virUUIDFormat(ctl->def->uuid, uuidstr);
if (virAsprintf(&uuid, "%s%s", AA_PREFIX, uuidstr) == -1) {
vah_error(ctl, 0, "could not allocate memory");
return rc;
}
if (STRNEQ(uuid, ctl->uuid)) {
vah_error(ctl, 0, "given uuid does not match XML uuid");
goto clean;
}
for (i = 0; i < ctl->def->ndisks; i++)
if (ctl->def->disks[i] && ctl->def->disks[i]->src) {
int ret;
if (ctl->def->disks[i]->readonly)
ret = vah_add_file(&buf, ctl->def->disks[i]->src, "r");
else
ret = vah_add_file(&buf, ctl->def->disks[i]->src, "rw");
if (ret != 0)
goto clean;
}
for (i = 0; i < ctl->def->nserials; i++)
if (ctl->def->serials[i] && ctl->def->serials[i]->data.file.path)
if (vah_add_file(&buf,
ctl->def->serials[i]->data.file.path, "w") != 0)
goto clean;
if (ctl->def->console && ctl->def->console->data.file.path)
if (vah_add_file(&buf, ctl->def->console->data.file.path, "w") != 0)
goto clean;
if (ctl->def->os.kernel && ctl->def->os.kernel)
if (vah_add_file(&buf, ctl->def->os.kernel, "r") != 0)
goto clean;
if (ctl->def->os.initrd && ctl->def->os.initrd)
if (vah_add_file(&buf, ctl->def->os.initrd, "r") != 0)
goto clean;
if (ctl->def->os.loader && ctl->def->os.loader)
if (vah_add_file(&buf, ctl->def->os.loader, "r") != 0)
goto clean;
for (i = 0; i < ctl->def->nhostdevs; i++)
if (ctl->def->hostdevs[i]) {
virDomainHostdevDefPtr dev = ctl->def->hostdevs[i];
switch (dev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
if (dev->source.subsys.u.usb.bus &&
dev->source.subsys.u.usb.device) {
usbDevice *usb = usbGetDevice(NULL,
dev->source.subsys.u.usb.bus,
dev->source.subsys.u.usb.device);
if (usb == NULL)
continue;
rc = usbDeviceFileIterate(NULL, usb,
file_iterate_cb, &buf);
usbFreeDevice(NULL, usb);
if (rc != 0)
goto clean;
else {
/* TODO: deal with product/vendor better */
rc = 0;
}
}
break;
}
/* TODO: update so files in /sys are readonly
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
pciDevice *pci = pciGetDevice(NULL,
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 == NULL)
continue;
rc = pciDeviceFileIterate(NULL, pci, file_iterate_cb, &buf);
pciFreeDevice(NULL, pci);
break;
}
*/
default:
rc = 0;
break;
} /* switch */
}
if (ctl->newdisk)
if (vah_add_file(&buf, ctl->newdisk, "rw") != 0)
goto clean;
if (virBufferError(&buf)) {
vah_error(NULL, 0, "failed to allocate file buffer");
goto clean;
}
rc = 0;
ctl->files = virBufferContentAndReset(&buf);
clean:
VIR_FREE(uuid);
return rc;
}
static int
vahParseArgv(vahControl * ctl, int argc, char **argv)
{
int arg, idx = 0;
struct option opt[] = {
{"add", 0, 0, 'a'},
{"create", 0, 0, 'c'},
{"dryrun", 0, 0, 'd'},
{"delete", 0, 0, 'D'},
{"add-file", 0, 0, 'f'},
{"help", 0, 0, 'h'},
{"replace", 0, 0, 'r'},
{"remove", 0, 0, 'R'},
{"uuid", 1, 0, 'u'},
{"hvm", 1, 0, 'H'},
{"bits", 1, 0, 'b'},
{0, 0, 0, 0}
};
int bits;
while ((arg = getopt_long(argc, argv, "acdDhrRH:b:u:f:", opt,
&idx)) != -1) {
switch (arg) {
case 'a':
ctl->cmd = 'a';
break;
case 'b':
bits = atoi(optarg);
if (bits == 32 || bits == 64)
ctl->bits = bits;
else
vah_error(ctl, 1, "invalid bits (should be 32 or 64)");
break;
case 'c':
ctl->cmd = 'c';
break;
case 'd':
ctl->dryrun = true;
break;
case 'D':
ctl->cmd = 'D';
break;
case 'f':
if ((ctl->newdisk = strdup(optarg)) == NULL)
vah_error(ctl, 1, "could not allocate memory for disk");
break;
case 'h':
vah_usage();
exit(EXIT_SUCCESS);
break;
case 'H':
if ((ctl->hvm = strdup(optarg)) == NULL)
vah_error(ctl, 1, "could not allocate memory for hvm");
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");
if (virStrcpy((char *) ctl->uuid, optarg,
PROFILE_NAME_SIZE) == NULL)
vah_error(ctl, 1, "error copying UUID");
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) {
vah_usage();
exit(EXIT_FAILURE);
}
if (ctl->cmd == 'c' || ctl->cmd == 'r') {
char *xmlStr = NULL;
if (virFileReadLimFD(STDIN_FILENO, MAX_FILE_LEN, &xmlStr) < 0)
vah_error(ctl, 1, "could not read xml file");
if (get_definition(ctl, xmlStr) != 0 || ctl->def == NULL) {
VIR_FREE(xmlStr);
vah_error(ctl, 1, "could not get VM definition");
}
VIR_FREE(xmlStr);
if (get_files(ctl) != 0)
vah_error(ctl, 1, "invalid VM definition");
}
return 0;
}
/*
* virt-aa-helper -c -u UUID < file.xml
* virt-aa-helper -r -u UUID [-f <file>] < file.xml
* 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;
virBuffer buf = VIR_BUFFER_INITIALIZER;
int rc = -1;
char profile[PATH_MAX];
char include_file[PATH_MAX];
/* 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') {
char *included_files = NULL;
if (ctl->cmd == 'c' && virFileExists(profile))
vah_error(ctl, 1, "profile exists");
virBufferVSprintf(&buf, " \"%s/log/libvirt/**/%s.log\" w,\n",
LOCAL_STATE_DIR, ctl->def->name);
virBufferVSprintf(&buf, " \"%s/lib/libvirt/**/%s.monitor\" rw,\n",
LOCAL_STATE_DIR, ctl->def->name);
virBufferVSprintf(&buf, " \"%s/run/libvirt/**/%s.pid\" rwk,\n",
LOCAL_STATE_DIR, ctl->def->name);
if (ctl->files)
virBufferVSprintf(&buf, "%s", ctl->files);
if (virBufferError(&buf))
vah_error(ctl, 1, "failed to allocate buffer");
included_files = virBufferContentAndReset(&buf);
/* (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') {
char *tmp = NULL;
if (virAsprintf(&tmp, " #include <libvirt/%s.files>\n",
ctl->uuid) == -1) {
vah_error(ctl, 0, "could not allocate memory");
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);
}
VIR_FREE(tmp);
}
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);
}
......@@ -16,6 +16,7 @@ INCLUDES = \
$(GNUTLS_CFLAGS) \
$(SASL_CFLAGS) \
$(SELINUX_CFLAGS) \
$(APPARMOR_CFLAGS) \
-DGETTEXT_PACKAGE=\"$(PACKAGE)\" \
$(COVERAGE_CFLAGS) \
$(WARN_CFLAGS)
......@@ -31,6 +32,7 @@ LDADDS = \
$(GNUTLS_LIBS) \
$(SASL_LIBS) \
$(SELINUX_LIBS) \
$(APPARMOR_LIBS) \
$(WARN_CFLAGS) \
../src/libvirt_test.la \
../gnulib/lib/libgnu.la \
......@@ -84,6 +86,10 @@ if WITH_SECDRIVER_SELINUX
noinst_PROGRAMS += seclabeltest
endif
if WITH_SECDRIVER_APPARMOR
noinst_PROGRAMS += secaatest
endif
if WITH_CIL
noinst_PROGRAMS += object-locking
endif
......@@ -119,6 +125,9 @@ test_scripts += \
virsh-synopsis
endif
if WITH_SECDRIVER_APPARMOR
test_scripts += virt-aa-helper-test
endif
EXTRA_DIST += $(test_scripts)
TESTS = virshtest \
......@@ -149,6 +158,10 @@ if WITH_SECDRIVER_SELINUX
TESTS += seclabeltest
endif
if WITH_SECDRIVER_APPARMOR
TESTS += secaatest
endif
if WITH_LIBVIRTD
noinst_PROGRAMS += eventtest
TESTS += eventtest
......@@ -285,6 +298,14 @@ else
EXTRA_DIST += seclabeltest.c
endif
if WITH_SECDRIVER_APPARMOR
secaatest_SOURCES = \
secaatest.c
secaatest_LDADD = ../src/libvirt_driver_security.la $(LDADDS)
else
EXTRA_DIST += secaatest.c
endif
qparamtest_SOURCES = \
qparamtest.c testutils.h testutils.c
qparamtest_LDADD = $(LDADDS)
......
#include <config.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "security/security_driver.h"
int
main (int argc ATTRIBUTE_UNUSED, char **argv ATTRIBUTE_UNUSED)
{
int ret;
const char *doi, *model;
virSecurityDriverPtr security_drv;
ret = virSecurityDriverStartup (&security_drv, "apparmor");
if (ret == -1)
{
fprintf (stderr, "Failed to start security driver");
exit (-1);
}
/* No security driver wanted to be enabled: just return */
if (ret == -2)
return 0;
model = virSecurityDriverGetModel (security_drv);
if (!model)
{
fprintf (stderr, "Failed to copy secModel model: %s",
strerror (errno));
exit (-1);
}
doi = virSecurityDriverGetDOI (security_drv);
if (!doi)
{
fprintf (stderr, "Failed to copy secModel DOI: %s",
strerror (errno));
exit (-1);
}
return 0;
}
#!/bin/sh
set -e
test_hostdev="no"
if [ "$1" = "test_hostdev" ]; then
test_hostdev="yes"
shift
fi
output="/dev/null"
use_valgrind=""
ld_library_path="../src/.libs/"
if [ ! -z "$1" ] && [ "$1" = "-d" ]; then
output="/dev/stdout"
shift
fi
exe="../src/virt-aa-helper"
if [ ! -z "$1" ]; then
if [ "$1" = "-v" ]; then
use_valgrind="yes"
shift
fi
if [ -n "$1" ]; then
exe="$1"
shift
fi
fi
if [ ! -x "$exe" ]; then
echo "Could not find '$exe'"
exit 1
fi
echo "testing `basename $exe`" >$output
if [ "$use_valgrind" = "yes" ]; then
exe="valgrind --error-exitcode=2 --track-origins=yes $exe"
fi
extra_args="--dryrun"
errors=0
tmpdir=`mktemp -d`
trap "rm -rf $tmpdir" EXIT HUP INT QUIT TERM
template_xml="$tmpdir/template.xml"
test_xml="$tmpdir/test.xml"
uuid="00000000-0000-0000-0000-0123456789ab"
disk1="$tmpdir/1.img"
disk2="$tmpdir/2.img"
relative_disk1="$tmpdir/./../`basename $tmpdir`//./1.img"
nonexistent="$tmpdir/nonexistant.img"
bad_disk="/etc/passwd"
valid_uuid="libvirt-$uuid"
nonexistent_uuid="libvirt-00000000-0000-0000-0000-000000000001"
cat > "$template_xml" <<EOM
<domain type='kvm'>
<name>virt-aa-helper-test</name>
<uuid>###UUID###</uuid>
<memory>524288</memory>
<currentMemory>524288</currentMemory>
<vcpu>1</vcpu>
<os>
<type arch='x86_64' machine='pc'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>destroy</on_crash>
<devices>
<emulator>/usr/bin/kvm</emulator>
<disk type='file' device='disk'>
<source file='###DISK###'/>
<target dev='hda' bus='ide'/>
</disk>
<interface type='network'>
<mac address='52:54:00:50:4b:26'/>
<source network='default'/>
<model type='virtio'/>
</interface>
<input type='tablet' bus='usb'/>
<input type='mouse' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1'/>
<video>
<model type='cirrus' vram='9216' heads='1'/>
</video>
</devices>
</domain>
EOM
touch "$disk1" "$disk2"
testme() {
expected="$1"
outstr="$2"
args="$3"
input=""
if [ -n "$4" ]; then
input="$4"
if [ ! -e "$input" ]; then
echo "FAIL: could not find $input" >$output
echo "FAIL: could not find $input"
echo " '$extra_args $args': "
errors=$(($errors + 1))
fi
fi
echo -n " $outstr: " >$output
echo -n " '$extra_args $args" >$output
if [ -n "$input" ]; then
echo -n " < $input" >$output
fi
echo "': " >$output
set +e
if [ -n "$input" ]; then
LD_LIBRARY_PATH="$ld_library_path" $exe $extra_args $args < $input >$output 2>&1
else
LD_LIBRARY_PATH="$ld_library_path" $exe $extra_args $args >$output 2>&1
fi
rc="$?"
set -e
if [ "$rc" = "$expected" ]; then
echo "pass" >$output
else
echo "FAIL: exited with '$rc'" >$output
echo "FAIL: exited with '$rc'"
echo -n " $outstr: "
echo " '$extra_args $args': "
errors=$(($errors + 1))
#exit $rc
fi
}
# Expected failures
echo "Expected failures:" >$output
testme "1" "invalid arg" "-z"
testme "1" "invalid case" "-A"
testme "1" "not enough args" "-c"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" > "$test_xml"
testme "1" "no -u with -c" "-c" "$test_xml"
testme "1" "bad uuid (bad digit)" "-c -u libvirt-00000000-0000-0000-0000-00000000000g" "$test_xml"
testme "1" "bad uuid (too long)" "-c -u ${valid_uuid}abcdef" "$test_xml"
testme "1" "bad uuid (too short)" "-c -u libvirt-00000000-0000-0000-0000-0123456789a" "$test_xml"
testme "1" "non-matching uuid" "-c -u libvirt-00000000-0000-0000-0000-00000000000a" "$test_xml"
testme "1" "missing uuid" "-c -u" "$test_xml"
testme "1" "no -u with -R" "-R"
testme "1" "non-existent uuid" "-R -u $nonexistent_uuid"
testme "1" "no -u with -r" "-r"
testme "1" "old '-n' option" "-c -n foo -u $valid_uuid" "$test_xml"
testme "1" "invalid bits" "-c -b 15 -u $valid_uuid" "$test_xml"
testme "1" "invalid bits2" "-c -b a -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$bad_disk,g" > "$test_xml"
testme "1" "bad disk" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$bad_disk,g" | sed "s,</devices>,<disk type='file' device='disk'><source file='$disk2'<target dev='hda' bus='ide'/></disk></devices>,g" > "$test_xml"
testme "1" "bad disk2" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</devices>,<devices>,g" > "$test_xml"
testme "1" "malformed xml" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,/boot/initrd,g" > "$test_xml"
testme "1" "disk in /boot" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,/boot/initrd,g" > "$test_xml"
testme "1" "-r with invalid -f" "-r -u $valid_uuid -f $bad_disk" "$test_xml"
echo "Expected pass:" >$output
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" > "$test_xml"
testme "0" "create" "-c -u $valid_uuid" "$test_xml"
testme "0" "create with bits (32)" "-c -b 32 -u $valid_uuid" "$test_xml"
testme "0" "create with bits (64)" "-c -b 64 -u $valid_uuid" "$test_xml"
testme "0" "create with hvm" "-c -H hvm -u $valid_uuid" "$test_xml"
testme "0" "create with hvm and bits" "-c -H hvm --bits 32 -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</disk>,</disk><disk type='file' device='disk'><source file='$disk2'/><target dev='hdb' bus='ide'/></disk>,g" > "$test_xml"
testme "0" "create multiple disks" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###',${disk1}'/><readonly,g" > "$test_xml"
testme "0" "create (readonly)" "-c -u $valid_uuid" "$test_xml"
if [ "$test_hostdev" = "yes" ]; then
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</disk>,</disk><hostdev mode='subsystem' type='usb'><source><address bus='002' device='004'/></source></hostdev>,g" > "$test_xml"
testme "0" "create hostdev (USB)" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</disk>,</disk><hostdev mode='subsystem' type='pci'><source><address bus='0x00' slot='0x00' function='0x0'/></source></hostdev>,g" > "$test_xml"
testme "0" "create hostdev (PCI)" "-c -u $valid_uuid" "$test_xml"
fi
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$nonexistent,g" > "$test_xml"
testme "0" "create (non-existent disk)" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$relative_disk1,g" > "$test_xml"
testme "0" "create (relative path)" "-c -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk2,g" > "$test_xml"
testme "0" "replace" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$nonexistent,g" > "$test_xml"
testme "0" "replace (non-existent disk)" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" > "$test_xml"
testme "0" "replace (adding disk)" "-r -u $valid_uuid -f $disk2" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" > "$test_xml"
testme "0" "replace (adding non-existent disk)" "-r -u $valid_uuid -f $nonexistent" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</devices>,<disk type='block' device='cdrom'><target dev='hdc' bus='ide'/><readonly/></disk></devices>,g" > "$test_xml"
testme "0" "disk (empty cdrom)" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</devices>,<serial type='file'><source path='$tmpdir/serial.log'/><target port='0'/></serial></devices>,g" > "$test_xml"
testme "0" "serial" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</devices>,<serial type='pty'><target port='0'/></serial></devices>,g" > "$test_xml"
testme "0" "serial (pty)" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</devices>,<console type='file'><source path='$tmpdir/console.log'/><target port='0'/></console></devices>,g" > "$test_xml"
touch "$tmpdir/console.log"
testme "0" "console" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</devices>,<console type='pty'><target port='0'/></console></devices>,g" > "$test_xml"
testme "0" "console (pty)" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<kernel>$tmpdir/kernel</kernel></os>,g" > "$test_xml"
touch "$tmpdir/kernel"
testme "0" "kernel" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<initrd>$tmpdir/initrd</initrd></os>,g" > "$test_xml"
touch "$tmpdir/initrd"
testme "0" "initrd" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<kernel>/boot/kernel</kernel></os>,g" > "$test_xml"
testme "0" "kernel in /boot" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<initrd>/boot/initrd</initrd></os>,g" > "$test_xml"
testme "0" "initrd in /boot" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<kernel>/vmlinuz</kernel></os>,g" > "$test_xml"
testme "0" "kernel is /vmlinuz" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<initrd>/initrd/ramdisk</initrd></os>,g" > "$test_xml"
testme "0" "initrd is /initrd/ramdisk" "-r -u $valid_uuid" "$test_xml"
cat "$template_xml" | sed "s,###UUID###,$uuid,g" | sed "s,###DISK###,$disk1,g" | sed "s,</os>,<initrd>/initrd.img</initrd></os>,g" > "$test_xml"
testme "0" "initrd is /initrd.img" "-r -u $valid_uuid" "$test_xml"
testme "0" "help" "-h"
echo "" >$output
if [ "$errors" != "0" ]; then
echo "FAIL: $errors error(s)" >$output
exit 1
fi
echo PASS >$output
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册