diff --git a/agent/qrexec-agent-data.c b/agent/qrexec-agent-data.c index bafeaff2..40a163a0 100644 --- a/agent/qrexec-agent-data.c +++ b/agent/qrexec-agent-data.c @@ -132,19 +132,23 @@ int handle_handshake(libvchan_t *ctrl) static int handle_just_exec(struct qrexec_parsed_command *cmd) { - int fdn, pid; + int fdn, pid, log_fd; if (cmd == NULL) return 127; + char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN]; + struct buffer buf = { .data = file_path, .buflen = (int)sizeof(file_path) }; + struct buffer stdin_buffer; + buffer_init(&stdin_buffer); if (cmd->service_descriptor) { int socket_fd; - struct buffer stdin_buffer; - buffer_init(&stdin_buffer); - if (!find_qrexec_service(cmd, &socket_fd, &stdin_buffer)) + if (!find_qrexec_service(cmd, &socket_fd, &stdin_buffer, &buf)) return 127; if (socket_fd != -1) return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? 0 : 127; + } else { + buf.data = NULL; } switch (pid = fork()) { case -1: @@ -152,8 +156,14 @@ static int handle_just_exec(struct qrexec_parsed_command *cmd) return 127; case 0: fdn = open("/dev/null", O_RDWR); - fix_fds(fdn, fdn, fdn); - do_exec(cmd->command, cmd->username); + if (fdn < 0) + _exit(127); + int other_pid; + log_fd = cmd->service_descriptor ? open_logger(cmd, &other_pid) : fdn; + if (log_fd < 0) + _exit(127); + fix_fds(fdn, fdn, log_fd); + do_exec(buf.data, cmd->command, cmd->username); default:; } LOG(INFO, "executed (nowait): %s (pid %d)", cmd->command, pid); @@ -311,6 +321,7 @@ static int handle_new_process_common( return 0; } + int logger_pid = 0; req.vchan = data_vchan; req.stdin_buf = &stdin_buf; @@ -330,6 +341,8 @@ static int handle_new_process_common( req.prefix_data.data = NULL; req.prefix_data.len = 0; + if (cmd->service_descriptor != NULL) + req.logger_fd = open_logger(cmd, &logger_pid); exit_code = process_io(&req); diff --git a/agent/qrexec-agent.c b/agent/qrexec-agent.c index 4e10f3ef..2833e4a6 100644 --- a/agent/qrexec-agent.c +++ b/agent/qrexec-agent.c @@ -139,7 +139,7 @@ static struct pam_conv conv = { * If dom0 sends overly long cmd, it will probably crash qrexec-agent (unless * process can allocate up to 4GB on both stack and heap), sorry. */ -_Noreturn void do_exec(const char *cmd, const char *user) +_Noreturn void do_exec(const char *prog, const char *cmd, const char *user) { #ifdef HAVE_PAM int retval, status; @@ -178,7 +178,7 @@ _Noreturn void do_exec(const char *cmd, const char *user) exit(1); } /* call QUBESRPC if requested */ - exec_qubes_rpc_if_requested(cmd, environ); + exec_qubes_rpc_if_requested(prog, cmd, environ); /* otherwise exec shell */ execl("/bin/sh", "sh", "-c", cmd, NULL); @@ -285,7 +285,7 @@ _Noreturn void do_exec(const char *cmd, const char *user) warn("chdir(%s)", pw->pw_dir); /* call QUBESRPC if requested */ - exec_qubes_rpc_if_requested(cmd, env); + exec_qubes_rpc_if_requested(prog, cmd, env); /* otherwise exec shell */ execle(pw->pw_shell, arg0, "-c", cmd, (char*)NULL, env); @@ -323,7 +323,7 @@ _Noreturn void do_exec(const char *cmd, const char *user) exit(1); #else /* call QUBESRPC if requested */ - exec_qubes_rpc_if_requested(cmd, environ); + exec_qubes_rpc_if_requested(prog, cmd, environ); /* otherwise exec shell */ execl("/bin/su", "su", "-", user, "-c", cmd, NULL); diff --git a/agent/qrexec-agent.h b/agent/qrexec-agent.h index 10d1ff7e..99a197d7 100644 --- a/agent/qrexec-agent.h +++ b/agent/qrexec-agent.h @@ -29,7 +29,7 @@ int handle_handshake(libvchan_t *ctrl); void handle_vchan_error(const char *op); -_Noreturn void do_exec(const char *cmd, const char *user); +_Noreturn void do_exec(const char *prog, const char *cmd, const char *user); /* call before fork() for service handling process (either end) */ void prepare_child_env(void); diff --git a/agent/qrexec-client-vm.c b/agent/qrexec-client-vm.c index a646cd3f..d24d4182 100644 --- a/agent/qrexec-client-vm.c +++ b/agent/qrexec-client-vm.c @@ -42,7 +42,7 @@ _Noreturn void handle_vchan_error(const char *op) exit(1); } -_Noreturn void do_exec(const char *cmd __attribute__((unused)), char const* user __attribute__((__unused__))) { +_Noreturn void do_exec(const char *prog __attribute__((unused)), const char *cmd __attribute__((unused)), char const* user __attribute__((__unused__))) { LOG(ERROR, "BUG: do_exec function shouldn't be called!"); abort(); } diff --git a/agent/qrexec-fork-server.c b/agent/qrexec-fork-server.c index 8a8ecd6e..a9e6beb4 100644 --- a/agent/qrexec-fork-server.c +++ b/agent/qrexec-fork-server.c @@ -37,7 +37,7 @@ extern char **environ; const bool qrexec_is_fork_server = true; -void do_exec(const char *cmd, const char *user __attribute__((unused))) +void do_exec(const char *prog, const char *cmd, const char *user __attribute__((unused))) { char *shell; @@ -45,7 +45,7 @@ void do_exec(const char *cmd, const char *user __attribute__((unused))) signal(SIGPIPE, SIG_DFL); /* call QUBESRPC if requested */ - exec_qubes_rpc_if_requested(cmd, environ); + exec_qubes_rpc_if_requested(prog, cmd, environ); /* otherwise, pass it to shell */ shell = getenv("SHELL"); diff --git a/daemon/qrexec-client.c b/daemon/qrexec-client.c index fe84005e..3075b1b0 100644 --- a/daemon/qrexec-client.c +++ b/daemon/qrexec-client.c @@ -62,13 +62,15 @@ static void set_remote_domain(const char *src_domain_name) { } /* called from do_fork_exec */ -static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused))) +static _Noreturn void do_exec(const char *prog, + const char *cmdline, + const char *username __attribute__((unused))) { /* avoid calling qubes-rpc-multiplexer through shell */ - exec_qubes_rpc_if_requested(prog, environ); + exec_qubes_rpc_if_requested(prog, cmdline, environ); /* if above haven't executed qubes-rpc-multiplexer, pass it to shell */ - execl("/bin/bash", "bash", "-c", prog, NULL); + execl("/bin/bash", "bash", "-c", cmdline, NULL); PERROR("exec bash"); exit(1); } diff --git a/daemon/qrexec-daemon.c b/daemon/qrexec-daemon.c index 87993071..a4623ac9 100644 --- a/daemon/qrexec-daemon.c +++ b/daemon/qrexec-daemon.c @@ -1064,13 +1064,13 @@ static size_t compute_service_length(const char *const remote_cmdline) { } /* called from do_fork_exec */ -static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused))) +static _Noreturn void do_exec(const char *prog, const char *cmd, const char *username __attribute__((unused))) { /* avoid calling qubes-rpc-multiplexer through shell */ - exec_qubes_rpc_if_requested(prog, environ); + exec_qubes_rpc_if_requested(prog, cmd, environ); /* if above haven't executed qubes-rpc-multiplexer, pass it to shell */ - execl("/bin/bash", "bash", "-c", prog, NULL); + execl("/bin/bash", "bash", "-c", cmd, NULL); PERROR("exec bash"); _exit(126); } diff --git a/debian/qubes-core-qrexec.install b/debian/qubes-core-qrexec.install index ce2f11a0..87cea54b 100644 --- a/debian/qubes-core-qrexec.install +++ b/debian/qubes-core-qrexec.install @@ -20,6 +20,5 @@ usr/bin/qubes-policy-lint usr/lib/qubes/qrexec-agent usr/lib/qubes/qrexec-client-vm usr/lib/qubes/qrexec_client_vm -usr/lib/qubes/qubes-rpc-multiplexer usr/lib/qubes/qrexec-policy-agent-autostart usr/share/man/man1/qrexec-client-vm.1.gz diff --git a/lib/qubes-rpc-multiplexer b/lib/qubes-rpc-multiplexer deleted file mode 100755 index 0c34d740..00000000 --- a/lib/qubes-rpc-multiplexer +++ /dev/null @@ -1,64 +0,0 @@ -#!/bin/sh -l -# we don't use globbing, disable it -set -f - -if [ -z "${QREXEC_SERVICE_PATH+x}" ]; then - QREXEC_SERVICE_PATH=/usr/local/etc/qubes-rpc:/etc/qubes-rpc -fi -tmpdir=${XDG_RUNTIME_DIR-/tmp} - -# write stderr to both calling party and local log; be very careful about -# closing file descriptors here - if either stdout or stderr will not be closed -# when service process does the same - service call will hang (waiting for EOF -# on stdout/stderr) -stderr_pipe=$tmpdir/qrexec-rpc-stderr.$$ -mkfifo -- "$stderr_pipe" -# tee can't write to file descriptor, nor /proc/self/fd/2 (EXIO on open) -return_stderr_pipe=$tmpdir/qrexec-rpc-stderr-return.$$ -mkfifo -- "$return_stderr_pipe" -{ cat <"$return_stderr_pipe" >&2 2>/dev/null; rm -f -- "$return_stderr_pipe"; } /dev/null & -{ tee -- "$return_stderr_pipe" <"$stderr_pipe" | - logger -t "$1-$2"; rm -f -- "$stderr_pipe"; } /dev/null 2>&1 & -exec 2>"$stderr_pipe" - -if ! [ $# = 2 -o $# = 4 ] ; then - echo "$0: bad argument count, usage: $0 SERVICE-NAME REMOTE-DOMAIN-NAME [REQUESTED_TARGET_TYPE REQUESTED_TARGET]" >&2 - exit 1 -fi -# Avoid inheriting these from the environment -unset QREXEC_REQUESTED_TARGET QREXEC_REQUESTED_TARGET_KEYWORD QREXEC_SERVICE_ARGUMENT -export QREXEC_REQUESTED_TARGET_TYPE="$3" -if [ "$QREXEC_REQUESTED_TARGET_TYPE" = "name" ]; then - export QREXEC_REQUESTED_TARGET="$4" -elif [ "$QREXEC_REQUESTED_TARGET_TYPE" = "keyword" ]; then - export QREXEC_REQUESTED_TARGET_KEYWORD="$4" -fi -# else: requested target type unknown or not given, ignore -export QREXEC_REMOTE_DOMAIN="$2" -export QREXEC_SERVICE_FULL_NAME="$1" -SERVICE_WITHOUT_ARGUMENT="${1%%+*}" -if [ "${QREXEC_SERVICE_FULL_NAME}" != "${SERVICE_WITHOUT_ARGUMENT}" ]; then - export QREXEC_SERVICE_ARGUMENT="${QREXEC_SERVICE_FULL_NAME#*+}" -else - # Search for qubes.Service+ if given qubes.Service - set -- "$1+" "$2" -fi - - -ifs=$IFS -IFS=: -for DIR in $QREXEC_SERVICE_PATH; do - CFG_FILE="$DIR/$1" - if [ -s "$CFG_FILE" ]; then - break - fi - CFG_FILE="$DIR/$SERVICE_WITHOUT_ARGUMENT" - if [ -s "$CFG_FILE" ]; then - break - fi -done -IFS=$ifs - -exec "$CFG_FILE" ${QREXEC_SERVICE_ARGUMENT:+"$QREXEC_SERVICE_ARGUMENT"} -echo "$0: failed to execute handler for $1" >&2 -exit 1 diff --git a/libqrexec/Makefile b/libqrexec/Makefile index cfe4ef2d..e6bdd655 100644 --- a/libqrexec/Makefile +++ b/libqrexec/Makefile @@ -21,7 +21,7 @@ endif all: libqrexec-utils.so -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 +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 open_logger.o $(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^ $(VCHANLIBS) libqrexec-utils.so: libqrexec-utils.so.$(SO_VER) diff --git a/libqrexec/buffer.c b/libqrexec/buffer.c index 1c49a07e..217d94dd 100644 --- a/libqrexec/buffer.c +++ b/libqrexec/buffer.c @@ -35,12 +35,12 @@ static char *limited_malloc(int len) (total_mem > BUFFER_LIMIT) || (len <= 0)) { LOG(ERROR, "attempt to allocate >BUFFER_LIMIT"); - exit(1); + abort(); } ret = malloc((size_t)len); if (!ret) { PERROR("malloc"); - exit(1); + abort(); } return ret; } @@ -83,11 +83,11 @@ void buffer_append(struct buffer *b, const char *data, int len) assert(data != NULL && "NULL data"); if (b->buflen < 0 || b->buflen > BUFFER_LIMIT) { LOG(ERROR, "buffer_append buflen %d", len); - exit(1); + abort(); } if (len < 0 || len > BUFFER_LIMIT) { LOG(ERROR, "buffer_append %d", len); - exit(1); + abort(); } if (len == 0) return; @@ -108,7 +108,7 @@ void buffer_remove(struct buffer *b, int len) char *qdata = NULL; if (len < 0 || len > b->buflen) { LOG(ERROR, "buffer_remove %d/%d", len, b->buflen); - exit(1); + abort(); } newsize = b->buflen - len; if (newsize > 0) { diff --git a/libqrexec/exec.c b/libqrexec/exec.c index 10b0ff5a..18f16fa2 100644 --- a/libqrexec/exec.c +++ b/libqrexec/exec.c @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -45,36 +46,115 @@ void register_exec_func(do_exec_t *func) { exec_func = func; } -void exec_qubes_rpc_if_requested(const char *prog, char *const envp[]) { - /* avoid calling qubes-rpc-multiplexer through shell */ - if (strncmp(prog, RPC_REQUEST_COMMAND, RPC_REQUEST_COMMAND_LEN) == 0) { +static bool should_strip_env_var(const char *var) +{ + if (strncmp(var, "QREXEC", sizeof "QREXEC" - 1) != 0) + return false; + return strncmp(var + (sizeof "QREXEC" - 1), "_SERVICE_PATH=", sizeof "_SERVICE_PATH") != 0; +} + +void exec_qubes_rpc_if_requested(const char *program, const char *cmd, char *const envp[]) { + /* avoid calling RPC service through shell */ + if (strncmp(cmd, RPC_REQUEST_COMMAND, RPC_REQUEST_COMMAND_LEN) == 0) { char *prog_copy; char *tok, *savetok; - char *argv[16]; // right now 6 are used, but allow future extensions + const char *argv[6]; size_t i = 0; +#define MAX_ADDED_ENV_VARS 5 + size_t const extra_env_vars = MAX_ADDED_ENV_VARS; + size_t env_amount = extra_env_vars; + + if (cmd[RPC_REQUEST_COMMAND_LEN] != ' ') { + LOG(ERROR, "\"" RPC_REQUEST_COMMAND "\" not followed by space"); + _exit(EX_DATAERR); + } + + assert(program); + + for (char *const *env = envp; *env; ++env) { + // Set this 0 to 1 if adding new variable settings below, + // to ensure that MAX_ADDED_ENV_VARS is correct. + if (0 && should_strip_env_var(*env)) + continue; + env_amount++; + } +#define EXTEND(...) \ + do { \ + if (iterator >= env_amount) \ + abort(); \ + if (asprintf(&buf[iterator++], __VA_ARGS__) < 0) \ + goto bad_asprintf; \ + } while (0) +#define EXTEND_RAW(arg) \ + do { \ + if (iterator >= env_amount) \ + abort(); \ + buf[iterator++] = (arg); \ + } while (0) + + char **buf = calloc(env_amount + 1, sizeof(char *)); + if (buf == NULL) { + LOG(ERROR, "calloc(%zu, %zu) failed: %m", env_amount, sizeof(char *)); + _exit(EX_OSERR); + } + size_t iterator = 0; + for (char *const *env = envp; *env; ++env) { + if (!should_strip_env_var(*env)) { + EXTEND_RAW(*env); + } + } - prog_copy = strdup(prog); + prog_copy = strdup(cmd + RPC_REQUEST_COMMAND_LEN + 1); if (!prog_copy) { PERROR("strdup"); - _exit(1); + _exit(EX_OSERR); } + argv[i++] = (char *)program; tok=strtok_r(prog_copy, " ", &savetok); - do { + while (tok != NULL) { if (i >= sizeof(argv)/sizeof(argv[0])-1) { - LOG(ERROR, "To many arguments to %s", RPC_REQUEST_COMMAND); - exit(1); + LOG(ERROR, "Too many arguments to %s", RPC_REQUEST_COMMAND); + _exit(EX_DATAERR); } argv[i++] = tok; - } while ((tok=strtok_r(NULL, " ", &savetok))); + tok = strtok_r(NULL, " ", &savetok); + } argv[i] = NULL; - argv[0] = getenv("QREXEC_MULTIPLEXER_PATH"); - if (!argv[0]) - argv[0] = QUBES_RPC_MULTIPLEXER_PATH; - execve(argv[0], argv, envp); + if (i == 5) { + EXTEND("QREXEC_REQUESTED_TARGET_TYPE=%s", argv[3]); + if (strcmp(argv[3], "name") == 0) { + EXTEND("QREXEC_REQUESTED_TARGET_TYPE=%s", argv[4]); + } else if (strcmp(argv[3], "keyword") == 0) { + EXTEND("QREXEC_REQUESTED_TARGET_KEYWORD=%s", argv[4]); + } else { + // requested target type unknown or not given, ignore + } + } else if (i == 3) { + EXTEND_RAW("QREXEC_REQUESTED_TARGET_TYPE="); + } else { + LOG(ERROR, "invalid number of arguments: %zu", i); + _exit(EX_DATAERR); + } + EXTEND("QREXEC_SERVICE_FULL_NAME=%s", argv[1]); + EXTEND("QREXEC_REMOTE_DOMAIN=%s", argv[2]); + const char *p = strchr(argv[1], '+'); + argv[1] = NULL; + argv[2] = NULL; + if (p != NULL) { + EXTEND("QREXEC_SERVICE_ARGUMENT=%s", p + 1); + if (p[1]) + argv[1] = p + 1; + } + assert(iterator <= env_amount); + buf[iterator] = NULL; + execve(argv[0], (char *const *)argv, buf); PERROR("exec qubes-rpc-multiplexer"); - _exit(126); + _exit(EX_UNAVAILABLE); +bad_asprintf: + PERROR("asprintf"); + _exit(EX_OSERR); } } @@ -96,7 +176,8 @@ void fix_fds(int fdin, int fdout, int fderr) } } -static int do_fork_exec(const char *user, +static int do_fork_exec(const char *prog, + const char *user, const char *cmdline, int *pid, int *stdin_fd, @@ -133,7 +214,7 @@ static int do_fork_exec(const char *user, fcntl(statuspipe[1], F_SETFD, status | FD_CLOEXEC); } if (exec_func != NULL) - exec_func(cmdline, user); + exec_func(prog, cmdline, user); else abort(); status = errno; @@ -372,7 +453,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command( goto err; } - size_t const descriptor_len = (size_t)(end - start); + size_t const descriptor_len = cmd->service_descriptor_len = (size_t)(end - start); if (descriptor_len > MAX_SERVICE_NAME_LEN) { LOG(ERROR, "Command too long (length %zu)", descriptor_len); goto err; @@ -405,6 +486,10 @@ struct qrexec_parsed_command *parse_qubes_rpc_command( start = end + 1; /* after the space */ end = strchrnul(start, ' '); + if (end <= start) { + LOG(ERROR, "Source qube empty"); + goto err; + } cmd->source_domain = memdupnul(start, (size_t)(end - start)); if (!cmd->source_domain) goto err; @@ -454,7 +539,9 @@ int execute_parsed_qubes_rpc_command( int *stdout_fd, int *stderr_fd, struct buffer *stdin_buffer) { if (cmd->service_descriptor) { // Proper Qubes RPC call - if (!find_qrexec_service(cmd, stdin_fd, stdin_buffer)) + char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN]; + struct buffer buf = { .data = file_path, .buflen = (int)sizeof(file_path) }; + if (!find_qrexec_service(cmd, stdin_fd, stdin_buffer, &buf)) return -1; if (*stdin_fd > -1) { *stdout_fd = *stdin_fd; @@ -463,22 +550,22 @@ int execute_parsed_qubes_rpc_command( *pid = 0; return 0; } - return do_fork_exec(cmd->username, cmd->command, + return do_fork_exec(buf.data, cmd->username, cmd->command, pid, stdin_fd, stdout_fd, stderr_fd); } else { // Legacy qrexec behavior: spawn shell directly - return do_fork_exec(cmd->username, cmd->command, - pid, stdin_fd, stdout_fd, stderr_fd); + return do_fork_exec(NULL, cmd->username, cmd->command, + pid, stdin_fd, stdout_fd, stderr_fd); } } bool find_qrexec_service( const struct qrexec_parsed_command *cmd, - int *socket_fd, struct buffer *stdin_buffer) { + int *socket_fd, struct buffer *stdin_buffer, + struct buffer *path_buffer) { assert(cmd->service_descriptor); + assert(path_buffer->buflen > NAME_MAX); - char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN]; - struct buffer path_buffer = { .data = file_path, .buflen = (int)sizeof(file_path) }; const char *qrexec_service_path = getenv("QREXEC_SERVICE_PATH"); if (!qrexec_service_path) qrexec_service_path = QREXEC_SERVICE_PATH; @@ -487,11 +574,11 @@ bool find_qrexec_service( struct stat statbuf; int ret = find_file(qrexec_service_path, cmd->service_descriptor, - path_buffer.data, (size_t)path_buffer.buflen, + path_buffer->data, (size_t)path_buffer->buflen, &statbuf); if (ret == -1) ret = find_file(qrexec_service_path, cmd->service_name, - path_buffer.data, (size_t)path_buffer.buflen, + path_buffer->data, (size_t)path_buffer->buflen, &statbuf); if (ret < 0) { LOG(ERROR, "Service not found: %s", @@ -502,11 +589,11 @@ bool find_qrexec_service( if (S_ISSOCK(statbuf.st_mode)) { /* Socket-based service. */ int s; - if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + if ((s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) { PERROR("socket"); return false; } - if (qubes_connect(s, path_buffer.data, strlen(path_buffer.data))) { + if (qubes_connect(s, path_buffer->data, strlen(path_buffer->data))) { PERROR("qubes_connect"); close(s); return false; @@ -522,13 +609,13 @@ bool find_qrexec_service( return true; } - if (euidaccess(path_buffer.data, X_OK) == 0) { + if (euidaccess(path_buffer->data, X_OK) == 0) { /* Executable-based service. */ return true; } LOG(ERROR, "Unknown service type (not executable, not a socket): %.*s", - path_buffer.buflen, path_buffer.data); + path_buffer->buflen, path_buffer->data); return false; } diff --git a/libqrexec/libqrexec-utils.h b/libqrexec/libqrexec-utils.h index dcad6462..2ce46d9d 100644 --- a/libqrexec/libqrexec-utils.h +++ b/libqrexec/libqrexec-utils.h @@ -82,6 +82,9 @@ struct qrexec_parsed_command { /* For socket-based services: Should the service descriptor be sent? */ bool send_service_descriptor; + + /* length of the command */ + size_t service_descriptor_len; }; /* Parse a command, return NULL on failure. Uses cmd->cmdline @@ -104,14 +107,14 @@ int load_service_config(struct qrexec_parsed_command *cmd_name, __attribute__((deprecated("use load_service_config_v2() instead"))); int load_service_config_v2(struct qrexec_parsed_command *cmd); -typedef void (do_exec_t)(const char *cmdline, const char *user); +typedef void (do_exec_t)(const char *program, const char *cmd, const char *user); void register_exec_func(do_exec_t *func); /* * exec() qubes-rpc-multiplexer if *prog* starts with magic "QUBESRPC" keyword, * do not return in that case; pass *envp* to execve() as en environment * otherwise, return false without any action */ -void exec_qubes_rpc_if_requested(const char *prog, char *const envp[]); +void exec_qubes_rpc_if_requested(const char *program, const char *cmd, char *const envp[]); int exec_wait_for_session(const char *source_domain); @@ -152,12 +155,16 @@ int execute_parsed_qubes_rpc_command( * or -1 for executable services. * @param stdin_buffer This buffer will need to be prepended to the child process’s * stdin. + * @param path_buffer This buffer (NUL-terminated) holds the service's path. On + * entry it must be at least NAME_MAX bytes. It will not be freed or reallocated. + * Its contents should be ignored if stdout_fd is not -1. * @return true if the implementation is found (and, for sockets, connected to) * successfully, false on failure. */ bool find_qrexec_service( const struct qrexec_parsed_command *cmd, - int *socket_fd, struct buffer *stdin_buffer); + int *socket_fd, struct buffer *stdin_buffer, + struct buffer *path_buffer); /** Suggested buffer size for the path buffer of find_qrexec_service. */ #define QUBES_SOCKADDR_UN_MAX_PATH_LEN 1024 @@ -257,7 +264,8 @@ struct prefix_data { */ int handle_input( libvchan_t *vchan, int fd, int msg_type, - int data_protocol_version, struct prefix_data *data); + int data_protocol_version, struct prefix_data *data, + int out_fd); int send_exit_code(libvchan_t *vchan, int status); @@ -267,7 +275,7 @@ struct process_io_request { struct buffer *stdin_buf; // stderr_fd can be -1 - int stdin_fd, stdout_fd, stderr_fd; + int stdin_fd, stdout_fd, stderr_fd, logger_fd; // 0 if no child process pid_t local_pid; @@ -295,6 +303,8 @@ struct process_io_request { volatile sig_atomic_t *sigusr1; struct prefix_data prefix_data; }; +/** Open an FD to a logger */ +int open_logger(struct qrexec_parsed_command *command, int *pid); /* * Pass IO between vchan and local FDs. See the comments for diff --git a/libqrexec/open_logger.c b/libqrexec/open_logger.c new file mode 100644 index 00000000..5f824ae2 --- /dev/null +++ b/libqrexec/open_logger.c @@ -0,0 +1,36 @@ +#include +#include +#include +#include + +int open_logger(struct qrexec_parsed_command *command, int *pid) +{ + int pipes[2]; + if (pipe2(pipes, O_CLOEXEC)) + return -1; + switch ((*pid = fork())) { + case -1: + return -1; + case 0:; + char *buf[] = { + "logger", + "-t", + NULL, + NULL, + }; + if (dup2(pipes[0], 0) != 0) { + LOG(ERROR, "dup2() failed: %m"); + _exit(126); + } + if (asprintf(buf + 2, "%.*s-%s", (int)command->service_descriptor_len, + command->service_descriptor, command->source_domain) < 0) { + LOG(ERROR, "asprintf() failed"); + _exit(126); + } + execvp("logger", buf); + _exit(126); + default: + close(pipes[0]); + return pipes[1]; + } +} diff --git a/libqrexec/process_io.c b/libqrexec/process_io.c index 35894924..0525bdce 100644 --- a/libqrexec/process_io.c +++ b/libqrexec/process_io.c @@ -102,6 +102,7 @@ int process_io(const struct process_io_request *req) { int stdin_fd = req->stdin_fd; int stdout_fd = req->stdout_fd; int stderr_fd = req->stderr_fd; + int dup_fd = req->logger_fd; struct buffer *stdin_buf = req->stdin_buf; bool is_service = req->is_service; @@ -298,7 +299,7 @@ int process_io(const struct process_io_request *req) { if (prefix.len > 0 || (stdout_fd >= 0 && fds[FD_STDOUT].revents)) { switch (handle_input( vchan, stdout_fd, stdout_msg_type, - data_protocol_version, &prefix)) { + data_protocol_version, &prefix, -1)) { case REMOTE_ERROR: handle_vchan_error("send(handle_input stdout)"); break; @@ -311,7 +312,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, &empty)) { + data_protocol_version, &empty, dup_fd)) { case REMOTE_ERROR: handle_vchan_error("send(handle_input stderr)"); break; diff --git a/libqrexec/remote.c b/libqrexec/remote.c index ed223b9d..2b2c4812 100644 --- a/libqrexec/remote.c +++ b/libqrexec/remote.c @@ -149,7 +149,8 @@ int handle_remote_data( int handle_input( libvchan_t *vchan, int fd, int msg_type, - int data_protocol_version, struct prefix_data *prefix_data) + int data_protocol_version, struct prefix_data *prefix_data, + int dup_fd) { const size_t max_len = max_data_chunk_size(data_protocol_version); char *buf; @@ -177,6 +178,8 @@ int handle_input( prefix_data->len -= len; } else { len = read(fd, buf, len); + if (dup_fd != -1 && len > 0) + write_all(dup_fd, buf, (size_t)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. */ diff --git a/qrexec/client.py b/qrexec/client.py index f2d97d8e..01e432bc 100644 --- a/qrexec/client.py +++ b/qrexec/client.py @@ -23,7 +23,6 @@ QREXEC_CLIENT_DOM0 = "/usr/bin/qrexec-client" QREXEC_CLIENT_VM = "/usr/bin/qrexec-client-vm" -RPC_MULTIPLEXER = "/usr/lib/qubes/qubes-rpc-multiplexer" VERSION = None @@ -102,11 +101,6 @@ def make_command(dest, rpcname, arg): if arg is not None: rpcname = f"{rpcname}+{arg}" - if VERSION == "dom0" and dest == "dom0": - # Invoke qubes-rpc-multiplexer directly. This will work for non-socket - # services only. - return [RPC_MULTIPLEXER, rpcname, "dom0"] - if VERSION == "dom0": return [ QREXEC_CLIENT_DOM0, diff --git a/qrexec/tests/socket/agent.py b/qrexec/tests/socket/agent.py index fac72564..415e17f5 100644 --- a/qrexec/tests/socket/agent.py +++ b/qrexec/tests/socket/agent.py @@ -57,20 +57,33 @@ def check_dom0(self, dom0): ), ) - def assertExpectedStdout(self, target, expected_stdout: bytes, *, exit_code=0): + def assertExpectedStdoutStderr(self, target, expected_stdout: bytes, + expected_stderr: bytes, *, exit_code: int=0) -> None: messages = util.sort_messages(target.recv_all_messages()) - self.assertListEqual(messages[-3:], + self.assertListEqual(messages[-2:], [ - (qrexec.MSG_DATA_STDOUT, b""), (qrexec.MSG_DATA_STDERR, b""), (qrexec.MSG_DATA_EXIT_CODE, struct.pack("&2 """, ) target, dom0 = self.execute_qubesrpc("qubes.Service+arg", "domX") target.send_message(qrexec.MSG_DATA_STDIN, b"") - self.assertExpectedStdout(target, b"arg: arg, remote domain: domX\n") + self.assertExpectedStdoutStderr(target, b"arg: arg, remote domain: domX\n", + b"something on stderr") self.check_dom0(dom0) def test_exec_service_keyword(self): @@ -610,7 +622,7 @@ def test_exec_null_argument_finds_service_for_empty_argument(self): "rpc", "qubes.Service", """\ -#!/bin/sh +#!/bin/sh -- echo "general service" """, ) diff --git a/qrexec/tests/socket/daemon.py b/qrexec/tests/socket/daemon.py index 94b3c7a6..b982889b 100644 --- a/qrexec/tests/socket/daemon.py +++ b/qrexec/tests/socket/daemon.py @@ -558,9 +558,6 @@ def start_client(self, args): os.path.join(self.tempdir, "rpc"), ] ) - env["QREXEC_MULTIPLEXER_PATH"] = os.path.join( - ROOT_PATH, "lib", "qubes-rpc-multiplexer" - ) env["QUBES_RPC_CONFIG_PATH"] = os.path.join(self.tempdir, "rpc-config") cmd = [ os.path.join(ROOT_PATH, "daemon", "qrexec-client"), @@ -830,7 +827,7 @@ def _test_run_dom0_service_exec(self, nogui): """\ #!/bin/sh read input -echo "arg: $1, remote domain: $QREXEC_REMOTE_DOMAIN, input: $input" +echo "arg: $1, remote domain: $QREXEC_REMOTE_DOMAIN, input: $input, service path: $QREXEC_SERVICE_PATH" """, ) @@ -839,13 +836,18 @@ def _test_run_dom0_service_exec(self, nogui): source.send_message(qrexec.MSG_DATA_STDIN, b"stdin data\n") source.send_message(qrexec.MSG_DATA_STDIN, b"") + service_path = (":".join( + [ + os.path.join(self.tempdir, "local-rpc"), + os.path.join(self.tempdir, "rpc"), + ])).encode("ascii", "strict") self.assertEqual( source.recv_all_messages(), [ ( qrexec.MSG_DATA_STDOUT, b"arg: arg, remote domain: src_domain, " - b"input: stdin data\n", + b"input: stdin data, service path: %s\n" % service_path, ), (qrexec.MSG_DATA_STDOUT, b""), (qrexec.MSG_DATA_EXIT_CODE, b"\0\0\0\0"), diff --git a/rpm_spec/qubes-qrexec.spec.in b/rpm_spec/qubes-qrexec.spec.in index 385b85b3..e6b80ed4 100644 --- a/rpm_spec/qubes-qrexec.spec.in +++ b/rpm_spec/qubes-qrexec.spec.in @@ -131,7 +131,6 @@ rm -f %{name}-%{version} %{_bindir}/qrexec-policy %dir /usr/lib/qubes -/usr/lib/qubes/qubes-rpc-multiplexer /usr/lib/qubes/qrexec-policy-agent-autostart %dir %{_sysconfdir}/qubes