This new security driver is responsible for managing UID/GID changes to the QEMU process, and any files/disks/devices assigned to it. * qemu/qemu_conf.h: Add flag for disabling automatic file permission changes * qemu/qemu_security_dac.h, qemu/qemu_security_dac.c: New DAC driver for QEMU guests * Makefile.am: Add new files --- po/POTFILES.in | 1 + src/Makefile.am | 4 +- src/qemu/qemu_conf.h | 1 + src/qemu/qemu_security_dac.c | 458 ++++++++++++++++++++++++++++++++++++++++++ src/qemu/qemu_security_dac.h | 22 ++ 5 files changed, 485 insertions(+), 1 deletions(-) create mode 100644 src/qemu/qemu_security_dac.c create mode 100644 src/qemu/qemu_security_dac.h diff --git a/po/POTFILES.in b/po/POTFILES.in index 3b82a74..18f5243 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -36,6 +36,7 @@ src/qemu/qemu_driver.c src/qemu/qemu_monitor.c src/qemu/qemu_monitor_json.c src/qemu/qemu_monitor_text.c +src/qemu/qemu_security_dac.c src/remote/remote_driver.c src/secret/secret_driver.c src/security/security_driver.c diff --git a/src/Makefile.am b/src/Makefile.am index 0fb6dba..3232256 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -200,7 +200,9 @@ QEMU_DRIVER_SOURCES = \ qemu/qemu_bridge_filter.c \ qemu/qemu_bridge_filter.h \ qemu/qemu_security_stacked.h \ - qemu/qemu_security_stacked.c + qemu/qemu_security_stacked.c \ + qemu/qemu_security_dac.h \ + qemu/qemu_security_dac.c UML_DRIVER_SOURCES = \ uml/uml_conf.c uml/uml_conf.h \ diff --git a/src/qemu/qemu_conf.h b/src/qemu/qemu_conf.h index 678bc6f..e4a10bb 100644 --- a/src/qemu/qemu_conf.h +++ b/src/qemu/qemu_conf.h @@ -90,6 +90,7 @@ struct qemud_driver { uid_t user; gid_t group; + int dynamicOwnership; unsigned int qemuVersion; int nextvmid; diff --git a/src/qemu/qemu_security_dac.c b/src/qemu/qemu_security_dac.c new file mode 100644 index 0000000..6b7aab5 --- /dev/null +++ b/src/qemu/qemu_security_dac.c @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2010 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. + * + * QEMU POSIX DAC security driver + */ +#include <config.h> +#include <selinux/selinux.h> +#include <selinux/context.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include "qemu_security_dac.h" +#include "qemu_conf.h" +#include "datatypes.h" +#include "virterror_internal.h" +#include "util.h" +#include "memory.h" +#include "logging.h" +#include "pci.h" +#include "hostusb.h" +#include "storage_file.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +static struct qemud_driver *driver; + +void qemuSecurityDACSetDriver(struct qemud_driver *newdriver) +{ + driver = newdriver; +} + + +static int +qemuSecurityDACSetOwnership(virConnectPtr conn, const char *path, int uid, int gid) +{ + VIR_INFO("Setting DAC context on '%s' to '%d:%d'", path, uid, gid); + + if (chown(path, uid, gid) < 0) { + struct stat sb; + int chown_errno = errno; + + if (stat(path, &sb) >= 0) { + if (sb.st_uid == uid && + sb.st_gid == gid) { + /* It's alright, there's nothing to change anyway. */ + return 0; + } + } + + /* 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 qemuSecurityDAC + * virt_use_{nfs,usb,pci} boolean tunables to allow it... + */ + if (chown_errno == EOPNOTSUPP) { + VIR_INFO("Setting security context '%d:%d' on '%s' not supported by filesystem", + uid, gid, path); + } else if (chown_errno == EPERM) { + VIR_INFO("Setting security context '%d:%d' on '%s' not permitted", + uid, gid, path); + } else if (chown_errno == EROFS) { + VIR_INFO("Setting security context '%d:%d' on '%s' not possible on readonly filesystem", + uid, gid, path); + } else { + virReportSystemError(conn, chown_errno, + _("unable to set security context '%d:%d' on '%s'"), + uid, gid, path); + return -1; + } + } + return 0; +} + +static int +qemuSecurityDACRestoreSecurityFileLabel(virConnectPtr conn, + const char *path) +{ + struct stat buf; + security_context_t fcon = NULL; + int rc = -1; + int err; + char *newpath = NULL; + + VIR_INFO("Restoring DAC context on '%s'", path); + + if ((err = virFileResolveLink(path, &newpath)) < 0) { + virReportSystemError(conn, err, + _("cannot resolve symlink %s"), path); + goto err; + } + + if (stat(newpath, &buf) != 0) + goto err; + + /* XXX record previous ownership */ + rc = qemuSecurityDACSetOwnership(conn, newpath, 0, 0); + +err: + VIR_FREE(fcon); + VIR_FREE(newpath); + return rc; +} + + +static int +qemuSecurityDACSetSecurityImageLabel(virConnectPtr conn, + virDomainObjPtr vm ATTRIBUTE_UNUSED, + virDomainDiskDefPtr disk) + +{ + const char *path; + + if (!driver->privileged || !driver->dynamicOwnership) + return 0; + + if (!disk->src) + return 0; + + path = disk->src; + do { + virStorageFileMetadata meta; + int ret; + + memset(&meta, 0, sizeof(meta)); + + ret = virStorageFileGetMetadata(conn, path, &meta); + + if (path != disk->src) + VIR_FREE(path); + path = NULL; + + if (ret < 0) + return -1; + + if (meta.backingStore != NULL && + qemuSecurityDACSetOwnership(conn, meta.backingStore, + driver->user, driver->group) < 0) { + VIR_FREE(meta.backingStore); + return -1; + } + + path = meta.backingStore; + } while (path != NULL); + + return qemuSecurityDACSetOwnership(conn, disk->src, driver->user, driver->group); +} + + +static int +qemuSecurityDACRestoreSecurityImageLabel(virConnectPtr conn, + virDomainObjPtr vm ATTRIBUTE_UNUSED, + virDomainDiskDefPtr disk) +{ + if (!driver->privileged || !driver->dynamicOwnership) + 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) + return 0; + + return qemuSecurityDACRestoreSecurityFileLabel(conn, disk->src); +} + + +static int +qemuSecurityDACSetSecurityPCILabel(virConnectPtr conn, + pciDevice *dev ATTRIBUTE_UNUSED, + const char *file, + void *opaque ATTRIBUTE_UNUSED) +{ + return qemuSecurityDACSetOwnership(conn, file, driver->user, driver->group); +} + + +static int +qemuSecurityDACSetSecurityUSBLabel(virConnectPtr conn, + usbDevice *dev ATTRIBUTE_UNUSED, + const char *file, + void *opaque ATTRIBUTE_UNUSED) +{ + return qemuSecurityDACSetOwnership(conn, file, driver->user, driver->group); +} + + +static int +qemuSecurityDACSetSecurityHostdevLabel(virConnectPtr conn, + virDomainObjPtr vm, + virDomainHostdevDefPtr dev) + +{ + int ret = -1; + + if (!driver->privileged || !driver->dynamicOwnership) + 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(conn, + dev->source.subsys.u.usb.bus, + dev->source.subsys.u.usb.device, + dev->source.subsys.u.usb.vendor, + dev->source.subsys.u.usb.product); + + if (!usb) + goto done; + + ret = usbDeviceFileIterate(conn, usb, qemuSecurityDACSetSecurityUSBLabel, vm); + usbFreeDevice(conn, usb); + break; + } + + case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: { + pciDevice *pci = pciGetDevice(conn, + 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(conn, pci, qemuSecurityDACSetSecurityPCILabel, vm); + pciFreeDevice(conn, pci); + + break; + } + + default: + ret = 0; + break; + } + +done: + return ret; +} + + +static int +qemuSecurityDACRestoreSecurityPCILabel(virConnectPtr conn, + pciDevice *dev ATTRIBUTE_UNUSED, + const char *file, + void *opaque ATTRIBUTE_UNUSED) +{ + return qemuSecurityDACRestoreSecurityFileLabel(conn, file); +} + + +static int +qemuSecurityDACRestoreSecurityUSBLabel(virConnectPtr conn, + usbDevice *dev ATTRIBUTE_UNUSED, + const char *file, + void *opaque ATTRIBUTE_UNUSED) +{ + return qemuSecurityDACRestoreSecurityFileLabel(conn, file); +} + + +static int +qemuSecurityDACRestoreSecurityHostdevLabel(virConnectPtr conn, + virDomainObjPtr vm ATTRIBUTE_UNUSED, + virDomainHostdevDefPtr dev) + +{ + int ret = -1; + + if (!driver->privileged || !driver->dynamicOwnership) + 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(conn, + dev->source.subsys.u.usb.bus, + dev->source.subsys.u.usb.device, + dev->source.subsys.u.usb.vendor, + dev->source.subsys.u.usb.product); + + if (!usb) + goto done; + + ret = usbDeviceFileIterate(conn, usb, qemuSecurityDACRestoreSecurityUSBLabel, NULL); + usbFreeDevice(conn, usb); + + break; + } + + case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: { + pciDevice *pci = pciGetDevice(conn, + 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(conn, pci, qemuSecurityDACRestoreSecurityPCILabel, NULL); + pciFreeDevice(conn, pci); + + break; + } + + default: + ret = 0; + break; + } + +done: + return ret; +} + + +static int +qemuSecurityDACRestoreSecurityAllLabel(virConnectPtr conn, + virDomainObjPtr vm) +{ + int i; + int rc = 0; + + if (!driver->privileged || !driver->dynamicOwnership) + return 0; + + VIR_DEBUG("Restoring security label on %s", vm->def->name); + + for (i = 0 ; i < vm->def->nhostdevs ; i++) { + if (qemuSecurityDACRestoreSecurityHostdevLabel(conn, vm, + vm->def->hostdevs[i]) < 0) + rc = -1; + } + for (i = 0 ; i < vm->def->ndisks ; i++) { + if (qemuSecurityDACRestoreSecurityImageLabel(conn, vm, + vm->def->disks[i]) < 0) + rc = -1; + } + return rc; +} + + +static int +qemuSecurityDACSetSecurityAllLabel(virConnectPtr conn, + virDomainObjPtr vm) +{ + int i; + + if (!driver->privileged || !driver->dynamicOwnership) + return 0; + + for (i = 0 ; i < vm->def->ndisks ; i++) { + /* XXX fixme - we need to recursively label the entriy tree :-( */ + if (vm->def->disks[i]->type == VIR_DOMAIN_DISK_TYPE_DIR) + continue; + if (qemuSecurityDACSetSecurityImageLabel(conn, vm, vm->def->disks[i]) < 0) + return -1; + } + for (i = 0 ; i < vm->def->nhostdevs ; i++) { + if (qemuSecurityDACSetSecurityHostdevLabel(conn, vm, vm->def->hostdevs[i]) < 0) + return -1; + } + + return 0; +} + + +static int +qemuSecurityDACSetSavedStateLabel(virConnectPtr conn, + virDomainObjPtr vm ATTRIBUTE_UNUSED, + const char *savefile) +{ + if (!driver->privileged || !driver->dynamicOwnership) + return 0; + + return qemuSecurityDACSetOwnership(conn, savefile, driver->user, driver->group); +} + + +static int +qemuSecurityDACRestoreSavedStateLabel(virConnectPtr conn, + virDomainObjPtr vm ATTRIBUTE_UNUSED, + const char *savefile) +{ + if (!driver->privileged || !driver->dynamicOwnership) + return 0; + + return qemuSecurityDACRestoreSecurityFileLabel(conn, savefile); +} + + +static int +qemuSecurityDACSetProcessLabel(virConnectPtr conn ATTRIBUTE_UNUSED, + virSecurityDriverPtr drv ATTRIBUTE_UNUSED, + virDomainObjPtr vm ATTRIBUTE_UNUSED) +{ + DEBUG("Dropping privileges of VM to %d:%d", driver->user, driver->group); + + if (!driver->privileged) + return 0; + + if (driver->group) { + if (setregid(driver->group, driver->group) < 0) { + virReportSystemError(NULL, errno, + _("cannot change to '%d' group"), + driver->group); + return -1; + } + } + if (driver->user) { + if (setreuid(driver->user, driver->user) < 0) { + virReportSystemError(NULL, errno, + _("cannot change to '%d' user"), + driver->user); + return -1; + } + } + + return 0; +} + + + +virSecurityDriver virqemuSecurityDACSecurityDriver = { + .name = "qemuDAC", + + .domainSetSecurityProcessLabel = qemuSecurityDACSetProcessLabel, + + .domainSetSecurityImageLabel = qemuSecurityDACSetSecurityImageLabel, + .domainRestoreSecurityImageLabel = qemuSecurityDACRestoreSecurityImageLabel, + + .domainSetSecurityAllLabel = qemuSecurityDACSetSecurityAllLabel, + .domainRestoreSecurityAllLabel = qemuSecurityDACRestoreSecurityAllLabel, + + .domainSetSecurityHostdevLabel = qemuSecurityDACSetSecurityHostdevLabel, + .domainRestoreSecurityHostdevLabel = qemuSecurityDACRestoreSecurityHostdevLabel, + + .domainSetSavedStateLabel = qemuSecurityDACSetSavedStateLabel, + .domainRestoreSavedStateLabel = qemuSecurityDACRestoreSavedStateLabel, +}; diff --git a/src/qemu/qemu_security_dac.h b/src/qemu/qemu_security_dac.h new file mode 100644 index 0000000..246ebe2 --- /dev/null +++ b/src/qemu/qemu_security_dac.h @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2010 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. + * + * QEMU POSIX DAC security driver + */ + +#include "security/security_driver.h" +#include "qemu_conf.h" + +#ifndef __QEMU_SECURITY_DAC +#define __QEMU_SECURITY_DAC + +extern virSecurityDriver qemuDACSecurityDriver; + +void qemuSecurityDACSetDriver(struct qemud_driver *driver); + +#endif /* __QEMU_SECURITY_DAC */ -- 1.6.5.2 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list