qrexec: implement buffered write to a child stdin
Implement one of TODOs left in the code. Without this buffering, it may happen that qrexec-agent will hang waiting on write(2) to the child process, while that child will do the same (try to write something to the qrexec-agent), without reading its stdin. This would end up in a deadlock. Fixes QubesOS/qubes-issues#1347
This commit is contained in:
parent
f063b4a90f
commit
97a3793345
@ -168,6 +168,8 @@ int handle_input(libvchan_t *vchan, int fd, int msg_type)
|
||||
return -1;
|
||||
|
||||
if (len == 0) {
|
||||
/* restore flags */
|
||||
set_block(fd);
|
||||
if (shutdown(fd, SHUT_RD) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(fd);
|
||||
@ -184,16 +186,27 @@ int handle_input(libvchan_t *vchan, int fd, int msg_type)
|
||||
* in this case "status" will be set
|
||||
* -1 - vchan error occurred
|
||||
* 0 - EOF received, do not attempt to access this FD again
|
||||
* 1 - some data processed, call it again when buffer space and more data
|
||||
* 1 - maybe some data processed, call it again when buffer space and more data
|
||||
* available
|
||||
*/
|
||||
|
||||
int handle_remote_data(libvchan_t *data_vchan, int stdin_fd, int *status)
|
||||
int handle_remote_data(libvchan_t *data_vchan, int stdin_fd, int *status,
|
||||
struct buffer *stdin_buf)
|
||||
{
|
||||
struct msg_header hdr;
|
||||
char buf[MAX_DATA_CHUNK];
|
||||
|
||||
/* TODO: set stdin_fd to non-blocking mode and handle its buffering */
|
||||
/* do not receive any data if we have something already buffered */
|
||||
switch (flush_client_data(stdin_fd, stdin_buf)) {
|
||||
case WRITE_STDIN_OK:
|
||||
break;
|
||||
case WRITE_STDIN_BUFFERED:
|
||||
return 1;
|
||||
case WRITE_STDIN_ERROR:
|
||||
perror("write");
|
||||
return 0;
|
||||
}
|
||||
|
||||
while (libvchan_data_ready(data_vchan) > 0) {
|
||||
if (libvchan_recv(data_vchan, &hdr, sizeof(hdr)) < 0)
|
||||
return -1;
|
||||
@ -214,6 +227,8 @@ int handle_remote_data(libvchan_t *data_vchan, int stdin_fd, int *status)
|
||||
/* discard the data */
|
||||
continue;
|
||||
if (hdr.len == 0) {
|
||||
/* restore flags */
|
||||
set_block(stdin_fd);
|
||||
if (shutdown(stdin_fd, SHUT_WR) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(stdin_fd);
|
||||
@ -221,18 +236,22 @@ int handle_remote_data(libvchan_t *data_vchan, int stdin_fd, int *status)
|
||||
stdin_fd = -1;
|
||||
return 0;
|
||||
} else {
|
||||
/* FIXME: use buffered write here to prevent deadlock */
|
||||
if (!write_all(stdin_fd, buf, hdr.len)) {
|
||||
if (errno == EPIPE || errno == ECONNRESET) {
|
||||
if (shutdown(stdin_fd, SHUT_WR) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(stdin_fd);
|
||||
switch (write_stdin(stdin_fd, buf, hdr.len, stdin_buf)) {
|
||||
case WRITE_STDIN_OK:
|
||||
break;
|
||||
case WRITE_STDIN_BUFFERED:
|
||||
return 1;
|
||||
case WRITE_STDIN_ERROR:
|
||||
if (errno == EPIPE || errno == ECONNRESET) {
|
||||
if (shutdown(stdin_fd, SHUT_WR) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(stdin_fd);
|
||||
}
|
||||
stdin_fd = -1;
|
||||
} else {
|
||||
perror("write");
|
||||
}
|
||||
stdin_fd = -1;
|
||||
} else {
|
||||
perror("write");
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
break;
|
||||
@ -266,15 +285,18 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
int remote_process_status = -1;
|
||||
int ret, max_fd;
|
||||
struct timespec zero_timeout = { 0, 0 };
|
||||
struct buffer stdin_buf;
|
||||
|
||||
sigemptyset(&selectmask);
|
||||
sigaddset(&selectmask, SIGCHLD);
|
||||
sigprocmask(SIG_BLOCK, &selectmask, NULL);
|
||||
sigemptyset(&selectmask);
|
||||
|
||||
set_nonblock(stdin_fd);
|
||||
set_nonblock(stdout_fd);
|
||||
set_nonblock(stderr_fd);
|
||||
|
||||
buffer_init(&stdin_buf);
|
||||
while (1) {
|
||||
if (child_exited) {
|
||||
pid_t pid;
|
||||
@ -283,6 +305,8 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
if (pid == child_process_pid) {
|
||||
child_process_status = WEXITSTATUS(status);
|
||||
if (stdin_fd >= 0) {
|
||||
/* restore flags */
|
||||
set_block(stdin_fd);
|
||||
if (shutdown(stdin_fd, SHUT_WR) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(stdin_fd);
|
||||
@ -305,7 +329,9 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
}
|
||||
/* also if vchan is disconnected (and we processed all the data), there
|
||||
* is no sense of processing further data */
|
||||
if (!libvchan_data_ready(data_vchan) && !libvchan_is_open(data_vchan)) {
|
||||
if (!libvchan_data_ready(data_vchan) &&
|
||||
!libvchan_is_open(data_vchan) &&
|
||||
!buffer_len(&stdin_buf)) {
|
||||
if (child_process_pid == 0)
|
||||
child_process_status = remote_process_status;
|
||||
break;
|
||||
@ -338,8 +364,15 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
FD_SET(vchan_fd, &rdset);
|
||||
if (vchan_fd > max_fd)
|
||||
max_fd = vchan_fd;
|
||||
/* if we have something buffered for the child process, wake also on
|
||||
* writable stdin */
|
||||
if (stdin_fd > -1 && buffer_len(&stdin_buf)) {
|
||||
FD_SET(stdin_fd, &wrset);
|
||||
if (stdin_fd > max_fd)
|
||||
max_fd = stdin_fd;
|
||||
}
|
||||
|
||||
if (libvchan_data_ready(data_vchan) > 0) {
|
||||
if (!buffer_len(&stdin_buf) && libvchan_data_ready(data_vchan) > 0) {
|
||||
/* check for other FDs, but exit immediately */
|
||||
ret = pselect(max_fd + 1, &rdset, &wrset, NULL, &zero_timeout, &selectmask);
|
||||
} else
|
||||
@ -361,7 +394,7 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
}
|
||||
|
||||
/* handle_remote_data will check if any data is available */
|
||||
switch (handle_remote_data(data_vchan, stdin_fd, &remote_process_status)) {
|
||||
switch (handle_remote_data(data_vchan, stdin_fd, &remote_process_status, &stdin_buf)) {
|
||||
case -1:
|
||||
handle_vchan_error("read");
|
||||
break;
|
||||
@ -406,6 +439,8 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
/* make sure that all the pipes/sockets are closed, so the child process
|
||||
* (if any) will know that the connection is terminated */
|
||||
if (stdout_fd != -1) {
|
||||
/* restore flags */
|
||||
set_block(stdout_fd);
|
||||
if (shutdown(stdout_fd, SHUT_RD) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(stdout_fd);
|
||||
@ -413,6 +448,8 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
stdout_fd = -1;
|
||||
}
|
||||
if (stdin_fd != -1) {
|
||||
/* restore flags */
|
||||
set_block(stdin_fd);
|
||||
if (shutdown(stdin_fd, SHUT_WR) < 0) {
|
||||
if (errno == ENOTSOCK)
|
||||
close(stdin_fd);
|
||||
@ -420,6 +457,8 @@ int process_child_io(libvchan_t *data_vchan,
|
||||
stdin_fd = -1;
|
||||
}
|
||||
if (stderr_fd != -1) {
|
||||
/* restore flags */
|
||||
set_block(stderr_fd);
|
||||
close(stderr_fd);
|
||||
stderr_fd = -1;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user