If qemudDomainRestore fails to open the domain save file, create a pipe, then fork a process that does setuid(qemu_user) and opens the file, then reads this file and stuffs it into the pipe. the parnet libvirtd process will use the other end of the pipe as its fd, then reap the child process after its done reading.
This makes domain restore work on a root-squash NFS share that is only visible to the qemu user. --- src/qemu/qemu_driver.c | 158 +++++++++++++++++++++++++++++++++++++++++++++++- 1 files changed, 155 insertions(+), 3 deletions(-) diff --git a/src/qemu/qemu_driver.c b/src/qemu/qemu_driver.c index ac6ef6a..f909a59 100644 --- a/src/qemu/qemu_driver.c +++ b/src/qemu/qemu_driver.c @@ -4659,6 +4659,135 @@ cleanup: return ret; } +/* qemudOpenAsUID() - pipe/fork/setuid/open a file, and return the + pipe fd to caller, so that it can read from the file. Also return + the pid of the child process, so the caller can wait for it to exit + after it's finished reading (to avoid a zombie, if nothing + else). */ + +static int qemudOpenAsUID(const char *path, uid_t uid, pid_t *childpid) { + int pipefd[2] = {-1, -1}; + int fd = -1; + + *childpid = -1; + + if (pipe(pipefd) < 0) { + virReportSystemError(errno, + _("failed to create pipe to read '%s'"), + path); + goto parentcleanup; + } + + int forkRet = virFork(childpid); + + if (*childpid < 0) { + virReportSystemError(errno, + _("failed to fork child to read '%s'"), + path); + goto parentcleanup; + } + + if (*childpid > 0) { + + /* parent */ + + /* parent doesn't need the write side of the pipe */ + close(pipefd[1]); + pipefd[1] = -1; + + if (forkRet < 0) { + virReportSystemError(errno, + _("failed in parent after forking child to read '%s'"), + path); + goto parentcleanup; + } + /* caller gets the read side of the pipe */ + fd = pipefd[0]; + pipefd[0] = -1; +parentcleanup: + if (pipefd[0] != -1) + close(pipefd[0]); + if (pipefd[1] != -1) + close(pipefd[1]); + if ((fd < 0) && (*childpid > 0)) { + /* a child process was started and subsequently an error + occurred in the parent, so we need to wait for it to + exit, but its status is inconsequential. */ + while ((waitpid(*childpid, NULL, 0) == -1) + && (errno == EINTR)); + *childpid = -1; + } + return fd; + } + + /* child */ + + /* setuid to the qemu user, then open the file, read it, + and stuff it into the pipe for the parent process to + read */ + int exit_code; + char *buf = NULL; + int bufsize = 1024 * 1024; + int bytesread; + + /* child doesn't need the read side of the pipe */ + close(pipefd[0]); + + if (forkRet < 0) { + exit_code = errno; + virReportSystemError(errno, + _("failed in child after forking to read '%s'"), + path); + goto childcleanup; + } + + if (setuid(uid) != 0) { + exit_code = errno; + virReportSystemError(errno, + _("cannot setuid(%d) to read '%s'"), + getuid(), path); + goto childcleanup; + } + if ((fd = open(path, O_RDONLY)) < 0) { + exit_code = errno; + virReportSystemError(errno, + _("cannot open '%s' as uid %d"), + path, getuid()); + goto childcleanup; + } + if (VIR_ALLOC_N(buf, bufsize) < 0) { + exit_code = ENOMEM; + virReportOOMError(); + goto childcleanup; + } + + /* read from fd and write to pipefd[1] until EOF */ + do { + if ((bytesread = saferead(fd, buf, bufsize)) < 0) { + exit_code = errno; + virReportSystemError(errno, + _("child failed reading from '%s'"), + path); + goto childcleanup; + } + if (safewrite(pipefd[1], buf, bytesread) != bytesread) { + exit_code = errno; + virReportSystemError(errno, "%s", + _("child failed writing to pipe")); + goto childcleanup; + } + } while (bytesread > 0); + exit_code = 0; + +childcleanup: + VIR_FREE(buf); + if (fd != -1) + close(fd); + if (pipefd[1] != -1) + close(pipefd[1]); + _exit(exit_code); +} + /* TODO: check seclabel restore */ static int qemudDomainRestore(virConnectPtr conn, const char *path) { @@ -4666,6 +4795,7 @@ static int qemudDomainRestore(virConnectPtr conn, virDomainDefPtr def = NULL; virDomainObjPtr vm = NULL; int fd = -1; + pid_t read_pid = -1; int ret = -1; char *xml = NULL; struct qemud_save_header header; @@ -4677,9 +4807,20 @@ static int qemudDomainRestore(virConnectPtr conn, qemuDriverLock(driver); /* Verify the header and read the XML */ if ((fd = open(path, O_RDONLY)) < 0) { - qemuReportError(VIR_ERR_OPERATION_FAILED, - "%s", _("cannot read domain image")); - goto cleanup; + if ((driver->user == 0) || (getuid() != 0)) { + qemuReportError(VIR_ERR_OPERATION_FAILED, + "%s", _("cannot read domain image")); + goto cleanup; + } + + /* Opening as root failed, but qemu runs as a different user + that might have better luck. Create a pipe, then fork a + child process to run as the qemu user, which will hopefully + have the necessary authority to read the file. */ + if ((fd = qemudOpenAsUID(path, driver->user, &read_pid)) < 0) { + /* error already reported */ + goto cleanup; + } } if (saferead(fd, &header, sizeof(header)) != sizeof(header)) { @@ -4769,6 +4910,12 @@ static int qemudDomainRestore(virConnectPtr conn, close(intermediatefd); close(fd); fd = -1; + if (read_pid != -1) { + /* reap the process that read the file */ + while ((waitpid(read_pid, NULL, 0) == -1) + && (errno == EINTR)); + read_pid = -1; + } if (ret < 0) { if (!vm->persistent) { if (qemuDomainObjEndJob(vm) > 0) @@ -4810,6 +4957,11 @@ cleanup: VIR_FREE(xml); if (fd != -1) close(fd); + if (read_pid != 0) { + /* reap the process that read the file */ + while ((waitpid(read_pid, NULL, 0) == -1) + && (errno == EINTR)); + } if (vm) virDomainObjUnlock(vm); if (event) -- 1.6.6.1 -- libvir-list mailing list libvir-list@redhat.com https://www.redhat.com/mailman/listinfo/libvir-list