diff --git a/agent/qrexec-agent-data.c b/agent/qrexec-agent-data.c index 8e760936..08fb71ff 100644 --- a/agent/qrexec-agent-data.c +++ b/agent/qrexec-agent-data.c @@ -138,22 +138,29 @@ 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 QREXEC_EXIT_PROBLEM; + 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); - int status = find_qrexec_service(cmd, &socket_fd, &stdin_buffer); + int status = find_qrexec_service(cmd, &socket_fd, &stdin_buffer, &buf); if (status == -1) return QREXEC_EXIT_SERVICE_NOT_FOUND; if (status != 0) return QREXEC_EXIT_PROBLEM; if (socket_fd != -1) - return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? 0 : QREXEC_EXIT_PROBLEM; + return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? + 0 : QREXEC_EXIT_PROBLEM; + } else { + buf.data = NULL; } switch (pid = fork()) { case -1: @@ -161,11 +168,22 @@ static int handle_just_exec(struct qrexec_parsed_command *cmd) return QREXEC_EXIT_PROBLEM; case 0: fdn = open("/dev/null", O_RDWR); - fix_fds(fdn, fdn, fdn); - do_exec(cmd->command, cmd->username); + if (fdn < 0) { + LOG(ERROR, "open /dev/null failed"); + _exit(QREXEC_EXIT_PROBLEM); + } + int other_pid; + log_fd = cmd->service_descriptor ? open_logger(cmd, &other_pid) : fdn; + if (log_fd < 0) + _exit(QREXEC_EXIT_PROBLEM); + 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); + if (buf.data) + LOG(INFO, "executed (nowait): %s %s (pid %d)", buf.data, cmd->command, pid); + else + LOG(INFO, "executed (nowait): %s (pid %d)", cmd->command, pid); return 0; } @@ -261,6 +279,7 @@ static int handle_new_process_common( return 0; } + int logger_pid = 0; req.vchan = data_vchan; req.stdin_buf = &stdin_buf; @@ -280,6 +299,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 = qrexec_process_io(&req, cmd); diff --git a/agent/qrexec-agent.c b/agent/qrexec-agent.c index b4f41212..ed6cdf71 100644 --- a/agent/qrexec-agent.c +++ b/agent/qrexec-agent.c @@ -137,7 +137,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; @@ -172,7 +172,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); @@ -279,7 +279,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); @@ -317,7 +317,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 83ed3f43..f85064ed 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 0f03bb8d..e933448b 100644 --- a/agent/qrexec-client-vm.c +++ b/agent/qrexec-client-vm.c @@ -43,7 +43,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 9edba38e..c9c7afbd 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 fb20aaf5..b24a79c6 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); + /* avoid calling RPC command through shell */ + 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); + /* if above haven't executed RPC command, pass it to shell */ + 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 3d20849e..9e8da61e 100644 --- a/daemon/qrexec-daemon.c +++ b/daemon/qrexec-daemon.c @@ -1131,14 +1131,15 @@ static enum policy_response connect_daemon_socket( } /* 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); + /* avoid calling RPC command through shell */ + 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); + /* if above haven't executed RPC command, pass it to shell */ + execl("/bin/bash", "bash", "-c", cmd, NULL); PERROR("exec bash"); + /* treat ENOENT as "problem" because bash should always exist */ _exit(QREXEC_EXIT_PROBLEM); } diff --git a/libqrexec/Makefile b/libqrexec/Makefile index 3838f6a6..bd9bcb8c 100644 --- a/libqrexec/Makefile +++ b/libqrexec/Makefile @@ -22,7 +22,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 vchan_timeout.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 vchan_timeout.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 1c6196c2..5eac233c 100644 --- a/libqrexec/exec.c +++ b/libqrexec/exec.c @@ -47,43 +47,116 @@ 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 (program) { + assert(program); char *prog_copy; char *tok, *savetok; - char *argv[16]; // right now 6 are used, but allow future extensions - size_t i = 1; + 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 (strncmp(cmd, RPC_REQUEST_COMMAND " ", RPC_REQUEST_COMMAND_LEN + 1) != 0) { + LOG(ERROR, "program != NULL, but '%s' does not start with '%s '", + cmd, RPC_REQUEST_COMMAND " "); + assert(!"Invalid command"); + _exit(QREXEC_EXIT_PROBLEM); + } - if (prog[RPC_REQUEST_COMMAND_LEN] != ' ') { - LOG(ERROR, "\"" RPC_REQUEST_COMMAND "\" not followed by space"); - _exit(126); + 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(QREXEC_EXIT_PROBLEM); + } + size_t iterator = 0; + for (char *const *env = envp; *env; ++env) { + if (!should_strip_env_var(*env)) { + EXTEND_RAW(*env); + } } - prog_copy = strdup(prog + RPC_REQUEST_COMMAND_LEN + 1); + prog_copy = strdup(cmd + RPC_REQUEST_COMMAND_LEN + 1); if (!prog_copy) { PERROR("strdup"); _exit(QREXEC_EXIT_PROBLEM); } + argv[i++] = (char *)program; tok=strtok_r(prog_copy, " ", &savetok); while (tok != NULL) { if (i >= sizeof(argv)/sizeof(argv[0])-1) { - LOG(ERROR, "To many arguments to %s", RPC_REQUEST_COMMAND); + LOG(ERROR, "Too many arguments to %s", RPC_REQUEST_COMMAND); _exit(QREXEC_EXIT_PROBLEM); } argv[i++] = tok; - 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); - bool noent = errno == ENOENT; - PERROR("exec qubes-rpc-multiplexer"); - _exit(noent ? QREXEC_EXIT_SERVICE_NOT_FOUND : QREXEC_EXIT_PROBLEM); + if (i == 5) { + EXTEND("QREXEC_REQUESTED_TARGET_TYPE=%s", argv[3]); + if (strcmp(argv[3], "name") == 0) { + EXTEND("QREXEC_REQUESTED_TARGET=%s", argv[4]); + } else if (strcmp(argv[3], "keyword") == 0) { + EXTEND("QREXEC_REQUESTED_TARGET_KEYWORD=%s", argv[4]); + } else { + // requested target type unknown, ignore + } + } else if (i == 3) { + EXTEND_RAW("QREXEC_REQUESTED_TARGET_TYPE="); + } else { + LOG(ERROR, "invalid number of arguments: %zu", i); + _exit(QREXEC_EXIT_PROBLEM); + } + 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); + _exit(errno == ENOENT ? QREXEC_EXIT_SERVICE_NOT_FOUND : QREXEC_EXIT_PROBLEM); +bad_asprintf: + PERROR("asprintf"); + _exit(QREXEC_EXIT_PROBLEM); + } else { + assert(strncmp(cmd, RPC_REQUEST_COMMAND, RPC_REQUEST_COMMAND_LEN) != 0); } } @@ -94,7 +167,9 @@ void fix_fds(int fdin, int fdout, int fderr) fdin, fdout, fderr); _exit(125); } - if (dup2(fdin, 0) < 0 || dup2(fdout, 1) < 0 || dup2(fderr, 2) < 0) { + if ((fdin != 0 && dup2(fdin, 0) < 0) || + (fdout != 1 && dup2(fdout, 1) < 0) || + (fderr != 2 && dup2(fderr, 2) < 0)) { PERROR("dup2"); abort(); } @@ -112,7 +187,8 @@ void fix_fds(int fdin, int fdout, int fderr) close(i); } -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, @@ -144,7 +220,7 @@ static int do_fork_exec(const char *user, fix_fds(inpipe[0], outpipe[1], 2); if (exec_func != NULL) - exec_func(cmdline, user); + exec_func(prog, cmdline, user); abort(); default: retval = 0; @@ -450,7 +526,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; @@ -538,7 +614,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 - int find_res = 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) }; + int find_res = find_qrexec_service(cmd, stdin_fd, stdin_buffer, &buf); if (find_res != 0) { assert(find_res < 0); return find_res; @@ -550,12 +628,12 @@ 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); } } static bool validate_port(const char *port) { @@ -632,11 +710,11 @@ static int qubes_tcp_connect(const char *host, const char *port) int find_qrexec_service( 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; @@ -645,11 +723,11 @@ int 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) { if (ret == -1) @@ -662,11 +740,11 @@ int 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 -2; } - 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 -2; @@ -682,9 +760,9 @@ int find_qrexec_service( return 0; } else if (S_ISLNK(statbuf.st_mode)) { /* TCP-based service */ - assert(path_buffer.buflen >= (int)sizeof("/dev/tcp") - 1); - assert(memcmp(path_buffer.data, "/dev/tcp", sizeof("/dev/tcp") - 1) == 0); - char *address = path_buffer.data + (sizeof("/dev/tcp") - 1); + assert(path_buffer->buflen >= (int)sizeof("/dev/tcp") - 1); + assert(memcmp(path_buffer->data, "/dev/tcp", sizeof("/dev/tcp") - 1) == 0); + char *address = path_buffer->data + (sizeof("/dev/tcp") - 1); char *host = NULL, *port = NULL; if (*address == '/') { host = address + 1; @@ -699,7 +777,7 @@ int find_qrexec_service( if (port == NULL) { if (cmd->arg == NULL || *cmd->arg == '\0') { LOG(ERROR, "No or empty argument provided, cannot connect to %s", - path_buffer.data); + path_buffer->data); return -2; } if (host == NULL) { @@ -726,7 +804,8 @@ int find_qrexec_service( } } else { if (cmd->arg != NULL && *cmd->arg != '\0') { - LOG(ERROR, "Unexpected argument %s to service %s", cmd->arg, path_buffer.data); + LOG(ERROR, "Unexpected argument %s to service %s", cmd->arg, + path_buffer->data); return -2; } } @@ -744,30 +823,30 @@ int find_qrexec_service( return 0; } - if (euidaccess(path_buffer.data, X_OK) == 0) { + if (euidaccess(path_buffer->data, X_OK) == 0) { /* Executable-based service. */ if (!cmd->send_service_descriptor) { LOG(WARNING, "Warning: ignoring skip-service-descriptor=true " "for execute executable service %s", - path_buffer.data); + path_buffer->data); } if (cmd->exit_on_stdout_eof) { LOG(WARNING, "Warning: ignoring exit-on-service-eof=true " "for executable service %s", - path_buffer.data); + path_buffer->data); cmd->exit_on_stdout_eof = false; } if (cmd->exit_on_stdin_eof) { LOG(WARNING, "Warning: ignoring exit-on-client-eof=true " "for executable service %s", - path_buffer.data); + path_buffer->data); cmd->exit_on_stdin_eof = false; } return 0; } LOG(ERROR, "Unknown service type (not executable, not a socket): %.*s", - path_buffer.buflen, path_buffer.data); + path_buffer->buflen, path_buffer->data); return -2; } diff --git a/libqrexec/libqrexec-utils.h b/libqrexec/libqrexec-utils.h index 18d9b141..484e1d22 100644 --- a/libqrexec/libqrexec-utils.h +++ b/libqrexec/libqrexec-utils.h @@ -104,6 +104,8 @@ struct qrexec_parsed_command { /* Pointer to the argument, or NULL if there is no argument. * Same buffer as "service_descriptor". */ char *arg; + /* length of the command */ + size_t service_descriptor_len; }; /* Parse a command, return NULL on failure. Uses cmd->cmdline @@ -137,16 +139,26 @@ int load_service_config(struct qrexec_parsed_command *cmd_name, __attribute__((visibility("default"))) int load_service_config_v2(struct qrexec_parsed_command *cmd_name); -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); __attribute__((visibility("default"))) 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 + +/** + * \param program Full path to program to execute or NULL. + * \param cmd RPC command, including "QUBESRPC " prefix, if *program* is not NULL. + * Otherwise *program* must be NULL and *cmd* must not start with "QUBESRPC". + * \param envp Environment passed to execve(), ignored if *program* is NULL. + * + * If *program* is not NULL, execute it as an RPC service or call _exit() on failure. + * *cmd* is used to set the argument (if any) and "QREXEC_*" environment variables. + * Environment variables in *envp* that start with "QREXEC" are ignored, except for + * "QREXEC_SERVICE_PATH", which is inherited. If *program* is not NULL and *cmd* does + * not start with "QUBESRPC ", or if *program* is NULL and *cmd* starts with "QUBESRPC", + * fails an assertion. If *program* is NULL and *cmd* does not start with "QUBESRPC", + * returns without doing anything. */ __attribute__((visibility("default"))) -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[]); /* Execute `qubes.WaitForSession` service, do not return on success, return -1 * (maybe setting errno) on failure. */ @@ -194,13 +206,17 @@ int execute_parsed_qubes_rpc_command( * the socket, 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 0 if the implementation is found (and, for sockets, connected to) * successfully, -1 if not found, -2 if problem. */ __attribute__((visibility("default"))) int find_qrexec_service( 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 @@ -288,7 +304,7 @@ struct process_io_request { * *local* process. For a *remote* process, stdin_fd is the standard * *output*, stdout_fd is the standard *input*, and stderr_fd must be -1. */ // 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; @@ -318,6 +334,9 @@ struct process_io_request { volatile sig_atomic_t *sigusr1; struct prefix_data prefix_data; }; +/** Open an FD to a logger */ +__attribute__((visibility("default"))) +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..a1d378b4 --- /dev/null +++ b/libqrexec/open_logger.c @@ -0,0 +1,38 @@ +#include +#include +#include +#include +#include + +int open_logger(struct qrexec_parsed_command *command, int *pid) +{ + int pipes[2]; + if (pipe2(pipes, O_CLOEXEC)) { + LOG(ERROR, "Cannot create status pipe"); + return -1; + } + char *buf[] = { + "logger", + "-t", + NULL, + NULL, + }; + if (asprintf(buf + 2, "%.*s-%s", (int)command->service_descriptor_len, + command->service_descriptor, command->source_domain) < 0) { + LOG(ERROR, "asprintf() failed"); + return -1; + } + switch ((*pid = fork())) { + case -1: + LOG(ERROR, "Cannot fork logger process"); + return -1; + case 0: + fix_fds(pipes[0], 1, 2); + execvp("logger", buf); + _exit(126); + default: + free(buf[2]); + close(pipes[0]); + return pipes[1]; + } +} diff --git a/libqrexec/process_io.c b/libqrexec/process_io.c index b0524876..4b16baf6 100644 --- a/libqrexec/process_io.c +++ b/libqrexec/process_io.c @@ -96,6 +96,7 @@ int qrexec_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 const is_service = req->is_service; @@ -344,7 +345,7 @@ int qrexec_process_io(const struct process_io_request *req, if (prefix.len > 0 || (stdout_fd >= 0 && fds[FD_STDOUT].revents)) { switch (handle_input_v2( vchan, stdout_fd, stdout_msg_type, - &prefix, &remote_buffer)) { + &prefix, &remote_buffer, -1)) { case REMOTE_ERROR: if (!is_service && remote_status == -1) { /* Even if sending fails, still try to read remaining @@ -365,7 +366,7 @@ int qrexec_process_io(const struct process_io_request *req, if (stderr_fd >= 0 && fds[FD_STDERR].revents) { switch (handle_input_v2( vchan, stderr_fd, MSG_DATA_STDERR, - &empty, &remote_buffer)) { + &empty, &remote_buffer, dup_fd)) { case REMOTE_ERROR: handle_vchan_error("send(handle_input stderr)"); break; diff --git a/libqrexec/qrexec.h b/libqrexec/qrexec.h index a8fc06b3..ec0d44a2 100644 --- a/libqrexec/qrexec.h +++ b/libqrexec/qrexec.h @@ -163,7 +163,6 @@ enum { #define QREXEC_AGENT_TRIGGER_PATH "/var/run/qubes/qrexec-agent" #define QREXEC_AGENT_FDPASS_PATH "/var/run/qubes/qrexec-agent-fdpass" #define MEMINFO_WRITER_PIDFILE "/var/run/meminfo-writer.pid" -#define QUBES_RPC_MULTIPLEXER_PATH "/usr/lib/qubes/qubes-rpc-multiplexer" #define QREXEC_DAEMON_SOCKET_DIR "/var/run/qubes" #define QREXEC_POLICY_PROGRAM "/usr/bin/qrexec-policy-exec" #define QREXEC_SERVICE_PATH "/usr/local/etc/qubes-rpc:/etc/qubes-rpc" diff --git a/libqrexec/remote.c b/libqrexec/remote.c index 314bb8af..b831e7a6 100644 --- a/libqrexec/remote.c +++ b/libqrexec/remote.c @@ -150,7 +150,8 @@ int handle_remote_data_v2( int handle_input_v2( libvchan_t *vchan, int fd, int msg_type, struct prefix_data *prefix_data, - const struct buffer *buffer) + const struct buffer *buffer, + int dup_fd) { const size_t max_len = (size_t)buffer->buflen; char *buf = buffer->data; @@ -175,6 +176,8 @@ int handle_input_v2( 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/libqrexec/remote.h b/libqrexec/remote.h index 7d19f7a4..b6a42397 100644 --- a/libqrexec/remote.h +++ b/libqrexec/remote.h @@ -73,9 +73,12 @@ int handle_remote_data_v2( * initialized, and will _not_ be anything meaningful on return. The * buffer pointer and length will not be freed or reallocated, though. * In Rust terms: this is an &mut [MaybeUninit]. + * + * If out_fd is not -1, all data written is duplicated to it. */ int handle_input_v2( libvchan_t *vchan, int fd, int msg_type, struct prefix_data *prefix_data, - const struct buffer *buffer); + const struct buffer *buffer, + int out_fd); #pragma GCC visibility pop 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 c71c3c43..52653a41 100644 --- a/qrexec/tests/socket/agent.py +++ b/qrexec/tests/socket/agent.py @@ -113,7 +113,7 @@ def setUp(self): os.mkdir(os.path.join(self.tempdir, "rpc-config")) self.addCleanup(shutil.rmtree, self.tempdir) - def start_agent(self, fail_exec=False): + def start_agent(self): if self.agent is not None: return env = os.environ.copy() @@ -130,11 +130,6 @@ def start_agent(self, fail_exec=False): ] ) env["QUBES_RPC_CONFIG_PATH"] = os.path.join(self.tempdir, "rpc-config") - env["QREXEC_MULTIPLEXER_PATH"] = ( - "/dev/null" - if fail_exec - else os.path.join(ROOT_PATH, "lib", "qubes-rpc-multiplexer") - ) cmd = [ os.path.join(ROOT_PATH, "agent", "qrexec-agent"), "--no-fork-server", @@ -377,10 +372,8 @@ def trigger_service(self, dom0, client, target_domain_name, service_name): @unittest.skipIf(os.environ.get("SKIP_SOCKET_TESTS"), "socket tests not set up") class TestAgentExecQubesRpc(TestAgentBase): - def execute_qubesrpc( - self, service: str, src_domain_name: str, fail_exec: bool = False - ): - self.start_agent(fail_exec) + def execute_qubesrpc(self, service: str, src_domain_name: str): + self.start_agent() dom0 = self.connect_dom0() @@ -574,14 +567,12 @@ def test_exec_service_not_found(self): """ self._test_exec_service_fail(qrexec.QREXEC_EXIT_SERVICE_NOT_FOUND) - def test_exec_service_bad_multiplexer(self): + def test_exec_service_bad_service(self): """ - RPC multiplexer is junk + Service to execute is junk (-ENOEXEC from execve()) """ util.make_executable_service(self.tempdir, "rpc", "qubes.Service", "") - target, dom0 = self.execute_qubesrpc( - "qubes.Service+arg", "domX", fail_exec=True - ) + target, dom0 = self.execute_qubesrpc("qubes.Service+arg", "domX") target.send_message(qrexec.MSG_DATA_STDIN, b"") messages = util.sort_messages(target.recv_all_messages()) self.assertEqual(messages[0], (qrexec.MSG_DATA_STDOUT, b"")) diff --git a/qrexec/tests/socket/daemon.py b/qrexec/tests/socket/daemon.py index c7e3a1e9..4da11d5b 100644 --- a/qrexec/tests/socket/daemon.py +++ b/qrexec/tests/socket/daemon.py @@ -652,9 +652,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"), @@ -1109,12 +1106,12 @@ def _test_run_dom0_service_failed( def test_run_dom0_service_3_args(self): self._test_run_dom0_service_failed( - exit_status=1, cmd="QUBESRPC qubes.Service+arg src_domain name" + cmd="QUBESRPC qubes.Service+arg src_domain name" ) def test_run_dom0_service_5_args(self): self._test_run_dom0_service_failed( - exit_status=1, cmd="QUBESRPC qubes.Service+arg src_domain name a b" + cmd="QUBESRPC qubes.Service+arg src_domain name a b" ) def test_run_dom0_service_not_found(self): diff --git a/selinux/qubes-core-qrexec.fc b/selinux/qubes-core-qrexec.fc index 2ffe77d3..fdb71d6d 100644 --- a/selinux/qubes-core-qrexec.fc +++ b/selinux/qubes-core-qrexec.fc @@ -1 +1,3 @@ /usr/lib/qubes/qrexec-agent -- gen_context(system_u:object_r:qubes_qrexec_agent_exec_t,s0) +/etc/qubes-rpc/qrexec/[^/]+ -- gen_context(system_u:object_r:bin_t,s0) +/usr/local/etc/qubes-rpc/qrexec/[^/]+ -- gen_context(system_u:object_r:bin_t,s0)