/* * Copyright (C) 2008-2011 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 * 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 "virterror_internal.h" #include "util.h" #include "memory.h" #include "logging.h" #include "pci.h" #include "hostusb.h" #include "storage_file.h" #include "virfile.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; } VIR_FREE(ptr->mcs); VIR_FREE(ptr); return 0; } prevptr = ptr; } return -1; } static char * SELinuxGenNewContext(const char *oldcontext, const char *mcs) { char *newcontext = NULL; char *scontext = strdup(oldcontext); context_t con; if (!scontext) goto err; 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(void) { char *ptr = NULL; int fd = 0; fd = open(selinux_virtual_domain_context_path(), O_RDONLY); if (fd < 0) { virReportSystemError(errno, _("cannot open SELinux virtual domain context file '%s'"), selinux_virtual_domain_context_path()); return -1; } if (saferead(fd, default_domain_context, sizeof(default_domain_context)) < 0) { virReportSystemError(errno, _("cannot read SELinux virtual domain context file %s"), selinux_virtual_domain_context_path()); VIR_FORCE_CLOSE(fd); return -1; } VIR_FORCE_CLOSE(fd); ptr = strchrnul(default_domain_context, '\n'); *ptr = '\0'; if ((fd = open(selinux_virtual_image_context_path(), O_RDONLY)) < 0) { virReportSystemError(errno, _("cannot open SELinux virtual image context file %s"), selinux_virtual_image_context_path()); return -1; } if (saferead(fd, default_image_context, sizeof(default_image_context)) < 0) { virReportSystemError(errno, _("cannot read SELinux virtual image context file %s"), selinux_virtual_image_context_path()); VIR_FORCE_CLOSE(fd); return -1; } VIR_FORCE_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(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm) { int rc = -1; char *mcs = NULL; char *scontext = NULL; int c1 = 0; int c2 = 0; context_t ctx = NULL; if ((vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_DYNAMIC) && !vm->def->seclabel.baselabel && vm->def->seclabel.model) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("security model already defined for VM")); return rc; } if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_DYNAMIC && vm->def->seclabel.label) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("security label already defined for VM")); return rc; } if (vm->def->seclabel.imagelabel) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, "%s", _("security image label already defined for VM")); return rc; } if (vm->def->seclabel.model && STRNEQ(vm->def->seclabel.model, SECURITY_SELINUX_NAME)) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("security label model %s is not supported with selinux"), vm->def->seclabel.model); return rc; } if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_STATIC) { if (!(ctx = context_new(vm->def->seclabel.label)) ) { virReportSystemError(errno, _("unable to allocate socket security context '%s'"), vm->def->seclabel.label); return rc; } const char *range = context_range_get(ctx); if (!range || !(mcs = strdup(range))) { virReportOOMError(); goto cleanup; } } else { do { c1 = virRandom(1024); c2 = virRandom(1024); if ( c1 == c2 ) { if (virAsprintf(&mcs, "s0:c%d", c1) < 0) { virReportOOMError(); goto cleanup; } } else { if (c1 > c2) { c1 ^= c2; c2 ^= c1; c1 ^= c2; } if (virAsprintf(&mcs, "s0:c%d,c%d", c1, c2) < 0) { virReportOOMError(); goto cleanup; } } } while (mcsAdd(mcs) == -1); vm->def->seclabel.label = SELinuxGenNewContext(vm->def->seclabel.baselabel ? vm->def->seclabel.baselabel : default_domain_context, mcs); if (! vm->def->seclabel.label) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("cannot generate selinux context for %s"), mcs); goto cleanup; } } vm->def->seclabel.imagelabel = SELinuxGenNewContext(default_image_context, mcs); if (!vm->def->seclabel.imagelabel) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("cannot generate selinux context for %s"), mcs); goto cleanup; } if (!vm->def->seclabel.model && !(vm->def->seclabel.model = strdup(SECURITY_SELINUX_NAME))) { virReportOOMError(); goto cleanup; } rc = 0; cleanup: if (rc != 0) { if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_DYNAMIC) VIR_FREE(vm->def->seclabel.label); VIR_FREE(vm->def->seclabel.imagelabel); if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_DYNAMIC && !vm->def->seclabel.baselabel) VIR_FREE(vm->def->seclabel.model); } if (ctx) context_free(ctx); VIR_FREE(scontext); VIR_FREE(mcs); VIR_DEBUG("model=%s label=%s imagelabel=%s baselabel=%s", NULLSTR(vm->def->seclabel.model), NULLSTR(vm->def->seclabel.label), NULLSTR(vm->def->seclabel.imagelabel), NULLSTR(vm->def->seclabel.baselabel)); return rc; } static int SELinuxReserveSecurityLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm) { security_context_t pctx; context_t ctx = NULL; const char *mcs; if (vm->def->seclabel.type == VIR_DOMAIN_SECLABEL_STATIC) return 0; if (getpidcon(vm->pid, &pctx) == -1) { virReportSystemError(errno, _("unable to get PID %d security context"), vm->pid); return -1; } ctx = context_new(pctx); freecon(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(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED) { return SELinuxInitialize(); } static int SELinuxSecurityDriverClose(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED) { return 0; } static const char *SELinuxSecurityGetModel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED) { return SECURITY_SELINUX_NAME; } static const char *SELinuxSecurityGetDOI(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; } static int SELinuxGetSecurityProcessLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, virSecurityLabelPtr sec) { security_context_t ctx; if (getpidcon(vm->pid, &ctx) == -1) { virReportSystemError(errno, _("unable to get PID %d security context"), vm->pid); return -1; } if (strlen((char *) ctx) >= VIR_SECURITY_LABEL_BUFLEN) { virSecurityReportError(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); sec->enforcing = security_getenforce(); if (sec->enforcing == -1) { virReportSystemError(errno, "%s", _("error calling security_getenforce()")); return -1; } return 0; } static int SELinuxSetFilecon(const char *path, char *tcon) { security_context_t econ; VIR_INFO("Setting SELinux context on '%s' to '%s'", path, tcon); if (setfilecon(path, tcon) < 0) { int setfilecon_errno = errno; if (getfilecon(path, &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 (setfilecon_errno != EOPNOTSUPP && setfilecon_errno != ENOTSUP) { virReportSystemError(setfilecon_errno, _("unable to set security context '%s' on '%s'"), tcon, path); if (security_getenforce() == 1) return -1; } else { const char *msg; if ((virStorageFileIsSharedFSType(path, VIR_STORAGE_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); } } } return 0; } static int SELinuxFSetFilecon(int fd, char *tcon) { security_context_t econ; VIR_INFO("Setting SELinux context on fd %d to '%s'", fd, tcon); if (fsetfilecon(fd, tcon) < 0) { int fsetfilecon_errno = errno; if (fgetfilecon(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(const char *newpath, mode_t mode, security_context_t *fcon) { #if HAVE_SELINUX_LABEL_H struct selabel_handle *handle = selabel_open(SELABEL_CTX_FILE, NULL, 0); int ret; if (handle == NULL) return -1; ret = selabel_lookup(handle, fcon, newpath, mode); selabel_close(handle); return ret; #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 SELinuxRestoreSecurityFileLabel(const char *path) { struct stat buf; security_context_t fcon = NULL; int rc = -1; char *newpath = NULL; char ebuf[1024]; 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 err; } if (stat(newpath, &buf) != 0) { VIR_WARN("cannot stat %s: %s", newpath, virStrerror(errno, ebuf, sizeof(ebuf))); goto err; } if (getContext(newpath, buf.st_mode, &fcon) < 0) { VIR_WARN("cannot lookup default selinux label for %s", newpath); } else { rc = SELinuxSetFilecon(newpath, fcon); } err: freecon(fcon); VIR_FREE(newpath); return rc; } static int SELinuxRestoreSecurityImageLabelInt(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, virDomainDiskDefPtr disk, int migrated) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (secdef->norelabel) return 0; /* 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 (!disk->src || disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK) return 0; /* If we have a shared FS & doing migrated, 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 = virStorageFileIsSharedFS(disk->src); if (rc < 0) return -1; if (rc == 1) { VIR_DEBUG("Skipping image label restore on %s because FS is shared", disk->src); return 0; } } return SELinuxRestoreSecurityFileLabel(disk->src); } static int SELinuxRestoreSecurityImageLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm, virDomainDiskDefPtr disk) { return SELinuxRestoreSecurityImageLabelInt(mgr, vm, disk, 0); } static int SELinuxSetSecurityFileLabel(virDomainDiskDefPtr disk, const char *path, size_t depth, void *opaque) { const virSecurityLabelDefPtr secdef = opaque; int ret; if (depth == 0) { if (disk->shared) { ret = SELinuxSetFilecon(path, default_image_context); } else if (disk->readonly) { ret = SELinuxSetFilecon(path, default_content_context); } else if (secdef->imagelabel) { ret = SELinuxSetFilecon(path, secdef->imagelabel); } else { ret = 0; } } else { ret = SELinuxSetFilecon(path, default_content_context); } if (ret < 0 && virStorageFileIsSharedFSType(path, VIR_STORAGE_FILE_SHFS_NFS) == 1) ret = 0; return ret; } static int SELinuxSetSecurityImageLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm, virDomainDiskDefPtr disk) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; bool allowDiskFormatProbing = virSecurityManagerGetAllowDiskFormatProbing(mgr); if (secdef->norelabel) return 0; if (disk->type == VIR_DOMAIN_DISK_TYPE_NETWORK) return 0; return virDomainDiskDefForeachPath(disk, allowDiskFormatProbing, true, SELinuxSetSecurityFileLabel, secdef); } static int SELinuxSetSecurityPCILabel(pciDevice *dev ATTRIBUTE_UNUSED, const char *file, void *opaque) { virDomainObjPtr vm = opaque; const virSecurityLabelDefPtr secdef = &vm->def->seclabel; return SELinuxSetFilecon(file, secdef->imagelabel); } static int SELinuxSetSecurityUSBLabel(usbDevice *dev ATTRIBUTE_UNUSED, const char *file, void *opaque) { virDomainObjPtr vm = opaque; const virSecurityLabelDefPtr secdef = &vm->def->seclabel; return SELinuxSetFilecon(file, secdef->imagelabel); } static int SELinuxSetSecurityHostdevLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, virDomainHostdevDefPtr dev) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; int ret = -1; if (secdef->norelabel) return 0; if (dev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) return 0; switch (dev->source.subsys.type) { case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: { usbDevice *usb = usbGetDevice(dev->source.subsys.u.usb.bus, dev->source.subsys.u.usb.device); if (!usb) goto done; ret = usbDeviceFileIterate(usb, SELinuxSetSecurityUSBLabel, vm); usbFreeDevice(usb); break; } case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: { pciDevice *pci = pciGetDevice(dev->source.subsys.u.pci.domain, dev->source.subsys.u.pci.bus, dev->source.subsys.u.pci.slot, dev->source.subsys.u.pci.function); if (!pci) goto done; ret = pciDeviceFileIterate(pci, SELinuxSetSecurityPCILabel, vm); pciFreeDevice(pci); break; } default: ret = 0; break; } done: return ret; } static int SELinuxRestoreSecurityPCILabel(pciDevice *dev ATTRIBUTE_UNUSED, const char *file, void *opaque ATTRIBUTE_UNUSED) { return SELinuxRestoreSecurityFileLabel(file); } static int SELinuxRestoreSecurityUSBLabel(usbDevice *dev ATTRIBUTE_UNUSED, const char *file, void *opaque ATTRIBUTE_UNUSED) { return SELinuxRestoreSecurityFileLabel(file); } static int SELinuxRestoreSecurityHostdevLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, virDomainHostdevDefPtr dev) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; int ret = -1; if (secdef->norelabel) return 0; if (dev->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) return 0; switch (dev->source.subsys.type) { case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: { usbDevice *usb = usbGetDevice(dev->source.subsys.u.usb.bus, dev->source.subsys.u.usb.device); if (!usb) goto done; ret = usbDeviceFileIterate(usb, SELinuxRestoreSecurityUSBLabel, NULL); usbFreeDevice(usb); break; } case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: { pciDevice *pci = pciGetDevice(dev->source.subsys.u.pci.domain, dev->source.subsys.u.pci.bus, dev->source.subsys.u.pci.slot, dev->source.subsys.u.pci.function); if (!pci) goto done; ret = pciDeviceFileIterate(pci, SELinuxRestoreSecurityPCILabel, NULL); pciFreeDevice(pci); break; } default: ret = 0; break; } done: return ret; } static int SELinuxSetSecurityChardevLabel(virDomainObjPtr vm, virDomainChrSourceDefPtr dev) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; char *in = NULL, *out = NULL; int ret = -1; if (secdef->norelabel) return 0; switch (dev->type) { case VIR_DOMAIN_CHR_TYPE_DEV: case VIR_DOMAIN_CHR_TYPE_FILE: ret = SELinuxSetFilecon(dev->data.file.path, secdef->imagelabel); break; case VIR_DOMAIN_CHR_TYPE_PIPE: if ((virAsprintf(&in, "%s.in", dev->data.file.path) < 0) || (virAsprintf(&out, "%s.out", dev->data.file.path) < 0)) { virReportOOMError(); goto done; } if (virFileExists(in) && virFileExists(out)) { if ((SELinuxSetFilecon(in, secdef->imagelabel) < 0) || (SELinuxSetFilecon(out, secdef->imagelabel) < 0)) { goto done; } } else if (SELinuxSetFilecon(dev->data.file.path, secdef->imagelabel) < 0) { goto done; } ret = 0; break; default: ret = 0; break; } done: VIR_FREE(in); VIR_FREE(out); return ret; } static int SELinuxRestoreSecurityChardevLabel(virDomainObjPtr vm, virDomainChrSourceDefPtr dev) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; char *in = NULL, *out = NULL; int ret = -1; if (secdef->norelabel) return 0; switch (dev->type) { case VIR_DOMAIN_CHR_TYPE_DEV: case VIR_DOMAIN_CHR_TYPE_FILE: if (SELinuxRestoreSecurityFileLabel(dev->data.file.path) < 0) goto done; ret = 0; break; case VIR_DOMAIN_CHR_TYPE_PIPE: if ((virAsprintf(&out, "%s.out", dev->data.file.path) < 0) || (virAsprintf(&in, "%s.in", dev->data.file.path) < 0)) { virReportOOMError(); goto done; } if (virFileExists(in) && virFileExists(out)) { if ((SELinuxRestoreSecurityFileLabel(out) < 0) || (SELinuxRestoreSecurityFileLabel(in) < 0)) { goto done; } } else if (SELinuxRestoreSecurityFileLabel(dev->data.file.path) < 0) { goto done; } ret = 0; break; default: ret = 0; break; } done: VIR_FREE(in); VIR_FREE(out); return ret; } static int SELinuxRestoreSecurityChardevCallback(virDomainDefPtr def ATTRIBUTE_UNUSED, virDomainChrDefPtr dev, void *opaque) { virDomainObjPtr vm = opaque; /* This is taken care of by processing of def->serials */ if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE && dev->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL) return 0; return SELinuxRestoreSecurityChardevLabel(vm, &dev->source); } static int SELinuxRestoreSecuritySmartcardCallback(virDomainDefPtr def ATTRIBUTE_UNUSED, virDomainSmartcardDefPtr dev, void *opaque) { virDomainObjPtr vm = 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 SELinuxRestoreSecurityFileLabel(database); case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH: return SELinuxRestoreSecurityChardevLabel(vm, &dev->data.passthru); default: virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("unknown smartcard type %d"), dev->type); return -1; } return 0; } static int SELinuxRestoreSecurityAllLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, int migrated ATTRIBUTE_UNUSED) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; int i; int rc = 0; VIR_DEBUG("Restoring security label on %s", vm->def->name); if (secdef->norelabel) return 0; for (i = 0 ; i < vm->def->nhostdevs ; i++) { if (SELinuxRestoreSecurityHostdevLabel(mgr, vm, vm->def->hostdevs[i]) < 0) rc = -1; } for (i = 0 ; i < vm->def->ndisks ; i++) { if (SELinuxRestoreSecurityImageLabelInt(mgr, vm, vm->def->disks[i], migrated) < 0) rc = -1; } if (virDomainChrDefForeach(vm->def, false, SELinuxRestoreSecurityChardevCallback, vm) < 0) rc = -1; if (virDomainSmartcardDefForeach(vm->def, false, SELinuxRestoreSecuritySmartcardCallback, vm) < 0) rc = -1; if (vm->def->os.kernel && SELinuxRestoreSecurityFileLabel(vm->def->os.kernel) < 0) rc = -1; if (vm->def->os.initrd && SELinuxRestoreSecurityFileLabel(vm->def->os.initrd) < 0) rc = -1; return rc; } static int SELinuxReleaseSecurityLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (secdef->type == VIR_DOMAIN_SECLABEL_DYNAMIC) { if (secdef->label != NULL) { context_t con = context_new(secdef->label); if (con) { mcsRemove(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 SELinuxSetSavedStateLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *savefile) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (secdef->norelabel) return 0; return SELinuxSetFilecon(savefile, secdef->imagelabel); } static int SELinuxRestoreSavedStateLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, const char *savefile) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (secdef->norelabel) return 0; return SELinuxRestoreSecurityFileLabel(savefile); } static int SELinuxSecurityVerify(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainDefPtr def) { const virSecurityLabelDefPtr secdef = &def->seclabel; if (!STREQ(virSecurityManagerGetModel(mgr), secdef->model)) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("security label driver mismatch: " "'%s' model configured for domain, but " "hypervisor driver is '%s'."), secdef->model, virSecurityManagerGetModel(mgr)); return -1; } if (secdef->type == VIR_DOMAIN_SECLABEL_STATIC) { if (security_check_context(secdef->label) != 0) { virSecurityReportError(VIR_ERR_XML_ERROR, _("Invalid security label %s"), secdef->label); return -1; } } return 0; } static int SELinuxSetSecurityProcessLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm) { /* TODO: verify DOI */ const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (vm->def->seclabel.label == NULL) return 0; if (!STREQ(virSecurityManagerGetModel(mgr), secdef->model)) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("security label driver mismatch: " "'%s' model configured for domain, but " "hypervisor driver is '%s'."), secdef->model, virSecurityManagerGetModel(mgr)); if (security_getenforce() == 1) return -1; } if (setexeccon(secdef->label) == -1) { virReportSystemError(errno, _("unable to set security context '%s'"), secdef->label); if (security_getenforce() == 1) return -1; } return 0; } static int SELinuxSetSecurityDaemonSocketLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm) { /* TODO: verify DOI */ const virSecurityLabelDefPtr secdef = &vm->def->seclabel; context_t execcon = NULL; context_t proccon = NULL; security_context_t scon = NULL; int rc = -1; if (vm->def->seclabel.label == NULL) return 0; if (!STREQ(virSecurityManagerGetModel(mgr), secdef->model)) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("security label driver mismatch: " "'%s' model configured for domain, but " "hypervisor driver is '%s'."), secdef->model, virSecurityManagerGetModel(mgr)); goto done; } if ( !(execcon = context_new(secdef->label)) ) { virReportSystemError(errno, _("unable to allocate socket security context '%s'"), secdef->label); goto done; } if (getcon(&scon) == -1) { virReportSystemError(errno, _("unable to get current process context '%s'"), secdef->label); goto done; } if ( !(proccon = context_new(scon)) ) { virReportSystemError(errno, _("unable to set socket security context '%s'"), secdef->label); goto done; } if (context_range_set(proccon, context_range_get(execcon)) == -1) { virReportSystemError(errno, _("unable to set socket security context range '%s'"), secdef->label); goto done; } VIR_DEBUG("Setting VM %s socket context %s", vm->def->name, context_str(proccon)); if (setsockcreatecon(context_str(proccon)) == -1) { virReportSystemError(errno, _("unable to set socket security context '%s'"), context_str(proccon)); goto done; } rc = 0; done: if (security_getenforce() != 1) rc = 0; if (execcon) context_free(execcon); if (proccon) context_free(proccon); freecon(scon); return rc; } static int SELinuxSetSecuritySocketLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; int rc = -1; if (secdef->label == NULL) return 0; if (!STREQ(virSecurityManagerGetModel(mgr), secdef->model)) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("security label driver mismatch: " "'%s' model configured for domain, but " "hypervisor driver is '%s'."), secdef->model, virSecurityManagerGetModel(mgr)); goto done; } VIR_DEBUG("Setting VM %s socket context %s", vm->def->name, secdef->label); if (setsockcreatecon(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 SELinuxClearSecuritySocketLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm) { /* TODO: verify DOI */ const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (vm->def->seclabel.label == NULL) return 0; if (!STREQ(virSecurityManagerGetModel(mgr), secdef->model)) { virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("security label driver mismatch: " "'%s' model configured for domain, but " "hypervisor driver is '%s'."), secdef->model, virSecurityManagerGetModel(mgr)); if (security_getenforce() == 1) return -1; } if (setsockcreatecon(NULL) == -1) { virReportSystemError(errno, _("unable to clear socket security context '%s'"), secdef->label); if (security_getenforce() == 1) return -1; } return 0; } static int SELinuxSetSecurityChardevCallback(virDomainDefPtr def ATTRIBUTE_UNUSED, virDomainChrDefPtr dev, void *opaque) { virDomainObjPtr vm = opaque; /* This is taken care of by processing of def->serials */ if (dev->deviceType == VIR_DOMAIN_CHR_DEVICE_TYPE_CONSOLE && dev->targetType == VIR_DOMAIN_CHR_CONSOLE_TARGET_TYPE_SERIAL) return 0; return SELinuxSetSecurityChardevLabel(vm, &dev->source); } static int SELinuxSetSecuritySmartcardCallback(virDomainDefPtr def ATTRIBUTE_UNUSED, virDomainSmartcardDefPtr dev, void *opaque) { virDomainObjPtr vm = 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 SELinuxSetFilecon(database, default_content_context); case VIR_DOMAIN_SMARTCARD_TYPE_PASSTHROUGH: return SELinuxSetSecurityChardevLabel(vm, &dev->data.passthru); default: virSecurityReportError(VIR_ERR_INTERNAL_ERROR, _("unknown smartcard type %d"), dev->type); return -1; } return 0; } static int SELinuxSetSecurityAllLabel(virSecurityManagerPtr mgr, virDomainObjPtr vm, const char *stdin_path) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; int i; if (secdef->norelabel) return 0; for (i = 0 ; i < vm->def->ndisks ; i++) { /* XXX fixme - we need to recursively label the entire tree :-( */ if (vm->def->disks[i]->type == VIR_DOMAIN_DISK_TYPE_DIR) { VIR_WARN("Unable to relabel directory tree %s for disk %s", vm->def->disks[i]->src, vm->def->disks[i]->dst); continue; } if (SELinuxSetSecurityImageLabel(mgr, vm, vm->def->disks[i]) < 0) return -1; } /* XXX fixme process vm->def->fss if relabel == true */ for (i = 0 ; i < vm->def->nhostdevs ; i++) { if (SELinuxSetSecurityHostdevLabel(mgr, vm, vm->def->hostdevs[i]) < 0) return -1; } if (virDomainChrDefForeach(vm->def, true, SELinuxSetSecurityChardevCallback, vm) < 0) return -1; if (virDomainSmartcardDefForeach(vm->def, true, SELinuxSetSecuritySmartcardCallback, vm) < 0) return -1; if (vm->def->os.kernel && SELinuxSetFilecon(vm->def->os.kernel, default_content_context) < 0) return -1; if (vm->def->os.initrd && SELinuxSetFilecon(vm->def->os.initrd, default_content_context) < 0) return -1; if (stdin_path) { if (SELinuxSetFilecon(stdin_path, default_content_context) < 0 && virStorageFileIsSharedFSType(stdin_path, VIR_STORAGE_FILE_SHFS_NFS) != 1) return -1; } return 0; } static int SELinuxSetImageFDLabel(virSecurityManagerPtr mgr ATTRIBUTE_UNUSED, virDomainObjPtr vm, int fd) { const virSecurityLabelDefPtr secdef = &vm->def->seclabel; if (secdef->imagelabel == NULL) return 0; return SELinuxFSetFilecon(fd, secdef->imagelabel); } virSecurityDriver virSecurityDriverSELinux = { 0, SECURITY_SELINUX_NAME, SELinuxSecurityDriverProbe, SELinuxSecurityDriverOpen, SELinuxSecurityDriverClose, SELinuxSecurityGetModel, SELinuxSecurityGetDOI, SELinuxSecurityVerify, SELinuxSetSecurityImageLabel, SELinuxRestoreSecurityImageLabel, SELinuxSetSecurityDaemonSocketLabel, SELinuxSetSecuritySocketLabel, SELinuxClearSecuritySocketLabel, SELinuxGenSecurityLabel, SELinuxReserveSecurityLabel, SELinuxReleaseSecurityLabel, SELinuxGetSecurityProcessLabel, SELinuxSetSecurityProcessLabel, SELinuxSetSecurityAllLabel, SELinuxRestoreSecurityAllLabel, SELinuxSetSecurityHostdevLabel, SELinuxRestoreSecurityHostdevLabel, SELinuxSetSavedStateLabel, SELinuxRestoreSavedStateLabel, SELinuxSetImageFDLabel, };