[libvirt] [PATCHv2 02/16] command: avoid leaking fds across fork
Daniel P. Berrange
berrange at redhat.com
Thu Jul 21 11:22:24 UTC 2011
On Tue, Jul 19, 2011 at 10:20:25PM -0600, Eric Blake wrote:
> Since libvirt is multi-threaded, we should use FD_CLOEXEC as much
> as possible in the parent, and only relax fds to inherited after
> forking, to avoid leaking an fd created in one thread to a fork
> run in another thread. This gets us closer to that ideal, by
> making virCommand automatically clear FD_CLOEXEC on fds intended
> for the child, as well as avoiding a window of time with non-cloexec
> pipes created for capturing output.
NB while we can leak FDs across fork(), we'll never leak FDs into
a child process, since we always close all FDs explicitly before
doing any exec(). So this is a nice cleanup, but doesn't fix any
actual bugs in libvirt code, unless there is some external library
we link to that does unsafe fork/exec.
> diff --git a/src/qemu/qemu_command.c b/src/qemu/qemu_command.c
> index a3bce4a..ee706f9 100644
> --- a/src/qemu/qemu_command.c
> +++ b/src/qemu/qemu_command.c
> @@ -4575,14 +4575,6 @@ qemuBuildCommandLine(virConnectPtr conn,
> } else if (STREQ(migrateFrom, "stdio")) {
> if (qemuCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_FD)) {
> virCommandAddArgFormat(cmd, "fd:%d", migrateFd);
> - /* migrateFd might be cloexec, but qemu must inherit
> - * it if vmop indicates qemu will be executed */
> - if (vmop != VIR_VM_OP_NO_OP &&
> - virSetInherit(migrateFd, true) < 0) {
> - qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> - _("Failed to clear cloexec flag"));
> - goto error;
> - }
> virCommandPreserveFD(cmd, migrateFd);
> } else if (qemuCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_EXEC)) {
> virCommandAddArg(cmd, "exec:cat");
> @@ -4612,14 +4604,6 @@ qemuBuildCommandLine(virConnectPtr conn,
> goto error;
> }
> virCommandAddArg(cmd, migrateFrom);
> - /* migrateFd might be cloexec, but qemu must inherit
> - * it if vmop indicates qemu will be executed */
> - if (vmop != VIR_VM_OP_NO_OP &&
> - virSetInherit(migrateFd, true) < 0) {
> - qemuReportError(VIR_ERR_INTERNAL_ERROR, "%s",
> - _("Failed to clear cloexec flag"));
> - goto error;
> - }
> virCommandPreserveFD(cmd, migrateFd);
> } else if (STRPREFIX(migrateFrom, "unix")) {
> if (!qemuCapsGet(qemuCaps, QEMU_CAPS_MIGRATE_QEMU_UNIX)) {
> diff --git a/src/util/command.c b/src/util/command.c
> index 11dd7b0..f8ee8b1 100644
> --- a/src/util/command.c
> +++ b/src/util/command.c
> @@ -259,7 +259,7 @@ cleanup:
> static int
> getDevNull(int *null)
> {
> - if (*null == -1 && (*null = open("/dev/null", O_RDWR)) < 0) {
> + if (*null == -1 && (*null = open("/dev/null", O_RDWR|O_CLOEXEC)) < 0) {
> virReportSystemError(errno,
> _("cannot open %s"),
> "/dev/null");
> @@ -268,6 +268,18 @@ getDevNull(int *null)
> return 0;
> }
>
> +/* Ensure that STD is an inheritable copy of FD. Return 0 on success,
> + * -1 on failure. */
> +static int
> +prepareStdFd(int fd, int std)
> +{
> + if (fd == std)
> + return virSetInherit(fd, true);
> + if (dup2(fd, std) != std)
> + return -1;
> + return 0;
> +}
> +
> /*
> * @argv argv to exec
> * @envp optional environment to use for exec
> @@ -327,7 +339,7 @@ virExecWithHook(const char *const*argv,
>
> if (outfd != NULL) {
> if (*outfd == -1) {
> - if (pipe(pipeout) < 0) {
> + if (pipe2(pipeout, O_CLOEXEC) < 0) {
> virReportSystemError(errno,
> "%s", _("cannot create pipe"));
> goto cleanup;
> @@ -340,12 +352,6 @@ virExecWithHook(const char *const*argv,
> goto cleanup;
> }
>
> - if (virSetCloseExec(pipeout[0]) == -1) {
> - virReportSystemError(errno,
> - "%s", _("Failed to set close-on-exec file descriptor flag"));
> - goto cleanup;
> - }
> -
> childout = pipeout[1];
> } else {
> childout = *outfd;
> @@ -358,7 +364,7 @@ virExecWithHook(const char *const*argv,
>
> if (errfd != NULL) {
> if (*errfd == -1) {
> - if (pipe(pipeerr) < 0) {
> + if (pipe2(pipeerr, O_CLOEXEC) < 0) {
> virReportSystemError(errno,
> "%s", _("Failed to create pipe"));
> goto cleanup;
> @@ -371,12 +377,6 @@ virExecWithHook(const char *const*argv,
> goto cleanup;
> }
>
> - if (virSetCloseExec(pipeerr[0]) == -1) {
> - virReportSystemError(errno,
> - "%s", _("Failed to set close-on-exec file descriptor flag"));
> - goto cleanup;
> - }
> -
> childerr = pipeerr[1];
> } else {
> childerr = *errfd;
> @@ -426,28 +426,29 @@ virExecWithHook(const char *const*argv,
> }
>
> openmax = sysconf(_SC_OPEN_MAX);
> - for (i = 3; i < openmax; i++)
> - if (i != infd &&
> - i != childout &&
> - i != childerr &&
> - (!keepfd || i >= FD_SETSIZE || !FD_ISSET(i, keepfd))) {
> + for (i = 3; i < openmax; i++) {
> + if (i == infd || i == childout || i == childerr)
> + continue;
> + if (!keepfd || i >= FD_SETSIZE || !FD_ISSET(i, keepfd)) {
> tmpfd = i;
> VIR_FORCE_CLOSE(tmpfd);
> + } else if (virSetInherit(i, true) < 0) {
> + virReportSystemError(errno, _("failed to preserve fd %d"), i);
> + goto fork_error;
> }
> + }
>
> - if (dup2(infd, STDIN_FILENO) < 0) {
> + if (prepareStdFd(infd, STDIN_FILENO) < 0) {
> virReportSystemError(errno,
> "%s", _("failed to setup stdin file handle"));
> goto fork_error;
> }
> - if (childout > 0 &&
> - dup2(childout, STDOUT_FILENO) < 0) {
> + if (childout > 0 && prepareStdFd(childout, STDOUT_FILENO) < 0) {
> virReportSystemError(errno,
> "%s", _("failed to setup stdout file handle"));
> goto fork_error;
> }
> - if (childerr > 0 &&
> - dup2(childerr, STDERR_FILENO) < 0) {
> + if (childerr > 0 && prepareStdFd(childerr, STDERR_FILENO) < 0) {
> virReportSystemError(errno,
> "%s", _("failed to setup stderr file handle"));
> goto fork_error;
> @@ -1805,7 +1806,7 @@ virCommandRun(virCommandPtr cmd, int *exitstatus)
> /* If we have an input buffer, we need
> * a pipe to feed the data to the child */
> if (cmd->inbuf) {
> - if (pipe(infd) < 0) {
> + if (pipe2(infd, O_CLOEXEC) < 0) {
> virReportSystemError(errno, "%s",
> _("unable to open pipe"));
> cmd->has_error = -1;
> @@ -2295,11 +2296,11 @@ void virCommandRequireHandshake(virCommandPtr cmd)
> return;
> }
>
> - if (pipe(cmd->handshakeWait) < 0) {
> + if (pipe2(cmd->handshakeWait, O_CLOEXEC) < 0) {
> cmd->has_error = errno;
> return;
> }
> - if (pipe(cmd->handshakeNotify) < 0) {
> + if (pipe2(cmd->handshakeNotify, O_CLOEXEC) < 0) {
> VIR_FORCE_CLOSE(cmd->handshakeWait[0]);
> VIR_FORCE_CLOSE(cmd->handshakeWait[1]);
> cmd->has_error = errno;
ACK
Daniel
--
|: http://berrange.com -o- http://www.flickr.com/photos/dberrange/ :|
|: http://libvirt.org -o- http://virt-manager.org :|
|: http://autobuild.org -o- http://search.cpan.org/~danberr/ :|
|: http://entangle-photo.org -o- http://live.gnome.org/gtk-vnc :|
More information about the libvir-list
mailing list