qrexec: add timeout for data vchan connection

When qrexec-agent crashes for any reason (for example
QubesOS/qubes-issues#1389), it will never connect back and qrexec-client
will wait forever. In worst case it may happen while holding qubes.xml
write lock (in case of DispVM startup) effectively locking the whole
system.

Fixes QubesOS/qubes-issues#1636
This commit is contained in:
Marek Marczykowski-Górecki 2016-02-22 17:52:38 +01:00
parent 466acad6fb
commit f8d8368b10
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724

View File

@ -27,6 +27,8 @@
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/select.h>
#include <errno.h>
#include <assert.h>
#include "qrexec.h"
@ -535,13 +537,14 @@ static void select_loop(libvchan_t *vchan)
static void usage(char *name)
{
fprintf(stderr,
"usage: %s [-t] [-T] -d domain_name ["
"usage: %s [-w timeout] [-t] [-T] -d domain_name ["
"-l local_prog|"
"-c request_id,src_domain_name,src_domain_id|"
"-e] remote_cmdline\n"
"-e means exit after sending cmd,\n"
"-t enables replacing ESC character with '_' in command output, -T is the same for stderr\n"
"-c: connect to existing process (response to trigger service call)\n",
"-c: connect to existing process (response to trigger service call)\n"
"-w timeout: override default connection timeout of 5s (set 0 for no timeout)\n",
name);
exit(1);
}
@ -581,6 +584,53 @@ static void parse_connect(char *str, char **request_id,
}
}
static void sigalrm_handler(int x __attribute__((__unused__)))
{
fprintf(stderr, "vchan connection timeout\n");
do_exit(1);
}
static void wait_for_vchan_client_with_timeout(libvchan_t *conn, int timeout) {
struct timeval start_tv, now_tv, timeout_tv;
if (timeout && gettimeofday(&start_tv, NULL) == -1) {
perror("gettimeofday");
do_exit(1);
}
while (conn && libvchan_is_open(conn) == VCHAN_WAITING) {
if (timeout) {
fd_set rdset;
int fd = libvchan_fd_for_select(conn);
/* calculate how much time left until connection timeout expire */
if (gettimeofday(&now_tv, NULL) == -1) {
perror("gettimeofday");
do_exit(1);
}
timersub(&start_tv, &now_tv, &timeout_tv);
timeout_tv.tv_sec += timeout;
if (timeout_tv.tv_sec < 0) {
fprintf(stderr, "vchan connection timeout\n");
libvchan_close(conn);
do_exit(1);
}
FD_ZERO(&rdset);
FD_SET(fd, &rdset);
switch (select(fd+1, &rdset, NULL, NULL, &timeout_tv)) {
case -1:
if (errno == EINTR)
break;
/* fallthough */
case 0:
fprintf(stderr, "vchan connection timeout (or error)\n");
libvchan_close(conn);
do_exit(1);
}
}
libvchan_wait(conn);
}
}
int main(int argc, char **argv)
{
int opt;
@ -597,8 +647,9 @@ int main(int argc, char **argv)
char *request_id;
char *src_domain_name = NULL;
int src_domain_id = 0; /* if not -c given, the process is run in dom0 */
int connection_timeout = 5;
struct service_params svc_params;
while ((opt = getopt(argc, argv, "d:l:ec:tT")) != -1) {
while ((opt = getopt(argc, argv, "d:l:ec:tTw:")) != -1) {
switch (opt) {
case 'd':
domname = strdup(optarg);
@ -620,6 +671,9 @@ int main(int argc, char **argv)
case 'T':
replace_esc_stderr = 1;
break;
case 'w':
connection_timeout = atoi(optarg);
break;
default:
usage(argv[0]);
}
@ -660,13 +714,20 @@ int main(int argc, char **argv)
&data_port);
prepare_local_fds(remote_cmdline);
if (connect_existing)
if (connect_existing) {
void (*old_handler)(int);
/* libvchan_client_init is blocking and does not support connection
* timeout, so use alarm(2) for that... */
old_handler = signal(SIGALRM, sigalrm_handler);
alarm(connection_timeout);
data_vchan = libvchan_client_init(data_domain, data_port);
else {
alarm(0);
signal(SIGALRM, old_handler);
} else {
data_vchan = libvchan_server_init(data_domain, data_port,
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
while (data_vchan && libvchan_is_open(data_vchan) == VCHAN_WAITING)
libvchan_wait(data_vchan);
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
}
if (!data_vchan || !libvchan_is_open(data_vchan)) {
fprintf(stderr, "Failed to open data vchan connection\n");
@ -702,8 +763,7 @@ int main(int argc, char **argv)
fprintf(stderr, "Failed to start data vchan server\n");
do_exit(1);
}
while (libvchan_is_open(data_vchan) == VCHAN_WAITING)
libvchan_wait(data_vchan);
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
if (!libvchan_is_open(data_vchan)) {
fprintf(stderr, "Failed to open data vchan connection\n");
do_exit(1);