Skip to content

Commit c664954

Browse files
committed
Avoid using alarm(2) for timeouts
libvchan has much better ways of dealing with timeouts now, so use them instead of calling an async signal unsafe function from a signal handler. Both qrexec-agent-data.c and qrexec-daemon-common.c reimplemented timeouts, so write common code in libqrexec for both to use.
1 parent 1f13095 commit c664954

10 files changed

+120
-135
lines changed

agent/qrexec-agent-data.c

+3-65
Original file line numberDiff line numberDiff line change
@@ -150,69 +150,6 @@ static int handle_just_exec(struct qrexec_parsed_command *cmd)
150150
return 0;
151151
}
152152

153-
static const long BILLION_NANOSECONDS = 1000000000L;
154-
155-
static int wait_for_vchan_connection_with_timeout(
156-
libvchan_t *conn, int wait_fd, bool is_server, time_t timeout) {
157-
struct timespec end_tp, now_tp, timeout_tp;
158-
159-
if (timeout && clock_gettime(CLOCK_MONOTONIC, &end_tp)) {
160-
PERROR("clock_gettime");
161-
return -1;
162-
}
163-
assert(end_tp.tv_nsec >= 0 && end_tp.tv_nsec < BILLION_NANOSECONDS);
164-
end_tp.tv_sec += timeout;
165-
while (true) {
166-
bool did_timeout = true;
167-
struct pollfd fds = { .fd = wait_fd, .events = POLLIN | POLLHUP, .revents = 0 };
168-
169-
/* calculate how much time left until connection timeout expire */
170-
if (clock_gettime(CLOCK_MONOTONIC, &now_tp)) {
171-
PERROR("clock_gettime");
172-
return -1;
173-
}
174-
assert(now_tp.tv_nsec >= 0 && now_tp.tv_nsec < BILLION_NANOSECONDS);
175-
if (now_tp.tv_sec <= end_tp.tv_sec) {
176-
timeout_tp.tv_sec = end_tp.tv_sec - now_tp.tv_sec;
177-
timeout_tp.tv_nsec = end_tp.tv_nsec - now_tp.tv_nsec;
178-
if (timeout_tp.tv_nsec < 0) {
179-
timeout_tp.tv_nsec += BILLION_NANOSECONDS;
180-
timeout_tp.tv_sec--;
181-
}
182-
did_timeout = timeout_tp.tv_sec < 0;
183-
}
184-
switch (did_timeout ? 0 : ppoll(&fds, 1, &timeout_tp, NULL)) {
185-
case -1:
186-
if (errno == EINTR)
187-
break;
188-
LOG(ERROR, "vchan connection error");
189-
return -1;
190-
case 0:
191-
LOG(ERROR, "vchan connection timeout");
192-
return -1;
193-
case 1:
194-
break;
195-
default:
196-
abort();
197-
}
198-
if (fds.revents & POLLIN) {
199-
if (is_server) {
200-
libvchan_wait(conn);
201-
return 0;
202-
} else {
203-
int connect_ret = libvchan_client_init_async_finish(conn, true);
204-
205-
if (connect_ret < 0) {
206-
LOG(ERROR, "vchan connection error");
207-
return -1;
208-
} else if (connect_ret == 0) {
209-
return 0;
210-
}
211-
}
212-
}
213-
}
214-
}
215-
216153

217154
/* Behaviour depends on type parameter:
218155
* MSG_JUST_EXEC - connect to vchan server, fork+exec process given by cmdline
@@ -253,7 +190,8 @@ static int handle_new_process_common(
253190
LOG(ERROR, "Data vchan connection failed");
254191
exit(1);
255192
}
256-
if (wait_for_vchan_connection_with_timeout(data_vchan, wait_fd, false, connection_timeout) < 0) {
193+
if (qubes_wait_for_vchan_connection_with_timeout(
194+
data_vchan, wait_fd, false, connection_timeout) < 0) {
257195
LOG(ERROR, "Data vchan connection failed");
258196
exit(1);
259197
}
@@ -381,7 +319,7 @@ int handle_data_client(
381319
LOG(ERROR, "Data vchan connection failed");
382320
exit(1);
383321
}
384-
if (wait_for_vchan_connection_with_timeout(
322+
if (qubes_wait_for_vchan_connection_with_timeout(
385323
data_vchan, libvchan_fd_for_select(data_vchan), true, connection_timeout) < 0) {
386324
LOG(ERROR, "Data vchan connection failed");
387325
exit(1);

daemon/qrexec-client.c

+9-53
Original file line numberDiff line numberDiff line change
@@ -143,57 +143,6 @@ static void parse_connect(char *str, char **request_id,
143143
exit(1);
144144
}
145145

146-
static const long BILLION_NANOSECONDS = 1000000000L;
147-
148-
static void wait_for_vchan_client_with_timeout(libvchan_t *conn, time_t timeout) {
149-
struct timespec end_tp, now_tp, timeout_tp;
150-
151-
if (timeout && clock_gettime(CLOCK_MONOTONIC, &end_tp)) {
152-
PERROR("clock_gettime");
153-
exit(1);
154-
}
155-
assert(end_tp.tv_nsec >= 0 && end_tp.tv_nsec < BILLION_NANOSECONDS);
156-
end_tp.tv_sec += timeout;
157-
int const fd = libvchan_fd_for_select(conn);
158-
while (conn && libvchan_is_open(conn) == VCHAN_WAITING) {
159-
if (timeout) {
160-
bool did_timeout = true;
161-
struct pollfd fds = { .fd = fd, .events = POLLIN | POLLHUP, .revents = 0 };
162-
163-
/* calculate how much time left until connection timeout expire */
164-
if (clock_gettime(CLOCK_MONOTONIC, &now_tp)) {
165-
PERROR("clock_gettime");
166-
exit(1);
167-
}
168-
assert(now_tp.tv_nsec >= 0 && now_tp.tv_nsec < BILLION_NANOSECONDS);
169-
if (now_tp.tv_sec <= end_tp.tv_sec) {
170-
timeout_tp.tv_sec = end_tp.tv_sec - now_tp.tv_sec;
171-
timeout_tp.tv_nsec = end_tp.tv_nsec - now_tp.tv_nsec;
172-
if (timeout_tp.tv_nsec < 0) {
173-
timeout_tp.tv_nsec += BILLION_NANOSECONDS;
174-
timeout_tp.tv_sec--;
175-
}
176-
did_timeout = timeout_tp.tv_sec < 0;
177-
}
178-
switch (did_timeout ? 0 : ppoll(&fds, 1, &timeout_tp, NULL)) {
179-
case -1:
180-
if (errno == EINTR)
181-
break;
182-
LOG(ERROR, "vchan connection error");
183-
exit(1);
184-
case 0:
185-
LOG(ERROR, "vchan connection timeout");
186-
exit(1);
187-
case 1:
188-
break;
189-
default:
190-
abort();
191-
}
192-
}
193-
libvchan_wait(conn);
194-
}
195-
}
196-
197146
static size_t compute_service_length(const char *const remote_cmdline, const char *const prog_name) {
198147
const size_t service_length = strlen(remote_cmdline) + 1;
199148
if (service_length < 2 || service_length > MAX_QREXEC_CMD_LEN) {
@@ -365,9 +314,16 @@ int main(int argc, char **argv)
365314
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
366315
if (!data_vchan) {
367316
LOG(ERROR, "Failed to start data vchan server");
368-
exit(1);
317+
rc = QREXEC_EXIT_PROBLEM;
318+
goto cleanup;
319+
}
320+
int fd = libvchan_fd_for_select(data_vchan);
321+
if (qubes_wait_for_vchan_connection_with_timeout(
322+
data_vchan, fd, true, connection_timeout) < 0) {
323+
LOG(ERROR, "qrexec connection timeout");
324+
rc = QREXEC_EXIT_PROBLEM;
325+
goto cleanup;
369326
}
370-
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
371327
struct handshake_params params = {
372328
.data_vchan = data_vchan,
373329
.stdin_buffer = &stdin_buffer,

daemon/qrexec-daemon-common.c

+12-15
Original file line numberDiff line numberDiff line change
@@ -330,12 +330,6 @@ static int select_loop(struct handshake_params *params)
330330
return (params->exit_with_code ? exit_code : 0);
331331
}
332332

333-
static void sigalrm_handler(int x __attribute__((__unused__)))
334-
{
335-
LOG(ERROR, "vchan connection timeout");
336-
_exit(1);
337-
}
338-
339333
int run_qrexec_to_dom0(const struct service_params *svc_params,
340334
int src_domain_id,
341335
const char *src_domain_name,
@@ -374,15 +368,18 @@ int run_qrexec_to_dom0(const struct service_params *svc_params,
374368
} else {
375369
prepare_ret = prepare_local_fds(command, &stdin_buffer);
376370
}
377-
void (*old_handler)(int);
378-
379-
/* libvchan_client_init is blocking and does not support connection
380-
* timeout, so use alarm(2) for that... */
381-
old_handler = signal(SIGALRM, sigalrm_handler);
382-
alarm(connection_timeout);
383-
data_vchan = libvchan_client_init(data_domain, data_port);
384-
alarm(0);
385-
signal(SIGALRM, old_handler);
371+
int wait_fd;
372+
data_vchan = libvchan_client_init_async(data_domain, data_port, &wait_fd);
373+
if (!data_vchan) {
374+
LOG(ERROR, "Cannot create data vchan connection");
375+
return QREXEC_EXIT_PROBLEM;
376+
}
377+
if (qubes_wait_for_vchan_connection_with_timeout(
378+
data_vchan, wait_fd, false, connection_timeout) < 0) {
379+
LOG(ERROR, "qrexec connection timeout");
380+
return QREXEC_EXIT_PROBLEM;
381+
}
382+
386383
struct handshake_params params = {
387384
.data_vchan = data_vchan,
388385
.stdin_buffer = &stdin_buffer,

fuzz/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ CXXFLAGS += -O1 -fno-omit-frame-pointer -gline-tables-only \
1616
-fsanitize-address-use-after-scope -fsanitize=fuzzer
1717
endif
1818

19-
_LIBQREXEC_OBJS = remote.o write-stdin.o ioall.o txrx-vchan.o buffer.o replace.o exec.o log.o unix-server.o toml.o process_io.o
19+
_LIBQREXEC_OBJS = remote.o write-stdin.o ioall.o txrx-vchan.o buffer.o replace.o exec.o log.o unix-server.o toml.o process_io.o vchan_timeout.o
2020
LIBQREXEC_OBJS = $(patsubst %.o,libqrexec-%.o,$(_LIBQREXEC_OBJS))
2121

2222
FUZZERS = qubesrpc_parse_fuzzer qrexec_remote_fuzzer qrexec_daemon_fuzzer

fuzz/fuzz.c

+11
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,18 @@ ssize_t fuzz_write(int fd, const void *buf, size_t count) {
140140
return count;
141141
}
142142

143+
typedef int EVTCHN;
143144
fuzz_file_t *fuzz_libvchan_client_init(int domain, int port) {
144145
/* not implemented yet */
145146
abort();
146147
}
148+
149+
fuzz_file_t *fuzz_libvchan_client_init_async(int domain, int port, EVTCHN *watch_fd) {
150+
/* not implemented yet */
151+
abort();
152+
}
153+
154+
int fuzz_libvchan_client_init_async_finish(fuzz_file_t *ctrl, bool blocking) {
155+
/* not implemented yet */
156+
abort();
157+
}

fuzz/fuzz.h

+2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ int fuzz_libvchan_is_open(fuzz_file_t *file);
3131
int fuzz_libvchan_data_ready(fuzz_file_t *file);
3232
int fuzz_libvchan_buffer_space(fuzz_file_t *file);
3333
fuzz_file_t *fuzz_libvchan_client_init(int domain, int port);
34+
fuzz_file_t *fuzz_libvchan_client_init_async(int domain, int port, int *fd);
35+
int fuzz_libvchan_client_init_async_finish(fuzz_file_t *file, bool blocking);
3436

3537
ssize_t fuzz_read(int fd, void *buf, size_t count);
3638
ssize_t fuzz_write(int fd, const void *buf, size_t count);

fuzz/mock-fuzz.h

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
#define libvchan_data_ready fuzz_libvchan_data_ready
1717
#define libvchan_buffer_space fuzz_libvchan_buffer_space
1818
#define libvchan_client_init fuzz_libvchan_client_init
19+
#define libvchan_client_init_async fuzz_libvchan_client_init_async
20+
#define libvchan_client_init_async_finish fuzz_libvchan_client_init_async_finish
1921

2022
#define read fuzz_read
2123
#define write fuzz_write

libqrexec/Makefile

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ endif
2121

2222

2323
all: libqrexec-utils.so
24-
libqrexec-utils.so.$(SO_VER): unix-server.o ioall.o buffer.o exec.o txrx-vchan.o write-stdin.o replace.o remote.o process_io.o log.o toml.o
24+
libqrexec-utils.so.$(SO_VER): unix-server.o ioall.o buffer.o exec.o txrx-vchan.o write-stdin.o replace.o remote.o process_io.o log.o toml.o vchan_timeout.o
2525
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^ $(VCHANLIBS)
2626

2727
libqrexec-utils.so: libqrexec-utils.so.$(SO_VER)

libqrexec/libqrexec-utils.h

+12
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
#include <poll.h>
3333
#include <sys/socket.h>
3434

35+
#include <libvchan.h>
3536
#include <qrexec.h>
3637

3738
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
@@ -347,4 +348,15 @@ void *qubes_read_all_to_malloc(int fd, size_t initial_buffer_size, size_t max_by
347348
*/
348349
bool qubes_sendmsg_all(struct msghdr *msg, int sock);
349350

351+
/**
352+
* Wait for a vchan connection with a timeout.
353+
*
354+
* @param conn the vchan
355+
* @param wait_fd The FD set by libvchan_client_init_async() for clients,
356+
* or the FD returned by libvchan_fd_for_select() for servers.
357+
* @param is_server Is this a server or a client vchan?
358+
* @param timeout The timeout to use.
359+
*/
360+
int qubes_wait_for_vchan_connection_with_timeout(
361+
libvchan_t *conn, int wait_fd, bool is_server, time_t timeout);
350362
#endif /* LIBQREXEC_UTILS_H */

libqrexec/vchan_timeout.c

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#include <libqrexec-utils.h>
2+
#include <time.h>
3+
#include <assert.h>
4+
#include <stdlib.h>
5+
6+
static const long BILLION_NANOSECONDS = 1000000000L;
7+
8+
int qubes_wait_for_vchan_connection_with_timeout(
9+
libvchan_t *conn, int wait_fd, bool is_server, time_t timeout) {
10+
struct timespec end_tp, now_tp, timeout_tp;
11+
12+
if (clock_gettime(CLOCK_MONOTONIC, &end_tp)) {
13+
PERROR("clock_gettime");
14+
return -1;
15+
}
16+
assert(end_tp.tv_nsec >= 0 && end_tp.tv_nsec < BILLION_NANOSECONDS);
17+
end_tp.tv_sec += timeout;
18+
for (;;) {
19+
bool did_timeout = true;
20+
struct pollfd fds = { .fd = wait_fd, .events = POLLIN | POLLHUP, .revents = 0 };
21+
22+
/* calculate how much time left until connection timeout expire */
23+
if (clock_gettime(CLOCK_MONOTONIC, &now_tp)) {
24+
PERROR("clock_gettime");
25+
return -1;
26+
}
27+
assert(now_tp.tv_nsec >= 0 && now_tp.tv_nsec < BILLION_NANOSECONDS);
28+
if (now_tp.tv_sec <= end_tp.tv_sec) {
29+
timeout_tp.tv_sec = end_tp.tv_sec - now_tp.tv_sec;
30+
timeout_tp.tv_nsec = end_tp.tv_nsec - now_tp.tv_nsec;
31+
if (timeout_tp.tv_nsec < 0) {
32+
timeout_tp.tv_nsec += BILLION_NANOSECONDS;
33+
timeout_tp.tv_sec--;
34+
}
35+
did_timeout = timeout_tp.tv_sec < 0;
36+
}
37+
switch (did_timeout ? 0 : ppoll(&fds, 1, &timeout_tp, NULL)) {
38+
case -1:
39+
if (errno == EINTR)
40+
break;
41+
LOG(ERROR, "vchan connection error");
42+
return -1;
43+
case 0:
44+
LOG(ERROR, "vchan connection timeout");
45+
return -1;
46+
case 1:
47+
break;
48+
default:
49+
abort();
50+
}
51+
if (fds.revents & POLLIN) {
52+
if (is_server) {
53+
libvchan_wait(conn);
54+
return 0;
55+
} else {
56+
int connect_ret = libvchan_client_init_async_finish(conn, true);
57+
58+
if (connect_ret < 0) {
59+
LOG(ERROR, "vchan connection error");
60+
return -1;
61+
} else if (connect_ret == 0) {
62+
return 0;
63+
}
64+
}
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)