From 6caecafcd005bae6ae62a2966b2bbd8188512934 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Wed, 14 Sep 2022 13:49:08 -0400 Subject: [PATCH] Prefix data support This allows passing a prefix data argument to qrexec-client-vm. Prefix data is sent before the data on stdin, which is useful for e.g. requesting armored or binary OpenPGP signatures. --- agent/qrexec-agent-data.c | 14 +++++++++++++- agent/qrexec-agent.h | 2 +- agent/qrexec-client-vm.c | 15 +++++++++++---- daemon/qrexec-client.c | 2 ++ libqrexec/libqrexec-utils.h | 8 +++++++- libqrexec/process_io.c | 5 +++-- libqrexec/remote.c | 38 ++++++++++++++++++++++--------------- 7 files changed, 60 insertions(+), 24 deletions(-) diff --git a/agent/qrexec-agent-data.c b/agent/qrexec-agent-data.c index f4a82beb..3a4ee619 100644 --- a/agent/qrexec-agent-data.c +++ b/agent/qrexec-agent-data.c @@ -266,6 +266,9 @@ static int handle_new_process_common( req.sigchld = &sigchld; req.sigusr1 = &sigusr1; + req.prefix_data.data = NULL; + req.prefix_data.len = 0; + exit_code = process_io(&req); if (type == MSG_EXEC_CMDLINE) @@ -303,7 +306,8 @@ pid_t handle_new_process(int type, int connect_domain, int connect_port, /* Returns exit code of remote process */ int handle_data_client( int type, int connect_domain, int connect_port, - int stdin_fd, int stdout_fd, int stderr_fd, int buffer_size, pid_t pid) + int stdin_fd, int stdout_fd, int stderr_fd, int buffer_size, pid_t pid, + const char *extra_data) { int exit_code; int data_protocol_version; @@ -346,6 +350,14 @@ int handle_data_client( req.sigchld = &sigchld; req.sigusr1 = &sigusr1; + if (extra_data) { + req.prefix_data.data = extra_data; + req.prefix_data.len = strlen(extra_data); + } else { + req.prefix_data.data = NULL; + req.prefix_data.len = 0; + } + exit_code = process_io(&req); libvchan_close(data_vchan); return exit_code; diff --git a/agent/qrexec-agent.h b/agent/qrexec-agent.h index e0820204..2ac88c24 100644 --- a/agent/qrexec-agent.h +++ b/agent/qrexec-agent.h @@ -46,7 +46,7 @@ pid_t handle_new_process(int type, int handle_data_client(int type, int connect_domain, int connect_port, int stdin_fd, int stdout_fd, int stderr_fd, - int buffer_size, pid_t pid); + int buffer_size, pid_t pid, const char *extra_data); struct qrexec_cmd_info { diff --git a/agent/qrexec-client-vm.c b/agent/qrexec-client-vm.c index 13f5a750..97ca9884 100644 --- a/agent/qrexec-client-vm.c +++ b/agent/qrexec-client-vm.c @@ -103,6 +103,7 @@ static struct option longopts[] = { { "no-filter-escape-chars-stdout", no_argument, 0, opt_no_filter_stdout}, { "no-filter-escape-chars-stderr", no_argument, 0, opt_no_filter_stderr}, { "agent-socket", required_argument, 0, 'a'}, + { "prefix-data", required_argument, 0, 'p' }, { "help", no_argument, 0, 'h' }, { NULL, 0, 0, 0}, }; @@ -120,6 +121,7 @@ _Noreturn static void usage(const char *argv0, int status) { fprintf(stderr, " --agent-socket=PATH - path to connect to, default: %s\n", QREXEC_AGENT_TRIGGER_PATH); fprintf(stderr, " -h, --help - print this message\n"); + fprintf(stderr, " -p PREFIX-DATA, --prefix-data=PREFIX-DATA - send the given data before the provided stdin (can only be used once)\n"); exit(status); } @@ -139,7 +141,7 @@ int main(int argc, char **argv) int inpipe[2], outpipe[2]; int buffer_size = 0; int opt; - const char *agent_trigger_path = QREXEC_AGENT_TRIGGER_PATH; + const char *agent_trigger_path = QREXEC_AGENT_TRIGGER_PATH, *prefix_data = NULL; setup_logging("qrexec-client-vm"); @@ -147,7 +149,7 @@ int main(int argc, char **argv) signal(SIGPIPE, SIG_IGN); while (1) { - opt = getopt_long(argc, argv, "+tTa:h", longopts, NULL); + opt = getopt_long(argc, argv, "+tTa:hp:", longopts, NULL); if (opt == -1) break; switch (opt) { @@ -179,6 +181,11 @@ int main(int argc, char **argv) break; case 'h': usage(argv[0], 0); + case 'p': + if (prefix_data) + usage(argv[0], 2); + prefix_data = optarg; + break; case opt_no_filter_stdout: replace_chars_stdout = 0; break; @@ -293,11 +300,11 @@ int main(int argc, char **argv) ret = handle_data_client(MSG_SERVICE_CONNECT, exec_params.connect_domain, exec_params.connect_port, - inpipe[1], outpipe[0], -1, buffer_size, child_pid); + inpipe[1], outpipe[0], -1, buffer_size, child_pid, prefix_data); } else { ret = handle_data_client(MSG_SERVICE_CONNECT, exec_params.connect_domain, exec_params.connect_port, - 1, 0, -1, buffer_size, 0); + 1, 0, -1, buffer_size, 0, prefix_data); } close(trigger_fd); diff --git a/daemon/qrexec-client.c b/daemon/qrexec-client.c index e38a9bb3..d996e8cd 100644 --- a/daemon/qrexec-client.c +++ b/daemon/qrexec-client.c @@ -371,6 +371,8 @@ static void select_loop(libvchan_t *vchan, int data_protocol_version, struct buf req.data_protocol_version = data_protocol_version; req.sigchld = &sigchld; req.sigusr1 = NULL; + req.prefix_data.data = NULL; + req.prefix_data.len = 0; exit_code = process_io(&req); libvchan_close(vchan); diff --git a/libqrexec/libqrexec-utils.h b/libqrexec/libqrexec-utils.h index d21225c6..af91c17d 100644 --- a/libqrexec/libqrexec-utils.h +++ b/libqrexec/libqrexec-utils.h @@ -193,6 +193,11 @@ int handle_remote_data( struct buffer *stdin_buf, int data_protocol_version, bool replace_chars_stdout, bool replace_chars_stderr, bool is_service); +struct prefix_data { + const char *data; + size_t len; +}; + /* * Handle data from the specified FD (cannot be -1) and send it over vchan * with a given message type (MSG_DATA_STDIN/STDOUT/STDERR). @@ -205,7 +210,7 @@ int handle_remote_data( */ int handle_input( libvchan_t *vchan, int fd, int msg_type, - int data_protocol_version); + int data_protocol_version, struct prefix_data *data); int send_exit_code(libvchan_t *vchan, int status); @@ -241,6 +246,7 @@ struct process_io_request { volatile sig_atomic_t *sigchld; // can be NULL volatile sig_atomic_t *sigusr1; + struct prefix_data prefix_data; }; /* diff --git a/libqrexec/process_io.c b/libqrexec/process_io.c index 90a2ba0f..d635a727 100644 --- a/libqrexec/process_io.c +++ b/libqrexec/process_io.c @@ -123,6 +123,7 @@ int process_io(const struct process_io_request *req) { sigset_t pollmask; struct timespec zero_timeout = { 0, 0 }; struct timespec normal_timeout = { 10, 0 }; + struct prefix_data empty = { 0, 0 }, prefix = req->prefix_data; sigemptyset(&pollmask); sigaddset(&pollmask, SIGCHLD); @@ -297,7 +298,7 @@ int process_io(const struct process_io_request *req) { if (stdout_fd >= 0 && fds[FD_STDOUT].revents) { switch (handle_input( vchan, stdout_fd, stdout_msg_type, - data_protocol_version)) { + data_protocol_version, &prefix)) { case REMOTE_ERROR: handle_vchan_error("send(handle_input stdout)"); break; @@ -310,7 +311,7 @@ int process_io(const struct process_io_request *req) { if (stderr_fd >= 0 && fds[FD_STDERR].revents) { switch (handle_input( vchan, stderr_fd, MSG_DATA_STDERR, - data_protocol_version)) { + data_protocol_version, &empty)) { case REMOTE_ERROR: handle_vchan_error("send(handle_input stderr)"); break; diff --git a/libqrexec/remote.c b/libqrexec/remote.c index 8f8311af..0b5c7660 100644 --- a/libqrexec/remote.c +++ b/libqrexec/remote.c @@ -149,13 +149,13 @@ int handle_remote_data( int handle_input( libvchan_t *vchan, int fd, int msg_type, - int data_protocol_version) + int data_protocol_version, struct prefix_data *prefix_data) { const size_t max_len = max_data_chunk_size(data_protocol_version); char *buf; ssize_t len; struct msg_header hdr; - int rc = REMOTE_ERROR; + int rc = REMOTE_ERROR, buf_space; buf = malloc(max_len); if (!buf) { @@ -165,21 +165,29 @@ int handle_input( static_assert(SSIZE_MAX >= INT_MAX, "can't happen on Linux"); hdr.type = msg_type; - while (libvchan_buffer_space(vchan) > (int)sizeof(struct msg_header)) { - len = libvchan_buffer_space(vchan)-sizeof(struct msg_header); + while ((buf_space = libvchan_buffer_space(vchan)) > (int)sizeof(struct msg_header)) { + len = buf_space - sizeof(struct msg_header); if ((size_t)len > max_len) len = max_len; - len = read(fd, buf, len); - /* If the other side of the socket is a process that is already dead, - * read from such socket could fail with ECONNRESET instead of - * just 0. */ - if (len < 0 && errno == ECONNRESET) - len = 0; - if (len < 0) { - if (errno == EAGAIN || errno == EWOULDBLOCK) - rc = REMOTE_OK; - /* otherwise keep rc = REMOTE_ERROR */ - goto out; + if (prefix_data->len) { + if ((size_t)len > prefix_data->len) + len = prefix_data->len; + memcpy(buf, prefix_data->data, len); + prefix_data->data += len; + prefix_data->len -= len; + } else { + len = read(fd, buf, len); + /* If the other side of the socket is a process that is already dead, + * read from such socket could fail with ECONNRESET instead of + * just 0. */ + if (len < 0 && errno == ECONNRESET) + len = 0; + if (len < 0) { + if (errno == EAGAIN || errno == EWOULDBLOCK) + rc = REMOTE_OK; + /* otherwise keep rc = REMOTE_ERROR */ + goto out; + } } hdr.len = (uint32_t)len; /* do not fail on sending EOF (think: close()), it will be handled just below */