In a SELinux or root-squashing NFS environment, libvirt has to go through some hoops to create a new file that qemu can then open() by name. Snapshots are a case where we want to guarantee an empty file that qemu can open; also, reopening a save file to convert it from being marked partial to complete requires a reopen to avoid O_DIRECT headaches. Refactor some existing code to make it easier to reuse in later patches. * src/qemu/qemu_migration.h (qemuMigrationToFile): Drop parameter. * src/qemu/qemu_migration.c (qemuMigrationToFile): Let cgroup do the stat, rather than asking caller to do it and pass info down. * src/qemu/qemu_driver.c (qemuOpenFile): New function, pulled from... (qemuDomainSaveInternal): ...here. (doCoreDump, qemuDomainSaveImageOpen): Use it here as well. --- Originally posted here: https://www.redhat.com/archives/libvir-list/2011-August/msg01174.html but just a bit more tweaking made it useful for other purposes. src/qemu/qemu_driver.c | 248 ++++++++++++++++++++++---------------------- src/qemu/qemu_migration.c | 13 ++- src/qemu/qemu_migration.h | 2 +- 3 files changed, 133 insertions(+), 130 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index 4e8c691..9cb034b 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -2191,6 +2191,113 @@ qemuCompressProgramName(int compress) qemudSaveCompressionTypeToString(compress)); } +/* Internal function to properly create or open existing files, with + * ownership affected by qemu driver setup. */ +static int +qemuOpenFile(struct qemud_driver *driver, const char *path, int oflags, + bool *needUnlink, bool *bypassSecurityDriver) +{ + struct stat sb; + bool is_reg = true; + bool need_unlink = false; + bool bypass_security = false; + int fd = -1; + uid_t uid = getuid(); + gid_t gid = getgid(); + + /* path might be a pre-existing block dev, in which case + * we need to skip the create step, and also avoid unlink + * in the failure case */ + if (oflags & O_CREAT) { + need_unlink = true; + if (stat(path, &sb) == 0) { + is_reg = !!S_ISREG(sb.st_mode); + /* If the path is regular file which exists + * already and dynamic_ownership is off, we don't + * want to change it's ownership, just open it as-is */ + if (is_reg && !driver->dynamicOwnership) { + uid = sb.st_uid; + gid = sb.st_gid; + } + } + } + + /* First try creating the file as root */ + if (!is_reg) { + fd = open(path, oflags & ~O_CREAT); + if (fd < 0) { + virReportSystemError(errno, _("unable to open %s"), path); + goto cleanup; + } + } else { + if ((fd = virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, + uid, gid, 0)) < 0) { + /* If we failed as root, and the error was permission-denied + (EACCES or EPERM), assume it's on a network-connected share + where root access is restricted (eg, root-squashed NFS). If the + qemu user (driver->user) is non-root, just set a flag to + bypass security driver shenanigans, and retry the operation + after doing setuid to qemu user */ + if ((fd != -EACCES && fd != -EPERM) || + driver->user == getuid()) { + virReportSystemError(-fd, + _("Failed to create file '%s'"), + path); + goto cleanup; + } + + /* On Linux we can also verify the FS-type of the directory. */ + switch (virStorageFileIsSharedFS(path)) { + case 1: + /* it was on a network share, so we'll continue + * as outlined above + */ + break; + + case -1: + virReportSystemError(errno, + _("Failed to create file " + "'%s': couldn't determine fs type"), + path); + goto cleanup; + + case 0: + default: + /* local file - log the error returned by virFileOpenAs */ + virReportSystemError(-fd, + _("Failed to create file '%s'"), + path); + goto cleanup; + } + + /* Retry creating the file as driver->user */ + + if ((fd = virFileOpenAs(path, oflags, + S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, + driver->user, driver->group, + VIR_FILE_OPEN_AS_UID)) < 0) { + virReportSystemError(-fd, + _("Error from child process creating '%s'"), + path); + goto cleanup; + } + + /* Since we had to setuid to create the file, and the fstype + is NFS, we assume it's a root-squashing NFS share, and that + the security driver stuff would have failed anyway */ + + bypass_security = true; + } + } +cleanup: + if (needUnlink) + *needUnlink = need_unlink; + if (bypassSecurityDriver) + *bypassSecurityDriver = bypass_security; + + return fd; +} + /* This internal function expects the driver lock to already be held on * entry and the vm must be active + locked. Vm will be unlocked and * potentially free'd after this returns (eg transient VMs are freed @@ -2209,14 +2316,11 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, int rc; virDomainEventPtr event = NULL; qemuDomainObjPrivatePtr priv; - struct stat sb; - bool is_reg = false; + bool needUnlink = false; size_t len; unsigned long long offset; unsigned long long pad; int fd = -1; - uid_t uid = getuid(); - gid_t gid = getgid(); int directFlag = 0; virFileDirectFdPtr directFd = NULL; @@ -2304,107 +2408,18 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, header.xml_len = len; /* Obtain the file handle. */ - /* path might be a pre-existing block dev, in which case - * we need to skip the create step, and also avoid unlink - * in the failure case */ - if (stat(path, &sb) < 0) { - /* Avoid throwing an error here, since it is possible - * that with NFS we can't actually stat() the file. - * The subsequent codepaths will still raise an error - * if a truly fatal problem is hit */ - is_reg = true; - } else { - is_reg = !!S_ISREG(sb.st_mode); - /* If the path is regular file which exists - * already and dynamic_ownership is off, we don't - * want to change it's ownership, just open it as-is */ - if (is_reg && !driver->dynamicOwnership) { - uid=sb.st_uid; - gid=sb.st_gid; - } - } - - /* First try creating the file as root */ if (bypass_cache) { directFlag = virFileDirectFdFlag(); if (directFlag < 0) { qemuReportError(VIR_ERR_OPERATION_FAILED, "%s", _("bypass cache unsupported by this system")); - goto endjob; - } - } - if (!is_reg) { - fd = open(path, O_WRONLY | O_TRUNC | directFlag); - if (fd < 0) { - virReportSystemError(errno, _("unable to open %s"), path); - goto endjob; - } - } else { - int oflags = O_CREAT | O_TRUNC | O_WRONLY | directFlag; - if ((fd = virFileOpenAs(path, oflags, S_IRUSR | S_IWUSR, - uid, gid, 0)) < 0) { - /* If we failed as root, and the error was permission-denied - (EACCES or EPERM), assume it's on a network-connected share - where root access is restricted (eg, root-squashed NFS). If the - qemu user (driver->user) is non-root, just set a flag to - bypass security driver shenanigans, and retry the operation - after doing setuid to qemu user */ - rc = fd; - if (((rc != -EACCES) && (rc != -EPERM)) || - driver->user == getuid()) { - virReportSystemError(-rc, - _("Failed to create domain save file '%s'"), - path); - goto endjob; - } - - /* On Linux we can also verify the FS-type of the directory. */ - switch (virStorageFileIsSharedFS(path)) { - case 1: - /* it was on a network share, so we'll continue - * as outlined above - */ - break; - - case -1: - virReportSystemError(errno, - _("Failed to create domain save file " - "'%s': couldn't determine fs type"), - path); - goto endjob; - break; - - case 0: - default: - /* local file - log the error returned by virFileOpenAs */ - virReportSystemError(-rc, - _("Failed to create domain save file '%s'"), - path); - goto endjob; - break; - - } - - /* Retry creating the file as driver->user */ - - if ((fd = virFileOpenAs(path, oflags, - S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP, - driver->user, driver->group, - VIR_FILE_OPEN_AS_UID)) < 0) { - virReportSystemError(-fd, - _("Error from child process creating '%s'"), - path); - goto endjob; - } - - /* Since we had to setuid to create the file, and the fstype - is NFS, we assume it's a root-squashing NFS share, and that - the security driver stuff would have failed anyway */ - - bypassSecurityDriver = true; + goto cleanup; } } - + fd = qemuOpenFile(driver, path, O_WRONLY | O_TRUNC | O_CREAT | directFlag, + &needUnlink, &bypassSecurityDriver); + if (fd < 0) + goto endjob; if (bypass_cache && (directFd = virFileDirectFdNew(&fd, path)) == NULL) goto endjob; @@ -2417,7 +2432,7 @@ qemuDomainSaveInternal(struct qemud_driver *driver, virDomainPtr dom, /* Perform the migration */ if (qemuMigrationToFile(driver, vm, fd, offset, path, qemuCompressProgramName(compressed), - is_reg, bypassSecurityDriver, + bypassSecurityDriver, QEMU_ASYNC_JOB_SAVE) < 0) goto endjob; if (VIR_CLOSE(fd) < 0) { @@ -2461,7 +2476,7 @@ cleanup: VIR_FORCE_CLOSE(fd); virFileDirectFdFree(directFd); VIR_FREE(xml); - if (ret != 0 && is_reg) + if (ret != 0 && needUnlink) unlink(path); if (event) qemuDomainEventQueue(driver, event); @@ -2705,18 +2720,19 @@ doCoreDump(struct qemud_driver *driver, goto cleanup; } } - if ((fd = open(path, O_CREAT | O_TRUNC | O_WRONLY | directFlag, - S_IRUSR | S_IWUSR)) < 0) { - qemuReportError(VIR_ERR_OPERATION_FAILED, - _("failed to create '%s'"), path); + /* Core dumps usually imply last-ditch analysis efforts are + * desired, so we intentionally do not unlink even if a file was + * created. */ + if ((fd = qemuOpenFile(driver, path, + O_CREAT | O_TRUNC | O_WRONLY | directFlag, + NULL, NULL)) < 0) goto cleanup; - } if (bypass_cache && (directFd = virFileDirectFdNew(&fd, path)) == NULL) goto cleanup; if (qemuMigrationToFile(driver, vm, fd, 0, path, - qemuCompressProgramName(compress), true, false, + qemuCompressProgramName(compress), false, QEMU_ASYNC_JOB_DUMP) < 0) goto cleanup; @@ -3778,25 +3794,9 @@ qemuDomainSaveImageOpen(struct qemud_driver *driver, } oflags |= directFlag; } - if ((fd = virFileOpenAs(path, oflags, 0, - getuid(), getgid(), 0)) < 0) { - if ((fd != -EACCES && fd != -EPERM) || - driver->user == getuid()) { - qemuReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("cannot read domain image")); - goto error; - } - /* Opening as root failed, but qemu runs as a different user - * that might have better luck. */ - if ((fd = virFileOpenAs(path, oflags, 0, - driver->user, driver->group, - VIR_FILE_OPEN_AS_UID)) < 0) { - qemuReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("cannot read domain image")); - goto error; - } - } + if ((fd = qemuOpenFile(driver, path, oflags, NULL, NULL)) < 0) + goto error; if (bypass_cache && (*directFd = virFileDirectFdNew(&fd, path)) == NULL) goto error; diff --git a/src/qemu/qemu_migration.c b/src/qemu/qemu_migration.c index 38b05a9..d239cc8 100644 --- a/src/qemu/qemu_migration.c +++ b/src/qemu/qemu_migration.c @@ -2690,7 +2690,7 @@ int qemuMigrationToFile(struct qemud_driver *driver, virDomainObjPtr vm, int fd, off_t offset, const char *path, const char *compressor, - bool is_reg, bool bypassSecurityDriver, + bool bypassSecurityDriver, enum qemuDomainAsyncJob asyncJob) { qemuDomainObjPrivatePtr priv = vm->privateData; @@ -2713,11 +2713,11 @@ qemuMigrationToFile(struct qemud_driver *driver, virDomainObjPtr vm, bypassSecurityDriver = true; } else { /* Phooey - we have to fall back on exec migration, where qemu - * has to popen() the file by name. We might also stumble on + * has to popen() the file by name, and block devices have to be + * given cgroup ACL permission. We might also stumble on * a race present in some qemu versions where it does a wait() * that botches pclose. */ - if (!is_reg && - qemuCgroupControllerActive(driver, + if (qemuCgroupControllerActive(driver, VIR_CGROUP_CONTROLLER_DEVICES)) { if (virCgroupForDomain(driver->cgroup, vm->def->name, &cgroup, 0) != 0) { @@ -2729,7 +2729,10 @@ qemuMigrationToFile(struct qemud_driver *driver, virDomainObjPtr vm, rc = virCgroupAllowDevicePath(cgroup, path, VIR_CGROUP_DEVICE_RW); virDomainAuditCgroupPath(vm, cgroup, "allow", path, "rw", rc); - if (rc < 0) { + if (rc == 1) { + /* path was not a device, no further need for cgroup */ + virCgroupFree(&cgroup); + } else if (rc < 0) { virReportSystemError(-rc, _("Unable to allow device %s for %s"), path, vm->def->name); diff --git a/src/qemu/qemu_migration.h b/src/qemu/qemu_migration.h index 5c6921d..ace411d 100644 --- a/src/qemu/qemu_migration.h +++ b/src/qemu/qemu_migration.h @@ -143,7 +143,7 @@ int qemuMigrationConfirm(struct qemud_driver *driver, int qemuMigrationToFile(struct qemud_driver *driver, virDomainObjPtr vm, int fd, off_t offset, const char *path, const char *compressor, - bool is_reg, bool bypassSecurityDriver, + bool bypassSecurityDriver, enum qemuDomainAsyncJob asyncJob) ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(2) ATTRIBUTE_NONNULL(5) ATTRIBUTE_RETURN_CHECK; -- 1.7.4.4 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list