libvirt/src/security_selinux.c
Daniel P. Berrange ed5a25841f Don't restore labels on shared/readonly disks
* src/security_selinux.c: Skip relabelling of shared/readonly
  disks upon shutdown, since this breaks other VMs still active
  using those disks
2009-07-23 16:26:25 +01:00

498 lines
14 KiB
C

/*
* Copyright (C) 2008,2009 Red Hat, Inc.
*
* 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.
*
* Authors:
* James Morris <jmorris@namei.org>
* Dan Walsh <dwalsh@redhat.com>
*
* SELinux security driver.
*/
#include <config.h>
#include <selinux/selinux.h>
#include <selinux/context.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "security.h"
#include "security_selinux.h"
#include "virterror_internal.h"
#include "util.h"
#include "memory.h"
#include "logging.h"
#define VIR_FROM_THIS VIR_FROM_SECURITY
static char default_domain_context[1024];
static char default_content_context[1024];
static char default_image_context[1024];
#define SECURITY_SELINUX_VOID_DOI "0"
#define SECURITY_SELINUX_NAME "selinux"
/* TODO
The data struct of used mcs should be replaced with a better data structure in the future
*/
struct MCS {
char *mcs;
struct MCS *next;
};
static struct MCS *mcsList = NULL;
static int
mcsAdd(const char *mcs)
{
struct MCS *ptr;
for (ptr = mcsList; ptr; ptr = ptr->next) {
if (STREQ(ptr->mcs, mcs))
return -1;
}
if (VIR_ALLOC(ptr) < 0)
return -1;
ptr->mcs = strdup(mcs);
ptr->next = mcsList;
mcsList = ptr;
return 0;
}
static int
mcsRemove(const char *mcs)
{
struct MCS *prevptr = NULL;
struct MCS *ptr = NULL;
for (ptr = mcsList; ptr; ptr = ptr->next) {
if (STREQ(ptr->mcs, mcs)) {
if (prevptr)
prevptr->next = ptr->next;
else {
mcsList = ptr->next;
}
free(ptr->mcs);
free(ptr);
return 0;
}
prevptr = ptr;
}
return -1;
}
static char *
SELinuxGenNewContext(const char *oldcontext, const char *mcs)
{
char *newcontext = NULL;
char *scontext = strdup(oldcontext);
if (!scontext) goto err;
context_t con = context_new(scontext);
if (!con) goto err;
context_range_set(con, mcs);
newcontext = strdup(context_str(con));
context_free(con);
err:
freecon(scontext);
return (newcontext);
}
static int
SELinuxInitialize(virConnectPtr conn)
{
char *ptr = NULL;
int fd = 0;
char ebuf[1024];
virRandomInitialize(time(NULL) ^ getpid());
fd = open(selinux_virtual_domain_context_path(), O_RDONLY);
if (fd < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: cannot open SELinux virtual domain context file %s: %s"),
__func__,selinux_virtual_domain_context_path(),
virStrerror(errno, ebuf, sizeof ebuf));
return -1;
}
if (saferead(fd, default_domain_context, sizeof(default_domain_context)) < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: cannot read SELinux virtual domain context file %s: %s"),
__func__,selinux_virtual_domain_context_path(),
virStrerror(errno, ebuf, sizeof ebuf));
close(fd);
return -1;
}
close(fd);
ptr = strchrnul(default_domain_context, '\n');
*ptr = '\0';
if ((fd = open(selinux_virtual_image_context_path(), O_RDONLY)) < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: cannot open SELinux virtual image context file %s: %s"),
__func__,selinux_virtual_image_context_path(),
virStrerror(errno, ebuf, sizeof ebuf));
return -1;
}
if (saferead(fd, default_image_context, sizeof(default_image_context)) < 0) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: cannot read SELinux virtual image context file %s: %s"),
__func__,selinux_virtual_image_context_path(),
virStrerror(errno, ebuf, sizeof ebuf));
close(fd);
return -1;
}
close(fd);
ptr = strchrnul(default_image_context, '\n');
if (*ptr == '\n') {
*ptr = '\0';
strcpy(default_content_context, ptr+1);
ptr = strchrnul(default_content_context, '\n');
if (*ptr == '\n')
*ptr = '\0';
}
return 0;
}
static int
SELinuxGenSecurityLabel(virConnectPtr conn,
virDomainObjPtr vm)
{
int rc = -1;
char mcs[1024];
char *scontext = NULL;
int c1 = 0;
int c2 = 0;
if (vm->def->seclabel.label ||
vm->def->seclabel.model ||
vm->def->seclabel.imagelabel) {
virSecurityReportError(conn, VIR_ERR_INTERNAL_ERROR,
"%s", _("security label already defined for VM"));
return rc;
}
do {
c1 = virRandom(1024);
c2 = virRandom(1024);
if ( c1 == c2 ) {
sprintf(mcs, "s0:c%d", c1);
} else {
if ( c1 < c2 )
sprintf(mcs, "s0:c%d,c%d", c1, c2);
else
sprintf(mcs, "s0:c%d,c%d", c2, c1);
}
} while(mcsAdd(mcs) == -1);
vm->def->seclabel.label = SELinuxGenNewContext(default_domain_context, mcs);
if (! vm->def->seclabel.label) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("cannot generate selinux context for %s"), mcs);
goto err;
}
vm->def->seclabel.imagelabel = SELinuxGenNewContext(default_image_context, mcs);
if (! vm->def->seclabel.imagelabel) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("cannot generate selinux context for %s"), mcs);
goto err;
}
vm->def->seclabel.model = strdup(SECURITY_SELINUX_NAME);
if (!vm->def->seclabel.model) {
virReportOOMError(conn);
goto err;
}
rc = 0;
goto done;
err:
VIR_FREE(vm->def->seclabel.label);
VIR_FREE(vm->def->seclabel.imagelabel);
VIR_FREE(vm->def->seclabel.model);
done:
VIR_FREE(scontext);
return rc;
}
static int
SELinuxReserveSecurityLabel(virConnectPtr conn,
virDomainObjPtr vm)
{
security_context_t pctx;
context_t ctx = NULL;
const char *mcs;
if (getpidcon(vm->pid, &pctx) == -1) {
char ebuf[1024];
virSecurityReportError(conn, VIR_ERR_ERROR, _("%s: error calling "
"getpidcon(): %s"), __func__,
virStrerror(errno, ebuf, sizeof ebuf));
return -1;
}
ctx = context_new(pctx);
VIR_FREE(pctx);
if (!ctx)
goto err;
mcs = context_range_get(ctx);
if (!mcs)
goto err;
mcsAdd(mcs);
context_free(ctx);
return 0;
err:
context_free(ctx);
return -1;
}
static int
SELinuxSecurityDriverProbe(void)
{
return is_selinux_enabled() ? SECURITY_DRIVER_ENABLE : SECURITY_DRIVER_DISABLE;
}
static int
SELinuxSecurityDriverOpen(virConnectPtr conn, virSecurityDriverPtr drv)
{
/*
* Where will the DOI come from? SELinux configuration, or qemu
* configuration? For the moment, we'll just set it to "0".
*/
virSecurityDriverSetDOI(conn, drv, SECURITY_SELINUX_VOID_DOI);
return SELinuxInitialize(conn);
}
static int
SELinuxGetSecurityLabel(virConnectPtr conn,
virDomainObjPtr vm,
virSecurityLabelPtr sec)
{
security_context_t ctx;
if (getpidcon(vm->pid, &ctx) == -1) {
char ebuf[1024];
virSecurityReportError(conn, VIR_ERR_ERROR, _("%s: error calling "
"getpidcon(): %s"), __func__,
virStrerror(errno, ebuf, sizeof ebuf));
return -1;
}
if (strlen((char *) ctx) >= VIR_SECURITY_LABEL_BUFLEN) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: security label exceeds "
"maximum lenth: %d"), __func__,
VIR_SECURITY_LABEL_BUFLEN - 1);
return -1;
}
strcpy(sec->label, (char *) ctx);
free(ctx);
sec->enforcing = security_getenforce();
if (sec->enforcing == -1) {
char ebuf[1024];
virSecurityReportError(conn, VIR_ERR_ERROR, _("%s: error calling "
"security_getenforce(): %s"), __func__,
virStrerror(errno, ebuf, sizeof ebuf));
return -1;
}
return 0;
}
static int
SELinuxSetFilecon(virConnectPtr conn, const char *path, char *tcon)
{
char ebuf[1024];
security_context_t econ;
VIR_INFO("Setting SELinux context on '%s' to '%s'", path, tcon);
if (setfilecon(path, tcon) < 0) {
if (getfilecon(path, &econ) >= 0) {
if (STREQ(tcon, econ)) {
freecon(econ);
/* It's alright, there's nothing to change anyway. */
return 0;
}
freecon(econ);
}
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: unable to set security context "
"'\%s\' on %s: %s."), __func__,
tcon,
path,
virStrerror(errno, ebuf, sizeof ebuf));
if (security_getenforce() == 1)
return -1;
}
return 0;
}
static int
SELinuxRestoreSecurityImageLabel(virConnectPtr conn,
virDomainDiskDefPtr disk)
{
struct stat buf;
security_context_t fcon = NULL;
int rc = -1;
int err;
char *newpath = NULL;
const char *path = disk->src;
/* Don't restore labels on readoly/shared disks, because
* other VMs may still be accessing these
* Alternatively we could iterate over all running
* domains and try to figure out if it is in use, but
* this would not work for clustered filesystems, since
* we can't see running VMs using the file on other nodes
* Safest bet is thus to skip the restore step.
*/
if (disk->readonly || disk->shared)
return 0;
if ((err = virFileResolveLink(path, &newpath)) < 0) {
virReportSystemError(conn, err,
_("cannot resolve symlink %s"), path);
goto err;
}
if (stat(newpath, &buf) != 0)
goto err;
if (matchpathcon(newpath, buf.st_mode, &fcon) == 0) {
rc = SELinuxSetFilecon(conn, newpath, fcon);
}
err:
VIR_FREE(fcon);
VIR_FREE(newpath);
return rc;
}
static int
SELinuxSetSecurityImageLabel(virConnectPtr conn,
virDomainObjPtr vm,
virDomainDiskDefPtr disk)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
if (!disk->src)
return 0;
if (disk->shared) {
return SELinuxSetFilecon(conn, disk->src, default_image_context);
} else if (disk->readonly) {
return SELinuxSetFilecon(conn, disk->src, default_content_context);
} else if (secdef->imagelabel) {
return SELinuxSetFilecon(conn, disk->src, secdef->imagelabel);
}
return 0;
}
static int
SELinuxRestoreSecurityLabel(virConnectPtr conn,
virDomainObjPtr vm)
{
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int i;
int rc = 0;
if (secdef->imagelabel) {
for (i = 0 ; i < vm->def->ndisks ; i++) {
if (SELinuxRestoreSecurityImageLabel(conn, vm->def->disks[i]) < 0)
rc = -1;
}
VIR_FREE(secdef->model);
VIR_FREE(secdef->label);
context_t con = context_new(secdef->imagelabel);
if (con) {
mcsRemove(context_range_get(con));
context_free(con);
}
VIR_FREE(secdef->imagelabel);
}
return rc;
}
static int
SELinuxSecurityVerify(virConnectPtr conn, virDomainDefPtr def)
{
const virSecurityLabelDefPtr secdef = &def->seclabel;
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) {
if (security_check_context(secdef->label) != 0) {
virSecurityReportError(conn, VIR_ERR_XML_ERROR,
_("Invalid security label %s"), secdef->label);
return -1;
}
}
return 0;
}
static int
SELinuxSetSecurityLabel(virConnectPtr conn,
virSecurityDriverPtr drv,
virDomainObjPtr vm)
{
/* TODO: verify DOI */
const virSecurityLabelDefPtr secdef = &vm->def->seclabel;
int i;
char ebuf[1024];
if (!STREQ(drv->name, secdef->model)) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: security label driver mismatch: "
"\'%s\' model configured for domain, but "
"hypervisor driver is \'%s\'."),
__func__, secdef->model, drv->name);
if (security_getenforce() == 1)
return -1;
}
if (setexeccon(secdef->label) == -1) {
virSecurityReportError(conn, VIR_ERR_ERROR,
_("%s: unable to set security context "
"'\%s\': %s."), __func__, secdef->label,
virStrerror(errno, ebuf, sizeof ebuf));
if (security_getenforce() == 1)
return -1;
}
if (secdef->imagelabel) {
for (i = 0 ; i < vm->def->ndisks ; i++) {
if (SELinuxSetSecurityImageLabel(conn, vm, vm->def->disks[i]) < 0)
return -1;
}
}
return 0;
}
virSecurityDriver virSELinuxSecurityDriver = {
.name = SECURITY_SELINUX_NAME,
.probe = SELinuxSecurityDriverProbe,
.open = SELinuxSecurityDriverOpen,
.domainSecurityVerify = SELinuxSecurityVerify,
.domainSetSecurityImageLabel = SELinuxSetSecurityImageLabel,
.domainRestoreSecurityImageLabel = SELinuxRestoreSecurityImageLabel,
.domainGenSecurityLabel = SELinuxGenSecurityLabel,
.domainReserveSecurityLabel = SELinuxReserveSecurityLabel,
.domainGetSecurityLabel = SELinuxGetSecurityLabel,
.domainRestoreSecurityLabel = SELinuxRestoreSecurityLabel,
.domainSetSecurityLabel = SELinuxSetSecurityLabel,
};