mirror of
https://gitlab.com/libvirt/libvirt.git
synced 2024-09-09 01:04:59 +00:00
It is possible to deadlock libvirt by having a domain with XML longer than PIPE_BUF, and by writing a hook script that closes stdin early. This is because libvirt was keeping a copy of the child's stdin read fd open, which means the write fd in the parent will never see EPIPE (remember, libvirt should always be run with SIGPIPE ignored, so we should never get a SIGPIPE signal). Since there is no error, libvirt blocks waiting for a write to complete, even though the only reader is also libvirt. The solution is to ensure that only the child can act as a reader before the parent does any writes; and then dealing with the fallout of dealing with EPIPE. Thankfully, this is not a security hole - since the only way to trigger the deadlock is to install a custom hook script, anyone that already has privileges to install a hook script already has privileges to do any number of other equally disruptive things to libvirt; it would only be a security hole if an unprivileged user could install a hook script to DoS a privileged user. * src/util/command.c (virCommandRun): Close parent's copy of child read fd earlier. (virCommandProcessIO): Don't let EPIPE be fatal; the child may be done parsing input. * tests/commandhelper.c (main): Set up a SIGPIPE situation. * tests/commandtest.c (test20): Trigger it. * tests/commanddata/test20.log: New file.
162 lines
3.9 KiB
C
162 lines
3.9 KiB
C
/*
|
|
* commandhelper.c: Auxiliary program for commandtest
|
|
*
|
|
* Copyright (C) 2010-2012 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.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include <config.h>
|
|
|
|
#include <stdio.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
|
|
#include "internal.h"
|
|
#include "util.h"
|
|
#include "memory.h"
|
|
#include "virfile.h"
|
|
#include "testutils.h"
|
|
|
|
#ifndef WIN32
|
|
|
|
|
|
static int envsort(const void *a, const void *b) {
|
|
const char *const*astrptr = a;
|
|
const char *const*bstrptr = b;
|
|
const char *astr = *astrptr;
|
|
const char *bstr = *bstrptr;
|
|
char *aeq = strchr(astr, '=');
|
|
char *beq = strchr(bstr, '=');
|
|
char *akey = strndup(astr, aeq - astr);
|
|
char *bkey = strndup(bstr, beq - bstr);
|
|
int ret = strcmp(akey, bkey);
|
|
VIR_FREE(akey);
|
|
VIR_FREE(bkey);
|
|
return ret;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
int i, n;
|
|
char **origenv;
|
|
char **newenv;
|
|
char *cwd;
|
|
FILE *log = fopen(abs_builddir "/commandhelper.log", "w");
|
|
|
|
if (!log)
|
|
goto error;
|
|
|
|
for (i = 1 ; i < argc ; i++) {
|
|
fprintf(log, "ARG:%s\n", argv[i]);
|
|
}
|
|
|
|
origenv = environ;
|
|
n = 0;
|
|
while (*origenv != NULL) {
|
|
n++;
|
|
origenv++;
|
|
}
|
|
|
|
if (VIR_ALLOC_N(newenv, n) < 0) {
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
origenv = environ;
|
|
n = i = 0;
|
|
while (*origenv != NULL) {
|
|
newenv[i++] = *origenv;
|
|
n++;
|
|
origenv++;
|
|
}
|
|
qsort(newenv, n, sizeof(newenv[0]), envsort);
|
|
|
|
for (i = 0 ; i < n ; i++) {
|
|
/* Ignore the variables used to instruct the loader into
|
|
* behaving differently, as they could throw the tests off. */
|
|
if (!STRPREFIX(newenv[i], "LD_"))
|
|
fprintf(log, "ENV:%s\n", newenv[i]);
|
|
}
|
|
|
|
for (i = 0 ; i < sysconf(_SC_OPEN_MAX) ; i++) {
|
|
int f;
|
|
int closed;
|
|
if (i == fileno(log))
|
|
continue;
|
|
closed = fcntl(i, F_GETFD, &f) == -1 &&
|
|
errno == EBADF;
|
|
if (!closed)
|
|
fprintf(log, "FD:%d\n", i);
|
|
}
|
|
|
|
fprintf(log, "DAEMON:%s\n", getpgrp() == getsid(0) ? "yes" : "no");
|
|
if (!(cwd = getcwd(NULL, 0)))
|
|
return EXIT_FAILURE;
|
|
if (strlen(cwd) > strlen(".../commanddata") &&
|
|
STREQ(cwd + strlen(cwd) - strlen("/commanddata"), "/commanddata"))
|
|
strcpy(cwd, ".../commanddata");
|
|
fprintf(log, "CWD:%s\n", cwd);
|
|
VIR_FREE(cwd);
|
|
|
|
VIR_FORCE_FCLOSE(log);
|
|
|
|
if (argc > 1 && STREQ(argv[1], "--close-stdin")) {
|
|
if (freopen("/dev/null", "r", stdin) != stdin)
|
|
goto error;
|
|
usleep(100*1000);
|
|
}
|
|
|
|
char buf[1024];
|
|
ssize_t got;
|
|
|
|
fprintf(stdout, "BEGIN STDOUT\n");
|
|
fflush(stdout);
|
|
fprintf(stderr, "BEGIN STDERR\n");
|
|
fflush(stderr);
|
|
|
|
for (;;) {
|
|
got = read(STDIN_FILENO, buf, sizeof(buf));
|
|
if (got < 0)
|
|
goto error;
|
|
if (got == 0)
|
|
break;
|
|
if (safewrite(STDOUT_FILENO, buf, got) != got)
|
|
goto error;
|
|
if (safewrite(STDERR_FILENO, buf, got) != got)
|
|
goto error;
|
|
}
|
|
|
|
fprintf(stdout, "END STDOUT\n");
|
|
fflush(stdout);
|
|
fprintf(stderr, "END STDERR\n");
|
|
fflush(stderr);
|
|
|
|
return EXIT_SUCCESS;
|
|
|
|
error:
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
#else
|
|
|
|
int
|
|
main(void)
|
|
{
|
|
return EXIT_AM_SKIP;
|
|
}
|
|
|
|
#endif
|