[libvirt] [PATCH 06/12] Introduce a new DAC security driver for QEMU

Daniel P. Berrange berrange at redhat.com
Wed Jan 20 15:15:03 UTC 2010


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




More information about the libvir-list mailing list