From 0714b2ba4c2e376625328d6b0ba956969cc8cd22 Mon Sep 17 00:00:00 2001 From: "Daniel P. Berrange" Date: Wed, 15 Jul 2009 22:25:01 +0100 Subject: [PATCH] Run QEMU guests as an unprivileged user * configure.in: Add --with-qemu-user and --with-qemu-group args * libvirt.spec.in: use 'qemu' for user/group for Fedora >= 12 * qemud/libvirtd_qemu.arg, qemud/test_libvirtd_qemu.aug, src/qemu.conf: Add 'user' and 'group' args for configuration * src/Makefile.am: Create %localstatedir/cache/libvirt/qemu * src/qemu_conf.c, src/qemu_conf.h: Load user/group from config * src/qemu_driver.c: Change user ID/group ID when launching QEMU guests. Change user/group ownership on disks/usb/pci devs. Put memory dumps in %localstatedir/cache/libvirt/qemu * src/util.c, src/util.h: Add convenient APIs for converting username/groupname to user ID / group ID --- configure.in | 17 +++ libvirt.spec.in | 15 +- qemud/libvirtd_qemu.aug | 2 + qemud/test_libvirtd_qemu.aug | 8 ++ src/Makefile.am | 1 + src/libvirt_private.syms | 2 + src/qemu.conf | 7 + src/qemu_conf.c | 32 +++++ src/qemu_conf.h | 3 + src/qemu_driver.c | 259 +++++++++++++++++++++++++++++++++-- src/util.c | 75 ++++++++++ src/util.h | 6 + 12 files changed, 417 insertions(+), 10 deletions(-) diff --git a/configure.in b/configure.in index 552089a881..634e812b98 100644 --- a/configure.in +++ b/configure.in @@ -1437,6 +1437,19 @@ AM_CONDITIONAL([WITH_NODE_DEVICES], [test "$with_nodedev" = "yes"]) AM_CONDITIONAL([WITH_LINUX], [test `uname -s` = "Linux"]) + +AC_ARG_WITH([qemu-user], + [ --with-qemu-user username to run QEMU system instance as], + [QEMU_USER=${withval}], + [QEMU_USER=root]) +AC_ARG_WITH([qemu-group], + [ --with-qemu-group groupname to run QEMU system instance as], + [QEMU_GROUP=${withval}], + [QEMU_GROUP=root]) +AC_DEFINE_UNQUOTED([QEMU_USER], ["$QEMU_USER"], [QEMU user account]) +AC_DEFINE_UNQUOTED([QEMU_GROUP], ["$QEMU_GROUP"], [QEMU group account]) + + # Only COPYING.LIB is under version control, yet COPYING # is included as part of the distribution tarball. # Copy one to the other, but only if this is a srcdir-build. @@ -1578,3 +1591,7 @@ AC_MSG_NOTICE([ Debug: $enable_debug]) AC_MSG_NOTICE([ Warnings: $enable_compile_warnings]) AC_MSG_NOTICE([ Readline: $lv_use_readline]) AC_MSG_NOTICE([]) +AC_MSG_NOTICE([Privileges]) +AC_MSG_NOTICE([]) +AC_MSG_NOTICE([ QEMU: $QEMU_USER:$QEMU_GROUP]) +AC_MSG_NOTICE([]) \ No newline at end of file diff --git a/libvirt.spec.in b/libvirt.spec.in index ffac3d4585..a561aad92b 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -46,6 +46,14 @@ %define with_capng 0%{!?_without_capng:1} %endif +%if 0%{?fedora} >= 12 +%define qemu_user qemu +%define qemu_group qemu +%else +%define qemu_user root +%define qemu_group root +%endif + # # If building on RHEL switch on the specific support # @@ -309,6 +317,8 @@ of recent versions of Linux (and other OSes). %{?_without_storage_iscsi} \ %{?_without_storage_disk} \ %{?_without_numactl} \ + --with-qemu-user=%{qemu_user} \ + --with-qemu-group=%{qemu_group} \ --with-init-script=redhat \ --with-remote-pid-file=%{_localstatedir}/run/libvirtd.pid make %{?_smp_mflags} @@ -445,8 +455,9 @@ fi %dir %attr(0700, root, root) %{_localstatedir}/cache/libvirt/ %if %{with_qemu} -%dir %{_localstatedir}/run/libvirt/qemu/ -%dir %attr(0700, root, root) %{_localstatedir}/lib/libvirt/qemu/ +%dir %attr(0700, %{qemu_user}, %{qemu_group}) %{_localstatedir}/run/libvirt/qemu/ +%dir %attr(0700, %{qemu_user}, %{qemu_group}) %{_localstatedir}/lib/libvirt/qemu/ +%dir %attr(0700, %{qemu_user}, %{qemu_group}) %{_localstatedir}/cache/libvirt/qemu/ %endif %if %{with_lxc} %dir %{_localstatedir}/run/libvirt/lxc/ diff --git a/qemud/libvirtd_qemu.aug b/qemud/libvirtd_qemu.aug index ff7e076fe4..8cf0461c54 100644 --- a/qemud/libvirtd_qemu.aug +++ b/qemud/libvirtd_qemu.aug @@ -29,6 +29,8 @@ module Libvirtd_qemu = | str_entry "vnc_password" | bool_entry "vnc_sasl" | str_entry "vnc_sasl_dir" + | str_entry "user" + | str_entry "group" (* Each enty in the config is one of the following three ... *) let entry = vnc_entry diff --git a/qemud/test_libvirtd_qemu.aug b/qemud/test_libvirtd_qemu.aug index 6f38e47e3b..f62da0106f 100644 --- a/qemud/test_libvirtd_qemu.aug +++ b/qemud/test_libvirtd_qemu.aug @@ -79,6 +79,10 @@ vnc_sasl = 1 # point to the directory, and create a qemu.conf in that location # vnc_sasl_dir = \"/some/directory/sasl2\" + +user = \"root\" + +group = \"root\" " test Libvirtd_qemu.lns get conf = @@ -161,3 +165,7 @@ vnc_sasl_dir = \"/some/directory/sasl2\" { "#comment" = "point to the directory, and create a qemu.conf in that location" } { "#comment" = "" } { "vnc_sasl_dir" = "/some/directory/sasl2" } +{ "#comment" = "" } +{ "user"= "root" } +{ "#comment" = "" } +{ "group" = "root" } \ No newline at end of file diff --git a/src/Makefile.am b/src/Makefile.am index 889ede4b7d..9b662ae702 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -684,6 +684,7 @@ install-exec-local: if WITH_QEMU $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/qemu" $(MKDIR_P) "$(DESTDIR)$(localstatedir)/run/libvirt/qemu" + $(MKDIR_P) "$(DESTDIR)$(localstatedir)/cache/libvirt/qemu" endif if WITH_LXC $(MKDIR_P) "$(DESTDIR)$(localstatedir)/lib/libvirt/lxc" diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index 0534d53a48..59c78d53f8 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -378,6 +378,8 @@ virRun; virSkipSpaces; virKillProcess; virGetUserDirectory; +virGetUserID; +virGetGroupID; # uuid.h diff --git a/src/qemu.conf b/src/qemu.conf index c2a53c5cc0..300972548a 100644 --- a/src/qemu.conf +++ b/src/qemu.conf @@ -88,3 +88,10 @@ # to 'none' instead # # security_driver = "selinux" + + +# The user ID for QEMU processes run by the system instance +#user = "root" + +# The group ID for QEMU processes run by the system instance +#group = "root" diff --git a/src/qemu_conf.c b/src/qemu_conf.c index 0c28c20cce..4043d706af 100644 --- a/src/qemu_conf.c +++ b/src/qemu_conf.c @@ -92,6 +92,8 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, const char *filename) { virConfPtr conf; virConfValuePtr p; + char *user; + char *group; /* Setup 2 critical defaults */ if (!(driver->vncListen = strdup("127.0.0.1"))) { @@ -186,6 +188,36 @@ int qemudLoadDriverConfig(struct qemud_driver *driver, } } + p = virConfGetValue (conf, "user"); + CHECK_TYPE ("user", VIR_CONF_STRING); + if (!(user = strdup(p && p->str ? p->str : QEMU_USER))) { + virReportOOMError(NULL); + virConfFree(conf); + return -1; + } + if (virGetUserID(NULL, user, &driver->user) < 0) { + VIR_FREE(user); + virConfFree(conf); + return -1; + } + VIR_FREE(user); + + p = virConfGetValue (conf, "group"); + CHECK_TYPE ("group", VIR_CONF_STRING); + if (!(group = strdup(p && p->str ? p->str : QEMU_GROUP))) { + virReportOOMError(NULL); + virConfFree(conf); + return -1; + } + + + if (virGetGroupID(NULL, group, &driver->group) < 0) { + VIR_FREE(group); + virConfFree(conf); + return -1; + } + VIR_FREE(group); + virConfFree (conf); return 0; } diff --git a/src/qemu_conf.h b/src/qemu_conf.h index 175173d95a..fbf2ab91f6 100644 --- a/src/qemu_conf.h +++ b/src/qemu_conf.h @@ -66,6 +66,9 @@ struct qemud_driver { int privileged; + uid_t user; + gid_t group; + unsigned int qemuVersion; int nextvmid; diff --git a/src/qemu_driver.c b/src/qemu_driver.c index e41dfe8080..429235c196 100644 --- a/src/qemu_driver.c +++ b/src/qemu_driver.c @@ -71,12 +71,11 @@ #define VIR_FROM_THIS VIR_FROM_QEMU /* For storing short-lived temporary files. */ -#define TEMPDIR LOCAL_STATE_DIR "/cache/libvirt" +#define TEMPDIR LOCAL_STATE_DIR "/cache/libvirt/qemu" #define QEMU_CMD_PROMPT "\n(qemu) " #define QEMU_PASSWD_PROMPT "Password: " - static int qemudShutdown(void); static void qemuDriverLock(struct qemud_driver *driver) @@ -1367,6 +1366,205 @@ static int qemudDomainSetSecurityLabel(virConnectPtr conn, struct qemud_driver * return 0; } + +#ifdef __linux__ +static int qemuDomainSetHostdevUSBOwnership(virConnectPtr conn, + virDomainHostdevDefPtr def, + uid_t uid, gid_t gid) +{ + char *usbpath = NULL; + + /* XXX what todo for USB devs assigned based on product/vendor ? Doom :-( */ + if (!def->source.subsys.u.usb.bus || + !def->source.subsys.u.usb.device) + return 0; + + if (virAsprintf(&usbpath, "/dev/bus/usb/%03d/%03d", + def->source.subsys.u.usb.bus, + def->source.subsys.u.usb.device) < 0) { + virReportOOMError(conn); + return -1; + } + + VIR_DEBUG("Setting ownership on %s to %d:%d", usbpath, uid, gid); + if (chown(usbpath, uid, gid) < 0) { + virReportSystemError(conn, errno, _("cannot set ownership on %s"), usbpath); + VIR_FREE(usbpath); + return -1; + } + VIR_FREE(usbpath); + + return 0; +} + +static int qemuDomainSetHostdevPCIOwnership(virConnectPtr conn, + virDomainHostdevDefPtr def, + uid_t uid, gid_t gid) +{ + char *pcidir = NULL; + char *file = NULL; + DIR *dir = NULL; + int ret = -1; + struct dirent *ent; + + if (virAsprintf(&pcidir, "/sys/bus/pci/devices/%04x:%02x:%02x.%x", + def->source.subsys.u.pci.domain, + def->source.subsys.u.pci.bus, + def->source.subsys.u.pci.slot, + def->source.subsys.u.pci.function) < 0) { + virReportOOMError(conn); + goto cleanup; + } + + if (!(dir = opendir(pcidir))) { + virReportSystemError(conn, errno, + _("cannot open %s"), pcidir); + goto cleanup; + } + + while ((ent = readdir(dir)) != NULL) { + /* QEMU device assignment requires: + * $PCIDIR/config, $PCIDIR/resource, $PCIDIR/resourceNNN, $PCIDIR/rom + */ + if (STREQ(ent->d_name, "config") || + STRPREFIX(ent->d_name, "resource") || + STREQ(ent->d_name, "rom")) { + if (virAsprintf(&file, "%s/%s", pcidir, ent->d_name) < 0) { + virReportOOMError(conn); + goto cleanup; + } + VIR_DEBUG("Setting ownership on %s to %d:%d", file, uid, gid); + if (chown(file, uid, gid) < 0) { + virReportSystemError(conn, errno, _("cannot set ownership on %s"), file); + goto cleanup; + } + VIR_FREE(file); + } + } + + ret = 0; + +cleanup: + if (dir) + closedir(dir); + VIR_FREE(file); + VIR_FREE(pcidir); + return ret; +} +#endif + + +static int qemuDomainSetHostdevOwnership(virConnectPtr conn, + virDomainHostdevDefPtr def, + uid_t uid, gid_t gid) +{ + if (def->mode != VIR_DOMAIN_HOSTDEV_MODE_SUBSYS) + return 0; + +#ifdef __linux__ + switch (def->source.subsys.type) { + case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB: + return qemuDomainSetHostdevUSBOwnership(conn, def, uid, gid); + + case VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_PCI: + return qemuDomainSetHostdevPCIOwnership(conn, def, uid, gid); + + } + return 0; +#else + qemudReportError(conn, NULL, NULL, "%s", + _("unable to set host device ownership on this platform")); + return -1; +#endif + +} + +static int qemuDomainSetDiskOwnership(virConnectPtr conn, + virDomainDiskDefPtr def, + uid_t uid, gid_t gid) +{ + + if (!def->src) + return 0; + + VIR_DEBUG("Setting ownership on %s to %d:%d", def->src, uid, gid); + if (chown(def->src, uid, gid) < 0) { + virReportSystemError(conn, errno, _("cannot set ownership on %s"), + def->src); + return -1; + } + return 0; +} + +static int qemuDomainSetDeviceOwnership(virConnectPtr conn, + struct qemud_driver *driver, + virDomainDeviceDefPtr def, + int restore) +{ + uid_t uid; + gid_t gid; + + if (!driver->privileged) + return 0; + + /* short circuit case of root:root */ + if (!driver->user && !driver->group) + return 0; + + uid = restore ? 0 : driver->user; + gid = restore ? 0 : driver->group; + + switch (def->type) { + case VIR_DOMAIN_DEVICE_DISK: + if (restore && + (def->data.disk->readonly || def->data.disk->shared)) + return 0; + + return qemuDomainSetDiskOwnership(conn, def->data.disk, uid, gid); + + case VIR_DOMAIN_DEVICE_HOSTDEV: + return qemuDomainSetHostdevOwnership(conn, def->data.hostdev, uid, gid); + } + + return 0; +} + +static int qemuDomainSetAllDeviceOwnership(virConnectPtr conn, + struct qemud_driver *driver, + virDomainDefPtr def, + int restore) +{ + int i; + uid_t uid; + gid_t gid; + + if (!driver->privileged) + return 0; + + /* short circuit case of root:root */ + if (!driver->user && !driver->group) + return 0; + + uid = restore ? 0 : driver->user; + gid = restore ? 0 : driver->group; + + for (i = 0 ; i < def->ndisks ; i++) { + if (restore && + (def->disks[i]->readonly || def->disks[i]->shared)) + continue; + + if (qemuDomainSetDiskOwnership(conn, def->disks[i], uid, gid) < 0) + return -1; + } + + for (i = 0 ; i < def->nhostdevs ; i++) { + if (qemuDomainSetHostdevOwnership(conn, def->hostdevs[i], uid, gid) < 0) + return -1; + } + + return 0; +} + static virDomainPtr qemudDomainLookupByName(virConnectPtr conn, const char *name); @@ -1377,14 +1575,39 @@ struct gemudHookData { }; static int qemudSecurityHook(void *data) { - struct gemudHookData *h = (struct gemudHookData *) data; + struct gemudHookData *h = (struct gemudHookData *) data; - if (qemudDomainSetSecurityLabel(h->conn, h->driver, h->vm) < 0) { - qemudReportError(h->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, - _("Failed to set security label")); + if (qemudDomainSetSecurityLabel(h->conn, h->driver, h->vm) < 0) { + qemudReportError(h->conn, NULL, NULL, VIR_ERR_INTERNAL_ERROR, + _("Failed to set security label")); + return -1; + } + + if (h->driver->privileged) { + DEBUG("Dropping privileges of VM to %d:%d", h->driver->user, h->driver->group); + + if (qemuDomainSetAllDeviceOwnership(h->conn, h->driver, h->vm->def, 0) < 0) + return -1; + + if (h->driver->group) { + if (setregid(h->driver->group, h->driver->group) < 0) { + virReportSystemError(NULL, errno, + _("cannot change to '%d' group"), + h->driver->group); return -1; + } } - return 0; + if (h->driver->user) { + if (setreuid(h->driver->user, h->driver->user) < 0) { + virReportSystemError(NULL, errno, + _("cannot change to '%d' user"), + h->driver->user); + return -1; + } + } + } + + return 0; } static int @@ -1662,6 +1885,10 @@ static void qemudShutdownVMDaemon(virConnectPtr conn ATTRIBUTE_UNUSED, VIR_FREE(vm->def->seclabel.imagelabel); } + if (qemuDomainSetAllDeviceOwnership(conn, driver, vm->def, 1) < 0) + VIR_WARN("Failed to restore all device ownership for %s", + vm->def->name); + if (qemudRemoveDomainStatus(conn, driver, vm) < 0) { VIR_WARN(_("Failed to remove domain status for %s"), vm->def->name); @@ -4329,12 +4556,20 @@ static int qemudDomainAttachDevice(virDomainPtr dom, case VIR_DOMAIN_DISK_DEVICE_FLOPPY: if (driver->securityDriver) driver->securityDriver->domainSetSecurityImageLabel(dom->conn, vm, dev->data.disk); + + if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 0) < 0) + goto cleanup; + ret = qemudDomainChangeEjectableMedia(dom->conn, vm, dev); break; case VIR_DOMAIN_DISK_DEVICE_DISK: if (driver->securityDriver) driver->securityDriver->domainSetSecurityImageLabel(dom->conn, vm, dev->data.disk); + + if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 0) < 0) + goto cleanup; + if (dev->data.disk->bus == VIR_DOMAIN_DISK_BUS_USB) { ret = qemudDomainAttachUsbMassstorageDevice(dom->conn, vm, dev); } else if (dev->data.disk->bus == VIR_DOMAIN_DISK_BUS_SCSI || @@ -4357,6 +4592,9 @@ static int qemudDomainAttachDevice(virDomainPtr dom, } else if (dev->type == VIR_DOMAIN_DEVICE_HOSTDEV && dev->data.hostdev->mode == VIR_DOMAIN_HOSTDEV_MODE_SUBSYS && dev->data.hostdev->source.subsys.type == VIR_DOMAIN_HOSTDEV_SUBSYS_TYPE_USB) { + if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 0) < 0) + goto cleanup; + ret = qemudDomainAttachHostDevice(dom->conn, vm, dev); } else { qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, @@ -4369,8 +4607,11 @@ static int qemudDomainAttachDevice(virDomainPtr dom, ret = -1; cleanup: - if (ret < 0) + if (ret < 0) { + if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 1) < 0) + VIR_WARN0("Fail to restore disk device ownership"); virDomainDeviceDefFree(dev); + } if (vm) virDomainObjUnlock(vm); qemuDriverUnlock(driver); @@ -4498,6 +4739,8 @@ static int qemudDomainDetachDevice(virDomainPtr dom, ret = qemudDomainDetachPciDiskDevice(dom->conn, vm, dev); if (driver->securityDriver) driver->securityDriver->domainRestoreSecurityImageLabel(dom->conn, dev->data.disk); + if (qemuDomainSetDeviceOwnership(dom->conn, driver, dev, 1) < 0) + VIR_WARN0("Fail to restore disk device ownership"); } else qemudReportError(dom->conn, dom, NULL, VIR_ERR_NO_SUPPORT, diff --git a/src/util.c b/src/util.c index 178ff0c810..2420f8bcb4 100644 --- a/src/util.c +++ b/src/util.c @@ -55,6 +55,7 @@ #include #ifdef HAVE_GETPWUID_R #include +#include #endif #if HAVE_CAPNG #include @@ -1862,4 +1863,78 @@ char *virGetUserDirectory(virConnectPtr conn, return ret; } + + +int virGetUserID(virConnectPtr conn, + const char *name, + uid_t *uid) +{ + char *strbuf; + struct passwd pwbuf; + struct passwd *pw = NULL; + size_t strbuflen = sysconf(_SC_GETPW_R_SIZE_MAX); + + if (VIR_ALLOC_N(strbuf, strbuflen) < 0) { + virReportOOMError(conn); + return -1; + } + + /* + * From the manpage (terrifying but true): + * + * ERRORS + * 0 or ENOENT or ESRCH or EBADF or EPERM or ... + * The given name or uid was not found. + */ + if (getpwnam_r(name, &pwbuf, strbuf, strbuflen, &pw) != 0 || pw == NULL) { + virReportSystemError(conn, errno, + _("Failed to find user record for name '%s'"), + name); + VIR_FREE(strbuf); + return -1; + } + + *uid = pw->pw_uid; + + VIR_FREE(strbuf); + + return 0; +} + + +int virGetGroupID(virConnectPtr conn, + const char *name, + gid_t *gid) +{ + char *strbuf; + struct group grbuf; + struct group *gr = NULL; + size_t strbuflen = sysconf(_SC_GETGR_R_SIZE_MAX); + + if (VIR_ALLOC_N(strbuf, strbuflen) < 0) { + virReportOOMError(conn); + return -1; + } + + /* + * From the manpage (terrifying but true): + * + * ERRORS + * 0 or ENOENT or ESRCH or EBADF or EPERM or ... + * The given name or uid was not found. + */ + if (getgrnam_r(name, &grbuf, strbuf, strbuflen, &gr) != 0 || gr == NULL) { + virReportSystemError(conn, errno, + _("Failed to find group record for name '%s'"), + name); + VIR_FREE(strbuf); + return -1; + } + + *gid = gr->gr_gid; + + VIR_FREE(strbuf); + + return 0; +} #endif diff --git a/src/util.h b/src/util.h index 6dd005fe6a..1a7286cd66 100644 --- a/src/util.h +++ b/src/util.h @@ -217,6 +217,12 @@ int virKillProcess(pid_t pid, int sig); #ifdef HAVE_GETPWUID_R char *virGetUserDirectory(virConnectPtr conn, uid_t uid); +int virGetUserID(virConnectPtr conn, + const char *name, + uid_t *uid); +int virGetGroupID(virConnectPtr conn, + const char *name, + gid_t *gid); #endif int virRandomInitialize(unsigned int seed);