/*
* Copyright (C) 2008-2014 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.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* .
*
* Authors:
* James Morris
* Dan Walsh
*
* SELinux security driver.
*/
#include
#include
#include
#include
#include
#include
#if HAVE_SELINUX_LABEL_H
# include
#endif
#include "security_driver.h"
#include "security_selinux.h"
#include "virerror.h"
#include "viralloc.h"
#include "virlog.h"
#include "virmdev.h"
#include "virpci.h"
#include "virusb.h"
#include "virscsi.h"
#include "virscsivhost.h"
#include "virstoragefile.h"
#include "virfile.h"
#include "virhash.h"
#include "virrandom.h"
#include "virconf.h"
#include "virtpm.h"
#include "virstring.h"
#define VIR_FROM_THIS VIR_FROM_SECURITY
VIR_LOG_INIT("security.security_selinux");
#define MAX_CONTEXT 1024
typedef struct _virSecuritySELinuxData virSecuritySELinuxData;
typedef virSecuritySELinuxData *virSecuritySELinuxDataPtr;
struct _virSecuritySELinuxData {
char *domain_context;
char *alt_domain_context;
char *file_context;
char *content_context;
virHashTablePtr mcs;
bool skipAllLabel;
#if HAVE_SELINUX_LABEL_H
struct selabel_handle *label_handle;
#endif
};
/* Data structure to pass to various callbacks so we have everything we need */
typedef struct _virSecuritySELinuxCallbackData virSecuritySELinuxCallbackData;
typedef virSecuritySELinuxCallbackData *virSecuritySELinuxCallbackDataPtr;
struct _virSecuritySELinuxCallbackData {
virSecurityManagerPtr mgr;
virDomainDefPtr def;
};
typedef struct _virSecuritySELinuxContextItem virSecuritySELinuxContextItem;
typedef virSecuritySELinuxContextItem *virSecuritySELinuxContextItemPtr;
struct _virSecuritySELinuxContextItem {
char *path;
char *tcon;
bool optional;
};
typedef struct _virSecuritySELinuxContextList virSecuritySELinuxContextList;
typedef virSecuritySELinuxContextList *virSecuritySELinuxContextListPtr;
struct _virSecuritySELinuxContextList {
bool privileged;
virSecuritySELinuxContextItemPtr *items;
size_t nItems;
};
#define SECURITY_SELINUX_VOID_DOI "0"
#define SECURITY_SELINUX_NAME "selinux"
static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainTPMDefPtr tpm);
virThreadLocal contextList;
static void
virSecuritySELinuxContextItemFree(virSecuritySELinuxContextItemPtr item)
{
if (!item)
return;
VIR_FREE(item->path);
VIR_FREE(item->tcon);
VIR_FREE(item);
}
static int
virSecuritySELinuxContextListAppend(virSecuritySELinuxContextListPtr list,
const char *path,
const char *tcon,
bool optional)
{
int ret = -1;
virSecuritySELinuxContextItemPtr item = NULL;
if (VIR_ALLOC(item) < 0)
return -1;
if (VIR_STRDUP(item->path, path) < 0 || VIR_STRDUP(item->tcon, tcon) < 0)
goto cleanup;
item->optional = optional;
if (VIR_APPEND_ELEMENT(list->items, list->nItems, item) < 0)
goto cleanup;
ret = 0;
cleanup:
virSecuritySELinuxContextItemFree(item);
return ret;
}
static void
virSecuritySELinuxContextListFree(void *opaque)
{
virSecuritySELinuxContextListPtr list = opaque;
size_t i;
if (!list)
return;
for (i = 0; i < list->nItems; i++)
virSecuritySELinuxContextItemFree(list->items[i]);
VIR_FREE(list);
}
/**
* virSecuritySELinuxTransactionAppend:
* @path: Path to chown
* @tcon: target context
* @optional: true if setting @tcon is optional
*
* Appends an entry onto transaction list.
*
* Returns: 1 in case of successful append
* 0 if there is no transaction enabled
* -1 otherwise.
*/
static int
virSecuritySELinuxTransactionAppend(const char *path,
const char *tcon,
bool optional)
{
virSecuritySELinuxContextListPtr list;
list = virThreadLocalGet(&contextList);
if (!list)
return 0;
if (virSecuritySELinuxContextListAppend(list, path, tcon, optional) < 0)
return -1;
return 1;
}
static int virSecuritySELinuxSetFileconHelper(const char *path,
const char *tcon,
bool optional,
bool privileged);
/**
* virSecuritySELinuxTransactionRun:
* @pid: process pid
* @opaque: opaque data
*
* This is the callback that runs in the same namespace as the domain we are
* relabelling. For given transaction (@opaque) it relabels all the paths on
* the list.
*
* Returns: 0 on success
* -1 otherwise.
*/
static int
virSecuritySELinuxTransactionRun(pid_t pid ATTRIBUTE_UNUSED,
void *opaque)
{
virSecuritySELinuxContextListPtr list = opaque;
size_t i;
for (i = 0; i < list->nItems; i++) {
virSecuritySELinuxContextItemPtr item = list->items[i];
/* TODO Implement rollback */
if (virSecuritySELinuxSetFileconHelper(item->path,
item->tcon,
item->optional,
list->privileged) < 0)
return -1;
}
return 0;
}
/*
* Returns 0 on success, 1 if already reserved, or -1 on fatal error
*/
static int
virSecuritySELinuxMCSAdd(virSecurityManagerPtr mgr,
const char *mcs)
{
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
if (virHashLookup(data->mcs, mcs))
return 1;
if (virHashAddEntry(data->mcs, mcs, (void*)0x1) < 0)
return -1;
return 0;
}
static void
virSecuritySELinuxMCSRemove(virSecurityManagerPtr mgr,
const char *mcs)
{
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
virHashRemoveEntry(data->mcs, mcs);
}
static char *
virSecuritySELinuxMCSFind(virSecurityManagerPtr mgr,
const char *sens,
int catMin,
int catMax)
{
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
int catRange;
char *mcs = NULL;
/* +1 since virRandomInt range is exclusive of the upper bound */
catRange = (catMax - catMin) + 1;
if (catRange < 8) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Category range c%d-c%d too small"),
catMin, catMax);
return NULL;
}
VIR_DEBUG("Using sensitivity level '%s' cat min %d max %d range %d",
sens, catMin, catMax, catRange);
for (;;) {
int c1 = virRandomInt(catRange);
int c2 = virRandomInt(catRange);
VIR_DEBUG("Try cat %s:c%d,c%d", sens, c1 + catMin, c2 + catMin);
if (c1 == c2) {
if (virAsprintf(&mcs, "%s:c%d", sens, catMin + c1) < 0)
return NULL;
} else {
if (c1 > c2) {
int t = c1;
c1 = c2;
c2 = t;
}
if (virAsprintf(&mcs, "%s:c%d,c%d", sens, catMin + c1, catMin + c2) < 0)
return NULL;
}
if (virHashLookup(data->mcs, mcs) == NULL)
break;
VIR_FREE(mcs);
}
return mcs;
}
/*
* This needs to cope with several styles of range
*
* system_u:system_r:virtd_t
* system_u:system_r:virtd_t:s0
* system_u:system_r:virtd_t:s0-s0
* system_u:system_r:virtd_t:s0-s0:c0.c1023
*
* In the first case we'll assume s0:c0.c1023 and
* in the next two cases, we'll assume c0.c1023 for
* the category part, since that's what we're really
* interested in. This won't work in Enforcing mode,
* but will prevent libvirtd breaking in Permissive
* mode when run with a weird process label.
*/
static int
virSecuritySELinuxMCSGetProcessRange(char **sens,
int *catMin,
int *catMax)
{
security_context_t ourSecContext = NULL;
context_t ourContext = NULL;
char *cat = NULL;
char *tmp;
const char *contextRange;
int ret = -1;
if (getcon_raw(&ourSecContext) < 0) {
virReportSystemError(errno, "%s",
_("Unable to get current process SELinux context"));
goto cleanup;
}
if (!(ourContext = context_new(ourSecContext))) {
virReportSystemError(errno,
_("Unable to parse current SELinux context '%s'"),
ourSecContext);
goto cleanup;
}
if (!(contextRange = context_range_get(ourContext)))
contextRange = "s0";
if (VIR_STRDUP(*sens, contextRange) < 0)
goto cleanup;
/* Find and blank out the category part (if any) */
tmp = strchr(*sens, ':');
if (tmp) {
*tmp = '\0';
cat = tmp + 1;
}
/* Find and blank out the sensitivity upper bound */
if ((tmp = strchr(*sens, '-')))
*tmp = '\0';
/* sens now just contains the sensitivity lower bound */
/* If there was no category part, just assume c0.c1023 */
if (!cat) {
*catMin = 0;
*catMax = 1023;
ret = 0;
goto cleanup;
}
/* Find & extract category min */
tmp = cat;
if (tmp[0] != 'c') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse category in %s"),
cat);
goto cleanup;
}
tmp++;
if (virStrToLong_i(tmp, &tmp, 10, catMin) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse category in %s"),
cat);
goto cleanup;
}
/* We *must* have a pair of categories otherwise
* there's no range to allocate VM categories from */
if (!tmp[0]) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("No category range available"));
goto cleanup;
}
/* Find & extract category max (if any) */
if (tmp[0] != '.') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse category in %s"),
cat);
goto cleanup;
}
tmp++;
if (tmp[0] != 'c') {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse category in %s"),
cat);
goto cleanup;
}
tmp++;
if (virStrToLong_i(tmp, &tmp, 10, catMax) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("Cannot parse category in %s"),
cat);
goto cleanup;
}
ret = 0;
cleanup:
if (ret < 0)
VIR_FREE(*sens);
freecon(ourSecContext);
context_free(ourContext);
return ret;
}
static char *
virSecuritySELinuxContextAddRange(security_context_t src,
security_context_t dst)
{
char *str = NULL;
char *ret = NULL;
context_t srccon = NULL;
context_t dstcon = NULL;
if (!src || !dst)
return ret;
if (!(srccon = context_new(src)) || !(dstcon = context_new(dst))) {
virReportSystemError(errno, "%s",
_("unable to allocate security context"));
goto cleanup;
}
if (context_range_set(dstcon, context_range_get(srccon)) == -1) {
virReportSystemError(errno,
_("unable to set security context range '%s'"), dst);
goto cleanup;
}
if (!(str = context_str(dstcon))) {
virReportSystemError(errno, "%s",
_("Unable to format SELinux context"));
goto cleanup;
}
ignore_value(VIR_STRDUP(ret, str));
cleanup:
if (srccon) context_free(srccon);
if (dstcon) context_free(dstcon);
return ret;
}
static char *
virSecuritySELinuxGenNewContext(const char *basecontext,
const char *mcs,
bool isObjectContext)
{
context_t context = NULL;
char *ret = NULL;
char *str;
security_context_t ourSecContext = NULL;
context_t ourContext = NULL;
VIR_DEBUG("basecontext=%s mcs=%s isObjectContext=%d",
basecontext, mcs, isObjectContext);
if (getcon_raw(&ourSecContext) < 0) {
virReportSystemError(errno, "%s",
_("Unable to get current process SELinux context"));
goto cleanup;
}
if (!(ourContext = context_new(ourSecContext))) {
virReportSystemError(errno,
_("Unable to parse current SELinux context '%s'"),
ourSecContext);
goto cleanup;
}
VIR_DEBUG("process=%s", ourSecContext);
if (!(context = context_new(basecontext))) {
virReportSystemError(errno,
_("Unable to parse base SELinux context '%s'"),
basecontext);
goto cleanup;
}
if (context_user_set(context,
context_user_get(ourContext)) != 0) {
virReportSystemError(errno,
_("Unable to set SELinux context user '%s'"),
context_user_get(ourContext));
goto cleanup;
}
if (!isObjectContext &&
context_role_set(context,
context_role_get(ourContext)) != 0) {
virReportSystemError(errno,
_("Unable to set SELinux context role '%s'"),
context_role_get(ourContext));
goto cleanup;
}
if (context_range_set(context, mcs) != 0) {
virReportSystemError(errno,
_("Unable to set SELinux context MCS '%s'"),
mcs);
goto cleanup;
}
if (!(str = context_str(context))) {
virReportSystemError(errno, "%s",
_("Unable to format SELinux context"));
goto cleanup;
}
if (VIR_STRDUP(ret, str) < 0)
goto cleanup;
VIR_DEBUG("Generated context '%s'", ret);
cleanup:
freecon(ourSecContext);
context_free(ourContext);
context_free(context);
return ret;
}
#ifdef HAVE_SELINUX_LXC_CONTEXTS_PATH
static int
virSecuritySELinuxLXCInitialize(virSecurityManagerPtr mgr)
{
virConfPtr selinux_conf;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
data->skipAllLabel = true;
# if HAVE_SELINUX_LABEL_H
data->label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
if (!data->label_handle) {
virReportSystemError(errno, "%s",
_("cannot open SELinux label_handle"));
return -1;
}
# endif
if (!(selinux_conf = virConfReadFile(selinux_lxc_contexts_path(), 0)))
goto error;
if (virConfGetValueString(selinux_conf, "process", &data->domain_context) < 0)
goto error;
if (!data->domain_context) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing 'process' value in selinux lxc contexts file '%s'"),
selinux_lxc_contexts_path());
goto error;
}
if (virConfGetValueString(selinux_conf, "file", &data->file_context) < 0)
goto error;
if (!data->file_context) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing 'file' value in selinux lxc contexts file '%s'"),
selinux_lxc_contexts_path());
goto error;
}
if (virConfGetValueString(selinux_conf, "content", &data->content_context) < 0)
goto error;
if (!data->content_context) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("missing 'content' value in selinux lxc contexts file '%s'"),
selinux_lxc_contexts_path());
goto error;
}
if (!(data->mcs = virHashCreate(10, NULL)))
goto error;
virConfFree(selinux_conf);
return 0;
error:
# if HAVE_SELINUX_LABEL_H
selabel_close(data->label_handle);
data->label_handle = NULL;
# endif
virConfFree(selinux_conf);
VIR_FREE(data->domain_context);
VIR_FREE(data->file_context);
VIR_FREE(data->content_context);
virHashFree(data->mcs);
return -1;
}
#else
static int
virSecuritySELinuxLXCInitialize(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
virReportSystemError(ENOSYS, "%s",
_("libselinux does not support LXC contexts path"));
return -1;
}
#endif
static int
virSecuritySELinuxQEMUInitialize(virSecurityManagerPtr mgr)
{
char *ptr;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
data->skipAllLabel = false;
#if HAVE_SELINUX_LABEL_H
data->label_handle = selabel_open(SELABEL_CTX_FILE, NULL, 0);
if (!data->label_handle) {
virReportSystemError(errno, "%s",
_("cannot open SELinux label_handle"));
return -1;
}
#endif
if (virFileReadAll(selinux_virtual_domain_context_path(), MAX_CONTEXT, &(data->domain_context)) < 0) {
virReportSystemError(errno,
_("cannot read SELinux virtual domain context file '%s'"),
selinux_virtual_domain_context_path());
goto error;
}
ptr = strchrnul(data->domain_context, '\n');
if (ptr && *ptr == '\n') {
*ptr = '\0';
ptr++;
if (*ptr != '\0') {
if (VIR_STRDUP(data->alt_domain_context, ptr) < 0)
goto error;
ptr = strchrnul(data->alt_domain_context, '\n');
if (ptr && *ptr == '\n')
*ptr = '\0';
}
}
VIR_DEBUG("Loaded domain context '%s', alt domain context '%s'",
data->domain_context, NULLSTR(data->alt_domain_context));
if (virFileReadAll(selinux_virtual_image_context_path(), 2*MAX_CONTEXT, &(data->file_context)) < 0) {
virReportSystemError(errno,
_("cannot read SELinux virtual image context file %s"),
selinux_virtual_image_context_path());
goto error;
}
ptr = strchrnul(data->file_context, '\n');
if (ptr && *ptr == '\n') {
*ptr = '\0';
if (VIR_STRDUP(data->content_context, ptr + 1) < 0)
goto error;
ptr = strchrnul(data->content_context, '\n');
if (ptr && *ptr == '\n')
*ptr = '\0';
}
VIR_DEBUG("Loaded file context '%s', content context '%s'",
data->file_context, data->content_context);
if (!(data->mcs = virHashCreate(10, NULL)))
goto error;
return 0;
error:
#if HAVE_SELINUX_LABEL_H
selabel_close(data->label_handle);
data->label_handle = NULL;
#endif
VIR_FREE(data->domain_context);
VIR_FREE(data->alt_domain_context);
VIR_FREE(data->file_context);
VIR_FREE(data->content_context);
virHashFree(data->mcs);
return -1;
}
static int
virSecuritySELinuxInitialize(virSecurityManagerPtr mgr)
{
VIR_DEBUG("SELinuxInitialize %s", virSecurityManagerGetDriver(mgr));
if (virThreadLocalInit(&contextList,
virSecuritySELinuxContextListFree) < 0) {
virReportSystemError(errno, "%s",
_("Unable to initialize thread local variable"));
return -1;
}
if (STREQ(virSecurityManagerGetDriver(mgr), "LXC")) {
return virSecuritySELinuxLXCInitialize(mgr);
} else {
return virSecuritySELinuxQEMUInitialize(mgr);
}
}
static int
virSecuritySELinuxGenLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def)
{
int rc = -1;
char *mcs = NULL;
char *scontext = NULL;
context_t ctx = NULL;
const char *range;
virSecurityLabelDefPtr seclabel;
virSecuritySELinuxDataPtr data;
const char *baselabel;
char *sens = NULL;
int catMin, catMax;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
data = virSecurityManagerGetPrivateData(mgr);
VIR_DEBUG("label=%s", virSecurityManagerGetDriver(mgr));
if (seclabel->type == VIR_DOMAIN_SECLABEL_DYNAMIC &&
seclabel->label) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("security label already defined for VM"));
return rc;
}
if (seclabel->imagelabel) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("security image label already defined for VM"));
return rc;
}
if (seclabel->model &&
STRNEQ(seclabel->model, SECURITY_SELINUX_NAME)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label model %s is not supported with selinux"),
seclabel->model);
return rc;
}
VIR_DEBUG("type=%d", seclabel->type);
switch (seclabel->type) {
case VIR_DOMAIN_SECLABEL_STATIC:
if (!(ctx = context_new(seclabel->label))) {
virReportSystemError(errno,
_("unable to allocate socket security context '%s'"),
seclabel->label);
return rc;
}
if (!(range = context_range_get(ctx))) {
virReportSystemError(errno, "%s", _("unable to get selinux context range"));
goto cleanup;
}
if (VIR_STRDUP(mcs, range) < 0)
goto cleanup;
break;
case VIR_DOMAIN_SECLABEL_DYNAMIC:
if (virSecuritySELinuxMCSGetProcessRange(&sens,
&catMin,
&catMax) < 0)
goto cleanup;
if (!(mcs = virSecuritySELinuxMCSFind(mgr,
sens,
catMin,
catMax)))
goto cleanup;
if (virSecuritySELinuxMCSAdd(mgr, mcs) < 0)
goto cleanup;
baselabel = seclabel->baselabel;
if (!baselabel) {
if (def->virtType == VIR_DOMAIN_VIRT_QEMU) {
if (data->alt_domain_context == NULL) {
static bool warned;
if (!warned) {
VIR_WARN("SELinux policy does not define a domain type for QEMU TCG. "
"Guest startup may be denied due to missing 'execmem' privilege "
"unless the 'virt_use_execmem' policy boolean is enabled");
warned = true;
}
baselabel = data->domain_context;
} else {
baselabel = data->alt_domain_context;
}
} else {
baselabel = data->domain_context;
}
}
seclabel->label = virSecuritySELinuxGenNewContext(baselabel, mcs, false);
if (!seclabel->label)
goto cleanup;
break;
case VIR_DOMAIN_SECLABEL_NONE:
if (virSecuritySELinuxMCSGetProcessRange(&sens,
&catMin,
&catMax) < 0)
goto cleanup;
if (VIR_STRDUP(mcs, sens) < 0)
goto cleanup;
break;
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unexpected security label type '%s'"),
virDomainSeclabelTypeToString(seclabel->type));
goto cleanup;
}
/* always generate a image label, needed to label new objects */
seclabel->imagelabel = virSecuritySELinuxGenNewContext(data->file_context,
mcs,
true);
if (!seclabel->imagelabel)
goto cleanup;
if (!seclabel->model &&
VIR_STRDUP(seclabel->model, SECURITY_SELINUX_NAME) < 0)
goto cleanup;
rc = 0;
cleanup:
if (rc != 0) {
if (seclabel->type == VIR_DOMAIN_SECLABEL_DYNAMIC)
VIR_FREE(seclabel->label);
VIR_FREE(seclabel->imagelabel);
if (seclabel->type == VIR_DOMAIN_SECLABEL_DYNAMIC &&
!seclabel->baselabel)
VIR_FREE(seclabel->model);
}
if (ctx)
context_free(ctx);
VIR_FREE(scontext);
VIR_FREE(mcs);
VIR_FREE(sens);
VIR_DEBUG("model=%s label=%s imagelabel=%s baselabel=%s",
NULLSTR(seclabel->model),
NULLSTR(seclabel->label),
NULLSTR(seclabel->imagelabel),
NULLSTR(seclabel->baselabel));
return rc;
}
static int
virSecuritySELinuxReserveLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
pid_t pid)
{
security_context_t pctx;
context_t ctx = NULL;
const char *mcs;
int rv;
virSecurityLabelDefPtr seclabel;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!seclabel ||
seclabel->type == VIR_DOMAIN_SECLABEL_NONE ||
seclabel->type == VIR_DOMAIN_SECLABEL_STATIC)
return 0;
if (getpidcon_raw(pid, &pctx) == -1) {
virReportSystemError(errno,
_("unable to get PID %d security context"), pid);
return -1;
}
ctx = context_new(pctx);
freecon(pctx);
if (!ctx)
goto error;
mcs = context_range_get(ctx);
if (!mcs)
goto error;
if ((rv = virSecuritySELinuxMCSAdd(mgr, mcs)) < 0)
goto error;
if (rv == 1) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("MCS level for existing domain label %s already reserved"),
(char*)pctx);
goto error;
}
context_free(ctx);
return 0;
error:
context_free(ctx);
return -1;
}
static int
virSecuritySELinuxDriverProbe(const char *virtDriver)
{
if (is_selinux_enabled() <= 0)
return SECURITY_DRIVER_DISABLE;
if (virtDriver && STREQ(virtDriver, "LXC")) {
#if HAVE_SELINUX_LXC_CONTEXTS_PATH
if (!virFileExists(selinux_lxc_contexts_path()))
#endif
return SECURITY_DRIVER_DISABLE;
}
return SECURITY_DRIVER_ENABLE;
}
static int
virSecuritySELinuxDriverOpen(virSecurityManagerPtr mgr)
{
return virSecuritySELinuxInitialize(mgr);
}
static int
virSecuritySELinuxDriverClose(virSecurityManagerPtr mgr)
{
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
if (!data)
return 0;
#if HAVE_SELINUX_LABEL_H
if (data->label_handle)
selabel_close(data->label_handle);
#endif
virHashFree(data->mcs);
VIR_FREE(data->domain_context);
VIR_FREE(data->alt_domain_context);
VIR_FREE(data->file_context);
VIR_FREE(data->content_context);
return 0;
}
static const char *
virSecuritySELinuxGetModel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
return SECURITY_SELINUX_NAME;
}
static const char *
virSecuritySELinuxGetDOI(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
/*
* Where will the DOI come from? SELinux configuration, or qemu
* configuration? For the moment, we'll just set it to "0".
*/
return SECURITY_SELINUX_VOID_DOI;
}
/**
* virSecuritySELinuxTransactionStart:
* @mgr: security manager
*
* Starts a new transaction. In transaction nothing is changed context
* until TransactionCommit() is called. This is implemented as a list
* that is appended to whenever setfilecon() would be called. Since
* secdriver APIs can be called from multiple threads (to work over
* different domains) the pointer to the list is stored in thread local
* variable.
*
* Returns 0 on success,
* -1 otherwise.
*/
static int
virSecuritySELinuxTransactionStart(virSecurityManagerPtr mgr)
{
bool privileged = virSecurityManagerGetPrivileged(mgr);
virSecuritySELinuxContextListPtr list;
list = virThreadLocalGet(&contextList);
if (list) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("Another relabel transaction is already started"));
return -1;
}
if (VIR_ALLOC(list) < 0)
return -1;
list->privileged = privileged;
if (virThreadLocalSet(&contextList, list) < 0) {
virReportSystemError(errno, "%s",
_("Unable to set thread local variable"));
VIR_FREE(list);
return -1;
}
return 0;
}
/**
* virSecuritySELinuxTransactionCommit:
* @mgr: security manager
* @pid: domain's PID
*
* If @pis is not -1 then enter the @pid namespace (usually @pid refers
* to a domain) and perform all the sefilecon()-s on the list. If @pid
* is -1 then the transaction is performed in the namespace of the
* caller.
*
* Note that the transaction is also freed, therefore new one has to be
* started after successful return from this function. Also it is
* considered as error if there's no transaction set and this function
* is called.
*
* Returns: 0 on success,
* -1 otherwise.
*/
static int
virSecuritySELinuxTransactionCommit(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
pid_t pid)
{
virSecuritySELinuxContextListPtr list;
int ret = 0;
list = virThreadLocalGet(&contextList);
if (!list)
return 0;
if (virThreadLocalSet(&contextList, NULL) < 0) {
virReportSystemError(errno, "%s",
_("Unable to clear thread local variable"));
goto cleanup;
}
if ((pid == -1 &&
virSecuritySELinuxTransactionRun(pid, list) < 0) ||
(pid != -1 &&
virProcessRunInMountNamespace(pid,
virSecuritySELinuxTransactionRun,
list) < 0))
goto cleanup;
ret = 0;
cleanup:
virSecuritySELinuxContextListFree(list);
return ret;
}
/**
* virSecuritySELinuxTransactionAbort:
* @mgr: security manager
*
* Cancels and frees any out standing transaction.
*/
static void
virSecuritySELinuxTransactionAbort(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED)
{
virSecuritySELinuxContextListPtr list;
list = virThreadLocalGet(&contextList);
if (!list)
return;
if (virThreadLocalSet(&contextList, NULL) < 0)
VIR_DEBUG("Unable to clear thread local variable");
virSecuritySELinuxContextListFree(list);
}
static int
virSecuritySELinuxGetProcessLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def ATTRIBUTE_UNUSED,
pid_t pid,
virSecurityLabelPtr sec)
{
security_context_t ctx;
if (getpidcon_raw(pid, &ctx) == -1) {
virReportSystemError(errno,
_("unable to get PID %d security context"),
pid);
return -1;
}
if (strlen((char *)ctx) >= VIR_SECURITY_LABEL_BUFLEN) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label exceeds "
"maximum length: %d"),
VIR_SECURITY_LABEL_BUFLEN - 1);
freecon(ctx);
return -1;
}
strcpy(sec->label, (char *)ctx);
freecon(ctx);
VIR_DEBUG("label=%s", sec->label);
sec->enforcing = security_getenforce();
if (sec->enforcing == -1) {
virReportSystemError(errno, "%s",
_("error calling security_getenforce()"));
return -1;
}
return 0;
}
/* Attempt to change the label of PATH to TCON. If OPTIONAL is true,
* return 1 if labelling was not possible. Otherwise, require a label
* change, and return 0 for success, -1 for failure. */
static int
virSecuritySELinuxSetFileconHelper(const char *path, const char *tcon,
bool optional, bool privileged)
{
security_context_t econ;
int rc;
/* Be aware that this function might run in a separate process.
* Therefore, any driver state changes would be thrown away. */
if ((rc = virSecuritySELinuxTransactionAppend(path, tcon, optional)) < 0)
return -1;
else if (rc > 0)
return 0;
VIR_INFO("Setting SELinux context on '%s' to '%s'", path, tcon);
if (setfilecon_raw(path, (VIR_SELINUX_CTX_CONST char *)tcon) < 0) {
int setfilecon_errno = errno;
if (getfilecon_raw(path, &econ) >= 0) {
if (STREQ(tcon, econ)) {
freecon(econ);
/* It's alright, there's nothing to change anyway. */
return optional ? 1 : 0;
}
freecon(econ);
}
/* If the error complaint is related to an image hosted on a (possibly
* read-only) NFS mount, or a usbfs/sysfs filesystem not supporting
* labelling, then just ignore it & hope for the best. The user
* hopefully sets one of the necessary SELinux virt_use_{nfs,usb,pci}
* boolean tunables to allow it ...
*/
VIR_WARNINGS_NO_WLOGICALOP_EQUAL_EXPR
if (setfilecon_errno != EOPNOTSUPP && setfilecon_errno != ENOTSUP &&
setfilecon_errno != EROFS) {
VIR_WARNINGS_RESET
virReportSystemError(setfilecon_errno,
_("unable to set security context '%s' on '%s'"),
tcon, path);
/* However, don't claim error if SELinux is in Enforcing mode and
* we are running as unprivileged user and we really did see EPERM.
* Otherwise we want to return error if SELinux is Enforcing. */
if (security_getenforce() == 1 && (setfilecon_errno != EPERM || privileged))
return -1;
} else {
const char *msg;
if (virFileIsSharedFSType(path, VIR_FILE_SHFS_NFS) == 1 &&
security_get_boolean_active("virt_use_nfs") != 1) {
msg = _("Setting security context '%s' on '%s' not supported. "
"Consider setting virt_use_nfs");
if (security_getenforce() == 1)
VIR_WARN(msg, tcon, path);
else
VIR_INFO(msg, tcon, path);
} else {
VIR_INFO("Setting security context '%s' on '%s' not supported",
tcon, path);
}
if (optional)
return 1;
}
}
return 0;
}
static int
virSecuritySELinuxSetFileconOptional(virSecurityManagerPtr mgr,
const char *path, const char *tcon)
{
bool privileged = virSecurityManagerGetPrivileged(mgr);
return virSecuritySELinuxSetFileconHelper(path, tcon, true, privileged);
}
static int
virSecuritySELinuxSetFilecon(virSecurityManagerPtr mgr,
const char *path, const char *tcon)
{
bool privileged = virSecurityManagerGetPrivileged(mgr);
return virSecuritySELinuxSetFileconHelper(path, tcon, false, privileged);
}
static int
virSecuritySELinuxFSetFilecon(int fd, char *tcon)
{
security_context_t econ;
VIR_INFO("Setting SELinux context on fd %d to '%s'", fd, tcon);
if (fsetfilecon_raw(fd, tcon) < 0) {
int fsetfilecon_errno = errno;
if (fgetfilecon_raw(fd, &econ) >= 0) {
if (STREQ(tcon, econ)) {
freecon(econ);
/* It's alright, there's nothing to change anyway. */
return 0;
}
freecon(econ);
}
/* if the error complaint is related to an image hosted on
* an nfs mount, or a usbfs/sysfs filesystem not supporting
* labelling, then just ignore it & hope for the best.
* The user hopefully set one of the necessary SELinux
* virt_use_{nfs,usb,pci} boolean tunables to allow it...
*/
if (fsetfilecon_errno != EOPNOTSUPP) {
virReportSystemError(fsetfilecon_errno,
_("unable to set security context '%s' on fd %d"),
tcon, fd);
if (security_getenforce() == 1)
return -1;
} else {
VIR_INFO("Setting security context '%s' on fd %d not supported",
tcon, fd);
}
}
return 0;
}
/* Set fcon to the appropriate label for path and mode, or return -1. */
static int
getContext(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
const char *newpath, mode_t mode, security_context_t *fcon)
{
#if HAVE_SELINUX_LABEL_H
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
return selabel_lookup_raw(data->label_handle, fcon, newpath, mode);
#else
return matchpathcon(newpath, mode, fcon);
#endif
}
/* This method shouldn't raise errors, since they'll overwrite
* errors that the caller(s) are already dealing with */
static int
virSecuritySELinuxRestoreFileLabel(virSecurityManagerPtr mgr,
const char *path)
{
struct stat buf;
security_context_t fcon = NULL;
int rc = -1;
char *newpath = NULL;
char ebuf[1024];
/* Some paths are auto-generated, so let's be safe here and do
* nothing if nothing is needed.
*/
if (!path)
return 0;
VIR_INFO("Restoring SELinux context on '%s'", path);
if (virFileResolveLink(path, &newpath) < 0) {
VIR_WARN("cannot resolve symlink %s: %s", path,
virStrerror(errno, ebuf, sizeof(ebuf)));
goto cleanup;
}
if (stat(newpath, &buf) != 0) {
VIR_WARN("cannot stat %s: %s", newpath,
virStrerror(errno, ebuf, sizeof(ebuf)));
goto cleanup;
}
if (getContext(mgr, newpath, buf.st_mode, &fcon) < 0) {
/* Any user created path likely does not have a default label,
* which makes this an expected non error
*/
VIR_WARN("cannot lookup default selinux label for %s", newpath);
rc = 0;
} else {
rc = virSecuritySELinuxSetFilecon(mgr, newpath, fcon);
}
cleanup:
freecon(fcon);
VIR_FREE(newpath);
return rc;
}
static int
virSecuritySELinuxSetInputLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainInputDefPtr input)
{
virSecurityLabelDefPtr seclabel;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
switch ((virDomainInputType)input->type) {
case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
if (virSecuritySELinuxSetFilecon(mgr, input->source.evdev,
seclabel->imagelabel) < 0)
return -1;
break;
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
case VIR_DOMAIN_INPUT_TYPE_TABLET:
case VIR_DOMAIN_INPUT_TYPE_KBD:
case VIR_DOMAIN_INPUT_TYPE_LAST:
break;
}
return 0;
}
static int
virSecuritySELinuxRestoreInputLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainInputDefPtr input)
{
int rc = 0;
virSecurityLabelDefPtr seclabel;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
switch ((virDomainInputType)input->type) {
case VIR_DOMAIN_INPUT_TYPE_PASSTHROUGH:
rc = virSecuritySELinuxRestoreFileLabel(mgr, input->source.evdev);
break;
case VIR_DOMAIN_INPUT_TYPE_MOUSE:
case VIR_DOMAIN_INPUT_TYPE_TABLET:
case VIR_DOMAIN_INPUT_TYPE_KBD:
case VIR_DOMAIN_INPUT_TYPE_LAST:
break;
}
return rc;
}
static int
virSecuritySELinuxSetMemoryLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainMemoryDefPtr mem)
{
virSecurityLabelDefPtr seclabel;
switch ((virDomainMemoryModel) mem->model) {
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!seclabel || !seclabel->relabel)
return 0;
if (virSecuritySELinuxSetFilecon(mgr, mem->nvdimmPath,
seclabel->imagelabel) < 0)
return -1;
break;
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
break;
}
return 0;
}
static int
virSecuritySELinuxRestoreMemoryLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainMemoryDefPtr mem)
{
int ret = -1;
virSecurityLabelDefPtr seclabel;
switch ((virDomainMemoryModel) mem->model) {
case VIR_DOMAIN_MEMORY_MODEL_NVDIMM:
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!seclabel || !seclabel->relabel)
return 0;
ret = virSecuritySELinuxRestoreFileLabel(mgr, mem->nvdimmPath);
break;
case VIR_DOMAIN_MEMORY_MODEL_DIMM:
case VIR_DOMAIN_MEMORY_MODEL_NONE:
case VIR_DOMAIN_MEMORY_MODEL_LAST:
ret = 0;
break;
}
return ret;
}
static int
virSecuritySELinuxSetTPMFileLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainTPMDefPtr tpm)
{
int rc;
virSecurityLabelDefPtr seclabel;
char *cancel_path;
const char *tpmdev;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
switch (tpm->type) {
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
tpmdev = tpm->data.passthrough.source.data.file.path;
rc = virSecuritySELinuxSetFilecon(mgr, tpmdev, seclabel->imagelabel);
if (rc < 0)
return -1;
if ((cancel_path = virTPMCreateCancelPath(tpmdev)) != NULL) {
rc = virSecuritySELinuxSetFilecon(mgr,
cancel_path,
seclabel->imagelabel);
VIR_FREE(cancel_path);
if (rc < 0) {
virSecuritySELinuxRestoreTPMFileLabelInt(mgr, def, tpm);
return -1;
}
} else {
return -1;
}
break;
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
tpmdev = tpm->data.emulator.source.data.nix.path;
rc = virSecuritySELinuxSetFilecon(mgr, tpmdev, seclabel->imagelabel);
if (rc < 0)
return -1;
break;
case VIR_DOMAIN_TPM_TYPE_LAST:
break;
}
return 0;
}
static int
virSecuritySELinuxRestoreTPMFileLabelInt(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainTPMDefPtr tpm)
{
int rc = 0;
virSecurityLabelDefPtr seclabel;
char *cancel_path;
const char *tpmdev;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
switch (tpm->type) {
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
tpmdev = tpm->data.passthrough.source.data.file.path;
rc = virSecuritySELinuxRestoreFileLabel(mgr, tpmdev);
if ((cancel_path = virTPMCreateCancelPath(tpmdev)) != NULL) {
if (virSecuritySELinuxRestoreFileLabel(mgr, cancel_path) < 0)
rc = -1;
VIR_FREE(cancel_path);
}
break;
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
/* swtpm will have removed the Unix socket upon termination */
case VIR_DOMAIN_TPM_TYPE_LAST:
break;
}
return rc;
}
static int
virSecuritySELinuxRestoreImageLabelInt(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virStorageSourcePtr src,
bool migrated)
{
virSecurityLabelDefPtr seclabel;
virSecurityDeviceLabelDefPtr disk_seclabel;
if (!src->path || !virStorageSourceIsLocalStorage(src))
return 0;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
disk_seclabel = virStorageSourceGetSecurityLabelDef(src,
SECURITY_SELINUX_NAME);
if (!seclabel->relabel || (disk_seclabel && !disk_seclabel->relabel))
return 0;
/* If labelskip is true and there are no backing files, then we
* know it is safe to skip the restore. FIXME - backing files should
* be tracked in domain XML, at which point labelskip should be a
* per-file attribute instead of a disk attribute. */
if (disk_seclabel && disk_seclabel->labelskip &&
!virStorageSourceHasBacking(src))
return 0;
/* Don't restore labels on readonly/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 (src->readonly || src->shared)
return 0;
/* If we have a shared FS and are doing migration, we must not change
* ownership, because that kills access on the destination host which is
* sub-optimal for the guest VM's I/O attempts :-) */
if (migrated) {
int rc = virFileIsSharedFS(src->path);
if (rc < 0)
return -1;
if (rc == 1) {
VIR_DEBUG("Skipping image label restore on %s because FS is shared",
src->path);
return 0;
}
}
return virSecuritySELinuxRestoreFileLabel(mgr, src->path);
}
static int
virSecuritySELinuxRestoreDiskLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainDiskDefPtr disk)
{
return virSecuritySELinuxRestoreImageLabelInt(mgr, def, disk->src,
false);
}
static int
virSecuritySELinuxRestoreImageLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virStorageSourcePtr src)
{
return virSecuritySELinuxRestoreImageLabelInt(mgr, def, src, false);
}
static int
virSecuritySELinuxSetImageLabelInternal(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virStorageSourcePtr src,
virStorageSourcePtr parent)
{
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
virSecurityLabelDefPtr secdef;
virSecurityDeviceLabelDefPtr disk_seclabel;
virSecurityDeviceLabelDefPtr parent_seclabel = NULL;
int ret;
if (!src->path || !virStorageSourceIsLocalStorage(src))
return 0;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel)
return 0;
disk_seclabel = virStorageSourceGetSecurityLabelDef(src,
SECURITY_SELINUX_NAME);
if (parent)
parent_seclabel = virStorageSourceGetSecurityLabelDef(parent,
SECURITY_SELINUX_NAME);
if (disk_seclabel && (!disk_seclabel->relabel || disk_seclabel->label)) {
if (!disk_seclabel->relabel)
return 0;
ret = virSecuritySELinuxSetFilecon(mgr, src->path, disk_seclabel->label);
} else if (parent_seclabel && (!parent_seclabel->relabel || parent_seclabel->label)) {
if (!parent_seclabel->relabel)
return 0;
ret = virSecuritySELinuxSetFilecon(mgr, src->path, parent_seclabel->label);
} else if (!parent || parent == src) {
if (src->shared) {
ret = virSecuritySELinuxSetFileconOptional(mgr,
src->path,
data->file_context);
} else if (src->readonly) {
ret = virSecuritySELinuxSetFileconOptional(mgr,
src->path,
data->content_context);
} else if (secdef->imagelabel) {
ret = virSecuritySELinuxSetFileconOptional(mgr,
src->path,
secdef->imagelabel);
} else {
ret = 0;
}
} else {
ret = virSecuritySELinuxSetFileconOptional(mgr,
src->path,
data->content_context);
}
if (ret == 1 && !disk_seclabel) {
/* If we failed to set a label, but virt_use_nfs let us
* proceed anyway, then we don't need to relabel later. */
disk_seclabel = virSecurityDeviceLabelDefNew(SECURITY_SELINUX_NAME);
if (!disk_seclabel)
return -1;
disk_seclabel->labelskip = true;
if (VIR_APPEND_ELEMENT(src->seclabels, src->nseclabels,
disk_seclabel) < 0) {
virSecurityDeviceLabelDefFree(disk_seclabel);
return -1;
}
ret = 0;
}
return ret;
}
static int
virSecuritySELinuxSetImageLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virStorageSourcePtr src)
{
return virSecuritySELinuxSetImageLabelInternal(mgr, def, src, NULL);
}
static int
virSecuritySELinuxSetDiskLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainDiskDefPtr disk)
{
virStorageSourcePtr next;
for (next = disk->src; virStorageSourceIsBacking(next); next = next->backingStore) {
if (virSecuritySELinuxSetImageLabelInternal(mgr, def, next, disk->src) < 0)
return -1;
}
return 0;
}
static int
virSecuritySELinuxSetHostdevLabelHelper(const char *file, void *opaque)
{
virSecurityLabelDefPtr secdef;
virSecuritySELinuxCallbackDataPtr data = opaque;
virSecurityManagerPtr mgr = data->mgr;
virDomainDefPtr def = data->def;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (secdef == NULL)
return 0;
return virSecuritySELinuxSetFilecon(mgr, file, secdef->imagelabel);
}
static int
virSecuritySELinuxSetPCILabel(virPCIDevicePtr dev ATTRIBUTE_UNUSED,
const char *file, void *opaque)
{
return virSecuritySELinuxSetHostdevLabelHelper(file, opaque);
}
static int
virSecuritySELinuxSetUSBLabel(virUSBDevicePtr dev ATTRIBUTE_UNUSED,
const char *file, void *opaque)
{
return virSecuritySELinuxSetHostdevLabelHelper(file, opaque);
}
static int
virSecuritySELinuxSetSCSILabel(virSCSIDevicePtr dev,
const char *file, void *opaque)
{
virSecurityLabelDefPtr secdef;
virSecuritySELinuxCallbackDataPtr ptr = opaque;
virSecurityManagerPtr mgr = ptr->mgr;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
secdef = virDomainDefGetSecurityLabelDef(ptr->def, SECURITY_SELINUX_NAME);
if (secdef == NULL)
return 0;
if (virSCSIDeviceGetShareable(dev))
return virSecuritySELinuxSetFileconOptional(mgr, file,
data->file_context);
else if (virSCSIDeviceGetReadonly(dev))
return virSecuritySELinuxSetFileconOptional(mgr, file,
data->content_context);
else
return virSecuritySELinuxSetFileconOptional(mgr, file,
secdef->imagelabel);
}
static int
virSecuritySELinuxSetHostLabel(virSCSIVHostDevicePtr dev ATTRIBUTE_UNUSED,
const char *file, void *opaque)
{
return virSecuritySELinuxSetHostdevLabelHelper(file, opaque);
}
static int
virSecuritySELinuxSetHostdevSubsysLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainHostdevDefPtr dev,
const char *vroot)
{
virDomainHostdevSubsysUSBPtr usbsrc = &dev->source.subsys.u.usb;
virDomainHostdevSubsysPCIPtr pcisrc = &dev->source.subsys.u.pci;
virDomainHostdevSubsysSCSIPtr scsisrc = &dev->source.subsys.u.scsi;
virDomainHostdevSubsysSCSIVHostPtr hostsrc = &dev->source.subsys.u.scsi_host;
virDomainHostdevSubsysMediatedDevPtr mdevsrc = &dev->source.subsys.u.mdev;
virSecuritySELinuxCallbackData data = {.mgr = mgr, .def = def};
int ret = -1;
/* Like virSecuritySELinuxSetImageLabelInternal() for a networked
* disk, do nothing for an iSCSI hostdev
*/
if (dev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI)
return 0;
switch ((virDomainHostdevSubsysType)dev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
virUSBDevicePtr usb;
if (dev->missing)
return 0;
usb = virUSBDeviceNew(usbsrc->bus,
usbsrc->device,
vroot);
if (!usb)
goto done;
ret = virUSBDeviceFileIterate(usb, virSecuritySELinuxSetUSBLabel, &data);
virUSBDeviceFree(usb);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
virPCIDevicePtr pci =
virPCIDeviceNew(pcisrc->addr.domain, pcisrc->addr.bus,
pcisrc->addr.slot, pcisrc->addr.function);
if (!pci)
goto done;
if (pcisrc->backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
char *vfioGroupDev = virPCIDeviceGetIOMMUGroupDev(pci);
if (!vfioGroupDev) {
virPCIDeviceFree(pci);
goto done;
}
ret = virSecuritySELinuxSetPCILabel(pci, vfioGroupDev, &data);
VIR_FREE(vfioGroupDev);
} else {
ret = virPCIDeviceFileIterate(pci, virSecuritySELinuxSetPCILabel, &data);
}
virPCIDeviceFree(pci);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI: {
virDomainHostdevSubsysSCSIHostPtr scsihostsrc = &scsisrc->u.host;
virSCSIDevicePtr scsi =
virSCSIDeviceNew(NULL,
scsihostsrc->adapter, scsihostsrc->bus,
scsihostsrc->target, scsihostsrc->unit,
dev->readonly, dev->shareable);
if (!scsi)
goto done;
ret = virSCSIDeviceFileIterate(scsi,
virSecuritySELinuxSetSCSILabel,
&data);
virSCSIDeviceFree(scsi);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: {
virSCSIVHostDevicePtr host = virSCSIVHostDeviceNew(hostsrc->wwpn);
if (!host)
goto done;
ret = virSCSIVHostDeviceFileIterate(host,
virSecuritySELinuxSetHostLabel,
&data);
virSCSIVHostDeviceFree(host);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
char *vfiodev = NULL;
if (!(vfiodev = virMediatedDeviceGetIOMMUGroupDev(mdevsrc->uuidstr)))
goto done;
ret = virSecuritySELinuxSetHostdevLabelHelper(vfiodev, &data);
VIR_FREE(vfiodev);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
ret = 0;
break;
}
done:
return ret;
}
static int
virSecuritySELinuxSetHostdevCapsLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainHostdevDefPtr dev,
const char *vroot)
{
int ret = -1;
virSecurityLabelDefPtr secdef;
char *path;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (secdef == NULL)
return 0;
switch (dev->source.caps.type) {
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_STORAGE: {
if (vroot) {
if (virAsprintf(&path, "%s/%s", vroot,
dev->source.caps.u.storage.block) < 0)
return -1;
} else {
if (VIR_STRDUP(path, dev->source.caps.u.storage.block) < 0)
return -1;
}
ret = virSecuritySELinuxSetFilecon(mgr, path, secdef->imagelabel);
VIR_FREE(path);
break;
}
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_MISC: {
if (vroot) {
if (virAsprintf(&path, "%s/%s", vroot,
dev->source.caps.u.misc.chardev) < 0)
return -1;
} else {
if (VIR_STRDUP(path, dev->source.caps.u.misc.chardev) < 0)
return -1;
}
ret = virSecuritySELinuxSetFilecon(mgr, path, secdef->imagelabel);
VIR_FREE(path);
break;
}
default:
ret = 0;
break;
}
return ret;
}
static int
virSecuritySELinuxSetHostdevLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainHostdevDefPtr dev,
const char *vroot)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel)
return 0;
switch (dev->mode) {
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
return virSecuritySELinuxSetHostdevSubsysLabel(mgr, def, dev, vroot);
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
return virSecuritySELinuxSetHostdevCapsLabel(mgr, def, dev, vroot);
default:
return 0;
}
}
static int
virSecuritySELinuxRestorePCILabel(virPCIDevicePtr dev ATTRIBUTE_UNUSED,
const char *file,
void *opaque)
{
virSecurityManagerPtr mgr = opaque;
return virSecuritySELinuxRestoreFileLabel(mgr, file);
}
static int
virSecuritySELinuxRestoreUSBLabel(virUSBDevicePtr dev ATTRIBUTE_UNUSED,
const char *file,
void *opaque)
{
virSecurityManagerPtr mgr = opaque;
return virSecuritySELinuxRestoreFileLabel(mgr, file);
}
static int
virSecuritySELinuxRestoreSCSILabel(virSCSIDevicePtr dev,
const char *file,
void *opaque)
{
virSecurityManagerPtr mgr = opaque;
/* Don't restore labels on a shareable or readonly hostdev, because
* other VMs may still be accessing.
*/
if (virSCSIDeviceGetShareable(dev) || virSCSIDeviceGetReadonly(dev))
return 0;
return virSecuritySELinuxRestoreFileLabel(mgr, file);
}
static int
virSecuritySELinuxRestoreHostLabel(virSCSIVHostDevicePtr dev ATTRIBUTE_UNUSED,
const char *file,
void *opaque)
{
virSecurityManagerPtr mgr = opaque;
return virSecuritySELinuxRestoreFileLabel(mgr, file);
}
static int
virSecuritySELinuxRestoreHostdevSubsysLabel(virSecurityManagerPtr mgr,
virDomainHostdevDefPtr dev,
const char *vroot)
{
virDomainHostdevSubsysUSBPtr usbsrc = &dev->source.subsys.u.usb;
virDomainHostdevSubsysPCIPtr pcisrc = &dev->source.subsys.u.pci;
virDomainHostdevSubsysSCSIPtr scsisrc = &dev->source.subsys.u.scsi;
virDomainHostdevSubsysSCSIVHostPtr hostsrc = &dev->source.subsys.u.scsi_host;
virDomainHostdevSubsysMediatedDevPtr mdevsrc = &dev->source.subsys.u.mdev;
int ret = -1;
/* Like virSecuritySELinuxRestoreImageLabelInt() for a networked
* disk, do nothing for an iSCSI hostdev
*/
if (dev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI &&
scsisrc->protocol == VIR_DOMAIN_HOSTDEV_SCSI_PROTOCOL_TYPE_ISCSI)
return 0;
switch ((virDomainHostdevSubsysType)dev->source.subsys.type) {
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: {
virUSBDevicePtr usb;
if (dev->missing)
return 0;
usb = virUSBDeviceNew(usbsrc->bus,
usbsrc->device,
vroot);
if (!usb)
goto done;
ret = virUSBDeviceFileIterate(usb, virSecuritySELinuxRestoreUSBLabel, mgr);
virUSBDeviceFree(usb);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: {
virPCIDevicePtr pci =
virPCIDeviceNew(pcisrc->addr.domain, pcisrc->addr.bus,
pcisrc->addr.slot, pcisrc->addr.function);
if (!pci)
goto done;
if (pcisrc->backend == VIR_DOMAIN_HOSTDEV_PCI_BACKEND_VFIO) {
char *vfioGroupDev = virPCIDeviceGetIOMMUGroupDev(pci);
if (!vfioGroupDev) {
virPCIDeviceFree(pci);
goto done;
}
ret = virSecuritySELinuxRestorePCILabel(pci, vfioGroupDev, mgr);
VIR_FREE(vfioGroupDev);
} else {
ret = virPCIDeviceFileIterate(pci, virSecuritySELinuxRestorePCILabel, mgr);
}
virPCIDeviceFree(pci);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI: {
virDomainHostdevSubsysSCSIHostPtr scsihostsrc = &scsisrc->u.host;
virSCSIDevicePtr scsi =
virSCSIDeviceNew(NULL,
scsihostsrc->adapter, scsihostsrc->bus,
scsihostsrc->target, scsihostsrc->unit,
dev->readonly, dev->shareable);
if (!scsi)
goto done;
ret = virSCSIDeviceFileIterate(scsi, virSecuritySELinuxRestoreSCSILabel, mgr);
virSCSIDeviceFree(scsi);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_SCSI_HOST: {
virSCSIVHostDevicePtr host = virSCSIVHostDeviceNew(hostsrc->wwpn);
if (!host)
goto done;
ret = virSCSIVHostDeviceFileIterate(host,
virSecuritySELinuxRestoreHostLabel,
mgr);
virSCSIVHostDeviceFree(host);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_MDEV: {
char *vfiodev = NULL;
if (!(vfiodev = virMediatedDeviceGetIOMMUGroupDev(mdevsrc->uuidstr)))
goto done;
ret = virSecuritySELinuxRestoreFileLabel(mgr, vfiodev);
VIR_FREE(vfiodev);
break;
}
case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_LAST:
ret = 0;
break;
}
done:
return ret;
}
static int
virSecuritySELinuxRestoreHostdevCapsLabel(virSecurityManagerPtr mgr,
virDomainHostdevDefPtr dev,
const char *vroot)
{
int ret = -1;
char *path;
switch (dev->source.caps.type) {
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_STORAGE: {
if (vroot) {
if (virAsprintf(&path, "%s/%s", vroot,
dev->source.caps.u.storage.block) < 0)
return -1;
} else {
if (VIR_STRDUP(path, dev->source.caps.u.storage.block) < 0)
return -1;
}
ret = virSecuritySELinuxRestoreFileLabel(mgr, path);
VIR_FREE(path);
break;
}
case VIR_DOMAIN_HOSTDEV_CAPS_TYPE_MISC: {
if (vroot) {
if (virAsprintf(&path, "%s/%s", vroot,
dev->source.caps.u.misc.chardev) < 0)
return -1;
} else {
if (VIR_STRDUP(path, dev->source.caps.u.misc.chardev) < 0)
return -1;
}
ret = virSecuritySELinuxRestoreFileLabel(mgr, path);
VIR_FREE(path);
break;
}
default:
ret = 0;
break;
}
return ret;
}
static int
virSecuritySELinuxRestoreHostdevLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainHostdevDefPtr dev,
const char *vroot)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel)
return 0;
switch (dev->mode) {
case VIR_DOMAIN_HOSTDEV_MODE_SUBSYS:
return virSecuritySELinuxRestoreHostdevSubsysLabel(mgr, dev, vroot);
case VIR_DOMAIN_HOSTDEV_MODE_CAPABILITIES:
return virSecuritySELinuxRestoreHostdevCapsLabel(mgr, dev, vroot);
default:
return 0;
}
}
static int
virSecuritySELinuxSetChardevLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainChrSourceDefPtr dev_source,
bool chardevStdioLogd)
{
virSecurityLabelDefPtr seclabel;
virSecurityDeviceLabelDefPtr chr_seclabel = NULL;
char *imagelabel = NULL;
char *in = NULL, *out = NULL;
int ret = -1;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!seclabel || !seclabel->relabel)
return 0;
chr_seclabel = virDomainChrSourceDefGetSecurityLabelDef(dev_source,
SECURITY_SELINUX_NAME);
if (chr_seclabel && !chr_seclabel->relabel)
return 0;
if (!chr_seclabel &&
dev_source->type == VIR_DOMAIN_CHR_TYPE_FILE &&
chardevStdioLogd)
return 0;
if (chr_seclabel)
imagelabel = chr_seclabel->label;
if (!imagelabel)
imagelabel = seclabel->imagelabel;
switch (dev_source->type) {
case VIR_DOMAIN_CHR_TYPE_DEV:
case VIR_DOMAIN_CHR_TYPE_FILE:
ret = virSecuritySELinuxSetFilecon(mgr,
dev_source->data.file.path,
imagelabel);
break;
case VIR_DOMAIN_CHR_TYPE_UNIX:
if (!dev_source->data.nix.listen) {
if (virSecuritySELinuxSetFilecon(mgr,
dev_source->data.nix.path,
imagelabel) < 0)
goto done;
}
ret = 0;
break;
case VIR_DOMAIN_CHR_TYPE_PIPE:
if ((virAsprintf(&in, "%s.in", dev_source->data.file.path) < 0) ||
(virAsprintf(&out, "%s.out", dev_source->data.file.path) < 0))
goto done;
if (virFileExists(in) && virFileExists(out)) {
if ((virSecuritySELinuxSetFilecon(mgr, in, imagelabel) < 0) ||
(virSecuritySELinuxSetFilecon(mgr, out, imagelabel) < 0)) {
goto done;
}
} else if (virSecuritySELinuxSetFilecon(mgr,
dev_source->data.file.path,
imagelabel) < 0) {
goto done;
}
ret = 0;
break;
default:
ret = 0;
break;
}
done:
VIR_FREE(in);
VIR_FREE(out);
return ret;
}
static int
virSecuritySELinuxRestoreChardevLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
virDomainChrSourceDefPtr dev_source,
bool chardevStdioLogd)
{
virSecurityLabelDefPtr seclabel;
virSecurityDeviceLabelDefPtr chr_seclabel = NULL;
char *in = NULL, *out = NULL;
int ret = -1;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!seclabel || !seclabel->relabel)
return 0;
chr_seclabel = virDomainChrSourceDefGetSecurityLabelDef(dev_source,
SECURITY_SELINUX_NAME);
if (chr_seclabel && !chr_seclabel->relabel)
return 0;
if (!chr_seclabel &&
dev_source->type == VIR_DOMAIN_CHR_TYPE_FILE &&
chardevStdioLogd)
return 0;
switch (dev_source->type) {
case VIR_DOMAIN_CHR_TYPE_DEV:
case VIR_DOMAIN_CHR_TYPE_FILE:
if (virSecuritySELinuxRestoreFileLabel(mgr, dev_source->data.file.path) < 0)
goto done;
ret = 0;
break;
case VIR_DOMAIN_CHR_TYPE_UNIX:
if (!dev_source->data.nix.listen) {
if (virSecuritySELinuxRestoreFileLabel(mgr, dev_source->data.file.path) < 0)
goto done;
}
ret = 0;
break;
case VIR_DOMAIN_CHR_TYPE_PIPE:
if ((virAsprintf(&out, "%s.out", dev_source->data.file.path) < 0) ||
(virAsprintf(&in, "%s.in", dev_source->data.file.path) < 0))
goto done;
if (virFileExists(in) && virFileExists(out)) {
if ((virSecuritySELinuxRestoreFileLabel(mgr, out) < 0) ||
(virSecuritySELinuxRestoreFileLabel(mgr, in) < 0)) {
goto done;
}
} else if (virSecuritySELinuxRestoreFileLabel(mgr, dev_source->data.file.path) < 0) {
goto done;
}
ret = 0;
break;
default:
ret = 0;
break;
}
done:
VIR_FREE(in);
VIR_FREE(out);
return ret;
}
struct _virSecuritySELinuxChardevCallbackData {
virSecurityManagerPtr mgr;
bool chardevStdioLogd;
};
static int
virSecuritySELinuxRestoreSecurityChardevCallback(virDomainDefPtr def,
virDomainChrDefPtr dev ATTRIBUTE_UNUSED,
void *opaque)
{
struct _virSecuritySELinuxChardevCallbackData *data = opaque;
return virSecuritySELinuxRestoreChardevLabel(data->mgr, def, dev->source,
data->chardevStdioLogd);
}
static int
virSecuritySELinuxRestoreSecuritySmartcardCallback(virDomainDefPtr def,
virDomainSmartcardDefPtr dev,
void *opaque)
{
virSecurityManagerPtr mgr = opaque;
const char *database;
switch (dev->type) {
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
break;
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
database = dev->data.cert.database;
if (!database)
database = VIR_DOMAIN_SMARTCARD_DEFAULT_DATABASE;
return virSecuritySELinuxRestoreFileLabel(mgr, database);
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
return virSecuritySELinuxRestoreChardevLabel(mgr, def,
dev->data.passthru, false);
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown smartcard type %d"),
dev->type);
return -1;
}
return 0;
}
static const char *
virSecuritySELinuxGetBaseLabel(virSecurityManagerPtr mgr, int virtType)
{
virSecuritySELinuxDataPtr priv = virSecurityManagerGetPrivateData(mgr);
if (virtType == VIR_DOMAIN_VIRT_QEMU && priv->alt_domain_context)
return priv->alt_domain_context;
else
return priv->domain_context;
}
static int
virSecuritySELinuxRestoreAllLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
bool migrated,
bool chardevStdioLogd)
{
virSecurityLabelDefPtr secdef;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
size_t i;
int rc = 0;
VIR_DEBUG("Restoring security label on %s", def->name);
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel || data->skipAllLabel)
return 0;
if (def->tpm) {
if (virSecuritySELinuxRestoreTPMFileLabelInt(mgr, def, def->tpm) < 0)
rc = -1;
}
for (i = 0; i < def->nhostdevs; i++) {
if (virSecuritySELinuxRestoreHostdevLabel(mgr,
def,
def->hostdevs[i],
NULL) < 0)
rc = -1;
}
for (i = 0; i < def->ninputs; i++) {
if (virSecuritySELinuxRestoreInputLabel(mgr, def, def->inputs[i]) < 0)
rc = -1;
}
for (i = 0; i < def->nmems; i++) {
if (virSecuritySELinuxRestoreMemoryLabel(mgr, def, def->mems[i]) < 0)
return -1;
}
for (i = 0; i < def->ndisks; i++) {
virDomainDiskDefPtr disk = def->disks[i];
if (virSecuritySELinuxRestoreImageLabelInt(mgr, def, disk->src,
migrated) < 0)
rc = -1;
}
struct _virSecuritySELinuxChardevCallbackData chardevData = {
.mgr = mgr,
.chardevStdioLogd = chardevStdioLogd
};
if (virDomainChrDefForeach(def,
false,
virSecuritySELinuxRestoreSecurityChardevCallback,
&chardevData) < 0)
rc = -1;
if (virDomainSmartcardDefForeach(def,
false,
virSecuritySELinuxRestoreSecuritySmartcardCallback,
mgr) < 0)
rc = -1;
if (def->os.loader && def->os.loader->nvram &&
virSecuritySELinuxRestoreFileLabel(mgr, def->os.loader->nvram) < 0)
rc = -1;
return rc;
}
static int
virSecuritySELinuxReleaseLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (secdef == NULL)
return 0;
if (secdef->type == VIR_DOMAIN_SECLABEL_DYNAMIC) {
if (secdef->label != NULL) {
context_t con = context_new(secdef->label);
if (con) {
virSecuritySELinuxMCSRemove(mgr, context_range_get(con));
context_free(con);
}
}
VIR_FREE(secdef->label);
if (!secdef->baselabel)
VIR_FREE(secdef->model);
}
VIR_FREE(secdef->imagelabel);
return 0;
}
static int
virSecuritySELinuxSetSavedStateLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
const char *savefile)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel)
return 0;
return virSecuritySELinuxSetFilecon(mgr, savefile, secdef->imagelabel);
}
static int
virSecuritySELinuxRestoreSavedStateLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
const char *savefile)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel)
return 0;
return virSecuritySELinuxRestoreFileLabel(mgr, savefile);
}
static int
virSecuritySELinuxVerify(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (secdef == NULL)
return 0;
if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"'%s' model configured for domain, but "
"hypervisor driver is '%s'."),
secdef->model, SECURITY_SELINUX_NAME);
return -1;
}
if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) {
if (security_check_context(secdef->label) != 0) {
virReportError(VIR_ERR_XML_ERROR,
_("Invalid security label %s"), secdef->label);
return -1;
}
}
return 0;
}
static int
virSecuritySELinuxSetProcessLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def)
{
/* TODO: verify DOI */
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->label)
return 0;
VIR_DEBUG("label=%s", secdef->label);
if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"'%s' model configured for domain, but "
"hypervisor driver is '%s'."),
secdef->model, SECURITY_SELINUX_NAME);
if (security_getenforce() == 1)
return -1;
}
if (setexeccon_raw(secdef->label) == -1) {
virReportSystemError(errno,
_("unable to set security context '%s'"),
secdef->label);
if (security_getenforce() == 1)
return -1;
}
return 0;
}
static int
virSecuritySELinuxSetChildProcessLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def,
virCommandPtr cmd)
{
/* TODO: verify DOI */
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->label)
return 0;
VIR_DEBUG("label=%s", secdef->label);
if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"'%s' model configured for domain, but "
"hypervisor driver is '%s'."),
secdef->model, SECURITY_SELINUX_NAME);
if (security_getenforce() == 1)
return -1;
}
/* save in cmd to be set after fork/before child process is exec'ed */
virCommandSetSELinuxLabel(cmd, secdef->label);
return 0;
}
static int
virSecuritySELinuxSetDaemonSocketLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def)
{
/* TODO: verify DOI */
virSecurityLabelDefPtr secdef;
security_context_t scon = NULL;
char *str = NULL;
int rc = -1;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->label)
return 0;
if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"'%s' model configured for domain, but "
"hypervisor driver is '%s'."),
secdef->model, SECURITY_SELINUX_NAME);
goto done;
}
if (getcon_raw(&scon) == -1) {
virReportSystemError(errno,
_("unable to get current process context '%s'"),
secdef->label);
goto done;
}
if (!(str = virSecuritySELinuxContextAddRange(secdef->label, scon)))
goto done;
VIR_DEBUG("Setting VM %s socket context %s", def->name, str);
if (setsockcreatecon_raw(str) == -1) {
virReportSystemError(errno,
_("unable to set socket security context '%s'"), str);
goto done;
}
rc = 0;
done:
if (security_getenforce() != 1)
rc = 0;
freecon(scon);
VIR_FREE(str);
return rc;
}
static int
virSecuritySELinuxSetSocketLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr vm)
{
virSecurityLabelDefPtr secdef;
int rc = -1;
secdef = virDomainDefGetSecurityLabelDef(vm, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->label)
return 0;
if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"'%s' model configured for domain, but "
"hypervisor driver is '%s'."),
secdef->model, SECURITY_SELINUX_NAME);
goto done;
}
VIR_DEBUG("Setting VM %s socket context %s",
vm->name, secdef->label);
if (setsockcreatecon_raw(secdef->label) == -1) {
virReportSystemError(errno,
_("unable to set socket security context '%s'"),
secdef->label);
goto done;
}
rc = 0;
done:
if (security_getenforce() != 1)
rc = 0;
return rc;
}
static int
virSecuritySELinuxClearSocketLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def)
{
/* TODO: verify DOI */
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->label)
return 0;
if (STRNEQ(SECURITY_SELINUX_NAME, secdef->model)) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("security label driver mismatch: "
"'%s' model configured for domain, but "
"hypervisor driver is '%s'."),
secdef->model, SECURITY_SELINUX_NAME);
if (security_getenforce() == 1)
return -1;
}
if (setsockcreatecon_raw(NULL) == -1) {
virReportSystemError(errno,
_("unable to clear socket security context '%s'"),
secdef->label);
if (security_getenforce() == 1)
return -1;
}
return 0;
}
static int
virSecuritySELinuxSetSecurityChardevCallback(virDomainDefPtr def,
virDomainChrDefPtr dev ATTRIBUTE_UNUSED,
void *opaque)
{
struct _virSecuritySELinuxChardevCallbackData *data = opaque;
return virSecuritySELinuxSetChardevLabel(data->mgr, def, dev->source,
data->chardevStdioLogd);
}
static int
virSecuritySELinuxSetSecuritySmartcardCallback(virDomainDefPtr def,
virDomainSmartcardDefPtr dev,
void *opaque)
{
const char *database;
virSecurityManagerPtr mgr = opaque;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
switch (dev->type) {
case VIR_DOMAIN_SMARTCARD_TYPE_HOST:
break;
case VIR_DOMAIN_SMARTCARD_TYPE_HOST_CERTIFICATES:
database = dev->data.cert.database;
if (!database)
database = VIR_DOMAIN_SMARTCARD_DEFAULT_DATABASE;
return virSecuritySELinuxSetFilecon(mgr, database, data->content_context);
case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH:
return virSecuritySELinuxSetChardevLabel(mgr, def,
dev->data.passthru, false);
default:
virReportError(VIR_ERR_INTERNAL_ERROR,
_("unknown smartcard type %d"),
dev->type);
return -1;
}
return 0;
}
static int
virSecuritySELinuxSetAllLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
const char *stdin_path,
bool chardevStdioLogd)
{
size_t i;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->relabel || data->skipAllLabel)
return 0;
for (i = 0; i < def->ndisks; i++) {
/* XXX fixme - we need to recursively label the entire tree :-( */
if (virDomainDiskGetType(def->disks[i]) == VIR_STORAGE_TYPE_DIR) {
VIR_WARN("Unable to relabel directory tree %s for disk %s",
virDomainDiskGetSource(def->disks[i]),
def->disks[i]->dst);
continue;
}
if (virSecuritySELinuxSetDiskLabel(mgr,
def, def->disks[i]) < 0)
return -1;
}
/* XXX fixme process def->fss if relabel == true */
for (i = 0; i < def->nhostdevs; i++) {
if (virSecuritySELinuxSetHostdevLabel(mgr,
def,
def->hostdevs[i],
NULL) < 0)
return -1;
}
for (i = 0; i < def->ninputs; i++) {
if (virSecuritySELinuxSetInputLabel(mgr, def, def->inputs[i]) < 0)
return -1;
}
for (i = 0; i < def->nmems; i++) {
if (virSecuritySELinuxSetMemoryLabel(mgr, def, def->mems[i]) < 0)
return -1;
}
if (def->tpm) {
if (virSecuritySELinuxSetTPMFileLabel(mgr, def, def->tpm) < 0)
return -1;
}
struct _virSecuritySELinuxChardevCallbackData chardevData = {
.mgr = mgr,
.chardevStdioLogd = chardevStdioLogd
};
if (virDomainChrDefForeach(def,
true,
virSecuritySELinuxSetSecurityChardevCallback,
&chardevData) < 0)
return -1;
if (virDomainSmartcardDefForeach(def,
true,
virSecuritySELinuxSetSecuritySmartcardCallback,
mgr) < 0)
return -1;
/* This is different than kernel or initrd. The nvram store
* is really a disk, qemu can read and write to it. */
if (def->os.loader && def->os.loader->nvram &&
secdef && secdef->imagelabel &&
virSecuritySELinuxSetFilecon(mgr, def->os.loader->nvram,
secdef->imagelabel) < 0)
return -1;
if (def->os.kernel &&
virSecuritySELinuxSetFilecon(mgr, def->os.kernel,
data->content_context) < 0)
return -1;
if (def->os.initrd &&
virSecuritySELinuxSetFilecon(mgr, def->os.initrd,
data->content_context) < 0)
return -1;
if (def->os.dtb &&
virSecuritySELinuxSetFilecon(mgr, def->os.dtb,
data->content_context) < 0)
return -1;
if (def->os.slic_table &&
virSecuritySELinuxSetFilecon(mgr, def->os.slic_table,
data->content_context) < 0)
return -1;
if (stdin_path &&
virSecuritySELinuxSetFilecon(mgr, stdin_path,
data->content_context) < 0)
return -1;
return 0;
}
static int
virSecuritySELinuxSetImageFDLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED,
virDomainDefPtr def,
int fd)
{
virSecurityLabelDefPtr secdef;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->imagelabel)
return 0;
return virSecuritySELinuxFSetFilecon(fd, secdef->imagelabel);
}
static int
virSecuritySELinuxSetTapFDLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
int fd)
{
struct stat buf;
security_context_t fcon = NULL;
virSecurityLabelDefPtr secdef;
char *str = NULL, *proc = NULL, *fd_path = NULL;
int rc = -1;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!secdef || !secdef->label)
return 0;
if (fstat(fd, &buf) < 0) {
virReportSystemError(errno, _("cannot stat tap fd %d"), fd);
goto cleanup;
}
if ((buf.st_mode & S_IFMT) != S_IFCHR) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("tap fd %d is not character device"), fd);
goto cleanup;
}
/* Label /dev/tap.* devices only. Leave /dev/net/tun alone! */
if (virAsprintf(&proc, "/proc/self/fd/%d", fd) == -1)
goto cleanup;
if (virFileResolveLink(proc, &fd_path) < 0) {
virReportSystemError(errno,
_("Unable to resolve link: %s"), proc);
goto cleanup;
}
if (!STRPREFIX(fd_path, "/dev/tap")) {
VIR_DEBUG("fd=%d points to %s not setting SELinux label",
fd, fd_path);
rc = 0;
goto cleanup;
}
if (getContext(mgr, "/dev/tap*", buf.st_mode, &fcon) < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("cannot lookup default selinux label for tap fd %d"), fd);
goto cleanup;
}
if (!(str = virSecuritySELinuxContextAddRange(secdef->label, fcon))) {
goto cleanup;
} else {
rc = virSecuritySELinuxFSetFilecon(fd, str);
}
cleanup:
freecon(fcon);
VIR_FREE(fd_path);
VIR_FREE(proc);
VIR_FREE(str);
return rc;
}
static char *
virSecuritySELinuxGenImageLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def)
{
virSecurityLabelDefPtr secdef;
virSecuritySELinuxDataPtr data = virSecurityManagerGetPrivateData(mgr);
const char *range;
context_t ctx = NULL;
char *label = NULL;
char *mcs = NULL;
secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (secdef == NULL)
goto cleanup;
if (secdef->label) {
ctx = context_new(secdef->label);
if (!ctx) {
virReportSystemError(errno, _("unable to create selinux context for: %s"),
secdef->label);
goto cleanup;
}
range = context_range_get(ctx);
if (range) {
if (VIR_STRDUP(mcs, range) < 0)
goto cleanup;
if (!(label = virSecuritySELinuxGenNewContext(data->file_context,
mcs, true)))
goto cleanup;
}
}
cleanup:
context_free(ctx);
VIR_FREE(mcs);
return label;
}
static char *
virSecuritySELinuxGetSecurityMountOptions(virSecurityManagerPtr mgr,
virDomainDefPtr def)
{
char *opts = NULL;
virSecurityLabelDefPtr secdef;
if ((secdef = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME))) {
if (!secdef->imagelabel)
secdef->imagelabel = virSecuritySELinuxGenImageLabel(mgr, def);
if (secdef->imagelabel &&
virAsprintf(&opts,
",context=\"%s\"",
(const char*) secdef->imagelabel) < 0)
return NULL;
}
if (!opts && VIR_STRDUP(opts, "") < 0)
return NULL;
VIR_DEBUG("imageLabel=%s opts=%s",
secdef ? secdef->imagelabel : "(null)", opts);
return opts;
}
static int
virSecuritySELinuxDomainSetPathLabel(virSecurityManagerPtr mgr,
virDomainDefPtr def,
const char *path,
bool allowSubtree ATTRIBUTE_UNUSED)
{
virSecurityLabelDefPtr seclabel;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (!seclabel || !seclabel->relabel)
return 0;
return virSecuritySELinuxSetFilecon(mgr, path, seclabel->imagelabel);
}
/*
* virSecuritySELinuxSetFileLabels:
*
* @mgr: the virSecurityManager
* @path: path to a directory or a file
* @seclabel: the security label
*
* Set the file labels on the given path; if the path is a directory
* we label all files found there, including the directory itself,
* otherwise we just label the file.
*/
static int
virSecuritySELinuxSetFileLabels(virSecurityManagerPtr mgr,
const char *path,
virSecurityLabelDefPtr seclabel)
{
int ret = 0;
struct dirent *ent;
char *filename = NULL;
DIR *dir;
if ((ret = virSecuritySELinuxSetFilecon(mgr, path, seclabel->imagelabel)))
return ret;
if (!virFileIsDir(path))
return 0;
if (virDirOpen(&dir, path) < 0)
return -1;
while ((ret = virDirRead(dir, &ent, path)) > 0) {
if (ent->d_type != DT_REG)
continue;
if (virAsprintf(&filename, "%s/%s", path, ent->d_name) < 0) {
ret = -1;
break;
}
ret = virSecuritySELinuxSetFilecon(mgr, filename,
seclabel->imagelabel);
VIR_FREE(filename);
if (ret < 0)
break;
}
if (ret < 0)
virReportSystemError(errno, _("Unable to label files under %s"),
path);
virDirClose(&dir);
return ret;
}
/*
* virSecuritySELinuxRestoreFileLabels:
*
* @mgr: the virSecurityManager
* @path: path to a directory or a file
*
* Restore the file labels on the given path; if the path is a directory
* we restore all file labels found there, including the label of the
* directory itself, otherwise we just restore the label on the file.
*/
static int
virSecuritySELinuxRestoreFileLabels(virSecurityManagerPtr mgr,
const char *path)
{
int ret = 0;
struct dirent *ent;
char *filename = NULL;
DIR *dir;
if ((ret = virSecuritySELinuxRestoreFileLabel(mgr, path)))
return ret;
if (!virFileIsDir(path))
return 0;
if (virDirOpen(&dir, path) < 0)
return -1;
while ((ret = virDirRead(dir, &ent, path)) > 0) {
if (ent->d_type != DT_REG)
continue;
if (virAsprintf(&filename, "%s/%s", path, ent->d_name) < 0) {
ret = -1;
break;
}
ret = virSecuritySELinuxRestoreFileLabel(mgr, filename);
VIR_FREE(filename);
if (ret < 0)
break;
}
if (ret < 0)
virReportSystemError(errno, _("Unable to restore file labels under %s"),
path);
virDirClose(&dir);
return ret;
}
static int
virSecuritySELinuxSetTPMLabels(virSecurityManagerPtr mgr,
virDomainDefPtr def)
{
int ret = 0;
virSecurityLabelDefPtr seclabel;
seclabel = virDomainDefGetSecurityLabelDef(def, SECURITY_SELINUX_NAME);
if (seclabel == NULL)
return 0;
switch (def->tpm->type) {
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
break;
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
ret = virSecuritySELinuxSetFileLabels(
mgr, def->tpm->data.emulator.storagepath,
seclabel);
if (ret == 0 && def->tpm->data.emulator.logfile)
ret = virSecuritySELinuxSetFileLabels(
mgr, def->tpm->data.emulator.logfile,
seclabel);
break;
case VIR_DOMAIN_TPM_TYPE_LAST:
break;
}
return ret;
}
static int
virSecuritySELinuxRestoreTPMLabels(virSecurityManagerPtr mgr,
virDomainDefPtr def)
{
int ret = 0;
switch (def->tpm->type) {
case VIR_DOMAIN_TPM_TYPE_PASSTHROUGH:
break;
case VIR_DOMAIN_TPM_TYPE_EMULATOR:
ret = virSecuritySELinuxRestoreFileLabels(
mgr, def->tpm->data.emulator.storagepath);
if (ret == 0 && def->tpm->data.emulator.logfile)
ret = virSecuritySELinuxRestoreFileLabels(
mgr, def->tpm->data.emulator.logfile);
break;
case VIR_DOMAIN_TPM_TYPE_LAST:
break;
}
return ret;
}
virSecurityDriver virSecurityDriverSELinux = {
.privateDataLen = sizeof(virSecuritySELinuxData),
.name = SECURITY_SELINUX_NAME,
.probe = virSecuritySELinuxDriverProbe,
.open = virSecuritySELinuxDriverOpen,
.close = virSecuritySELinuxDriverClose,
.getModel = virSecuritySELinuxGetModel,
.getDOI = virSecuritySELinuxGetDOI,
.transactionStart = virSecuritySELinuxTransactionStart,
.transactionCommit = virSecuritySELinuxTransactionCommit,
.transactionAbort = virSecuritySELinuxTransactionAbort,
.domainSecurityVerify = virSecuritySELinuxVerify,
.domainSetSecurityDiskLabel = virSecuritySELinuxSetDiskLabel,
.domainRestoreSecurityDiskLabel = virSecuritySELinuxRestoreDiskLabel,
.domainSetSecurityImageLabel = virSecuritySELinuxSetImageLabel,
.domainRestoreSecurityImageLabel = virSecuritySELinuxRestoreImageLabel,
.domainSetSecurityMemoryLabel = virSecuritySELinuxSetMemoryLabel,
.domainRestoreSecurityMemoryLabel = virSecuritySELinuxRestoreMemoryLabel,
.domainSetSecurityInputLabel = virSecuritySELinuxSetInputLabel,
.domainRestoreSecurityInputLabel = virSecuritySELinuxRestoreInputLabel,
.domainSetSecurityDaemonSocketLabel = virSecuritySELinuxSetDaemonSocketLabel,
.domainSetSecuritySocketLabel = virSecuritySELinuxSetSocketLabel,
.domainClearSecuritySocketLabel = virSecuritySELinuxClearSocketLabel,
.domainGenSecurityLabel = virSecuritySELinuxGenLabel,
.domainReserveSecurityLabel = virSecuritySELinuxReserveLabel,
.domainReleaseSecurityLabel = virSecuritySELinuxReleaseLabel,
.domainGetSecurityProcessLabel = virSecuritySELinuxGetProcessLabel,
.domainSetSecurityProcessLabel = virSecuritySELinuxSetProcessLabel,
.domainSetSecurityChildProcessLabel = virSecuritySELinuxSetChildProcessLabel,
.domainSetSecurityAllLabel = virSecuritySELinuxSetAllLabel,
.domainRestoreSecurityAllLabel = virSecuritySELinuxRestoreAllLabel,
.domainSetSecurityHostdevLabel = virSecuritySELinuxSetHostdevLabel,
.domainRestoreSecurityHostdevLabel = virSecuritySELinuxRestoreHostdevLabel,
.domainSetSavedStateLabel = virSecuritySELinuxSetSavedStateLabel,
.domainRestoreSavedStateLabel = virSecuritySELinuxRestoreSavedStateLabel,
.domainSetSecurityImageFDLabel = virSecuritySELinuxSetImageFDLabel,
.domainSetSecurityTapFDLabel = virSecuritySELinuxSetTapFDLabel,
.domainGetSecurityMountOptions = virSecuritySELinuxGetSecurityMountOptions,
.getBaseLabel = virSecuritySELinuxGetBaseLabel,
.domainSetPathLabel = virSecuritySELinuxDomainSetPathLabel,
.domainSetSecurityChardevLabel = virSecuritySELinuxSetChardevLabel,
.domainRestoreSecurityChardevLabel = virSecuritySELinuxRestoreChardevLabel,
.domainSetSecurityTPMLabels = virSecuritySELinuxSetTPMLabels,
.domainRestoreSecurityTPMLabels = virSecuritySELinuxRestoreTPMLabels,
};