diff --git a/qrexec/Makefile b/qrexec/Makefile index 06e2489..abf1961 100644 --- a/qrexec/Makefile +++ b/qrexec/Makefile @@ -2,9 +2,11 @@ CC=gcc CFLAGS+=-I. -g -Wall -Wextra -Werror -pie -fPIC `pkg-config --cflags vchan-$(BACKEND_VMM)` LIBS=`pkg-config --libs vchan-$(BACKEND_VMM)` -lqrexec-utils -all: qrexec-agent qrexec-client-vm +all: qrexec-agent qrexec-client-vm qrexec-fork-server qrexec-agent: qrexec-agent.o qrexec-agent-data.o $(CC) -pie -g -o qrexec-agent qrexec-agent.o qrexec-agent-data.o $(LIBS) +qrexec-fork-server: qrexec-fork-server.o qrexec-agent-data.o + $(CC) -pie -g -o qrexec-fork-server qrexec-fork-server.o qrexec-agent-data.o $(LIBS) qrexec-client-vm: qrexec-client-vm.o $(CC) -pie -g -o qrexec-client-vm qrexec-client-vm.o clean: @@ -12,9 +14,10 @@ clean: install: install -d $(DESTDIR)/etc/qubes-rpc - install -d $(DESTDIR)/usr/lib/qubes + install -d $(DESTDIR)/usr/lib/qubes $(DESTDIR)/usr/bin install qrexec-agent $(DESTDIR)/usr/lib/qubes install qrexec-client-vm $(DESTDIR)/usr/lib/qubes ln -s qrexec-client-vm $(DESTDIR)/usr/lib/qubes/qrexec_client_vm + install qrexec-fork-server $(DESTDIR)/usr/bin install qubes-rpc-multiplexer $(DESTDIR)/usr/lib/qubes diff --git a/qrexec/qrexec-agent.c b/qrexec/qrexec-agent.c index f6a96b9..d10d59e 100644 --- a/qrexec/qrexec-agent.c +++ b/qrexec/qrexec-agent.c @@ -19,7 +19,10 @@ * */ +#define _GNU_SOURCE #include +#include +#include #include #include #include @@ -38,7 +41,8 @@ #include "qrexec-agent.h" struct _connection_info { - int pid; + int pid; /* pid of child process handling the data */ + int fd; /* socket to process handling the data (wait for EOF here) */ int connect_domain; int connect_port; }; @@ -147,13 +151,70 @@ void wake_meminfo_writer() meminfo_write_started = 1; } -void register_vchan_connection(pid_t pid, int domain, int port) +int try_fork_server(int type, int connect_domain, int connect_port, + char *cmdline, int cmdline_len) { + char username[cmdline_len]; + char *colon; + char *fork_server_socket_path; + int s, len; + struct sockaddr_un remote; + struct qrexec_cmd_info info; + + strncpy(username, cmdline, cmdline_len); + colon = index(username, ':'); + if (!colon) + return -1; + *colon = '\0'; + + if (asprintf(&fork_server_socket_path, QREXEC_FORK_SERVER_SOCKET, username) < 0) { + fprintf(stderr, "Memory allocation failed\n"); + return -1; + } + + remote.sun_family = AF_UNIX; + strncpy(remote.sun_path, fork_server_socket_path, + sizeof(remote.sun_path)); + free(fork_server_socket_path); + + if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) { + perror("socket"); + return -1; + } + len = strlen(remote.sun_path) + sizeof(remote.sun_family); + if (connect(s, (struct sockaddr *) &remote, len) == -1) { + if (errno != ECONNREFUSED) + perror("connect"); + close(s); + return -1; + } + + info.type = type; + info.connect_domain = connect_domain; + info.connect_port = connect_port; + info.cmdline_len = cmdline_len-(strlen(username)+1); + if (!write_all(s, &info, sizeof(info))) { + perror("write"); + close(s); + return -1; + } + if (!write_all(s, colon+1, info.cmdline_len)) { + perror("write"); + close(s); + return -1; + } + + return s; +} + + +void register_vchan_connection(pid_t pid, int fd, int domain, int port) { int i; for (i = 0; i < MAX_FDS; i++) { if (connection_info[i].pid == 0) { connection_info[i].pid = pid; + connection_info[i].fd = fd; connection_info[i].connect_domain = domain; connection_info[i].connect_port = port; return; @@ -176,11 +237,23 @@ void handle_server_exec_request(struct msg_header *hdr) if (libvchan_recv(ctrl_vchan, buf, hdr->len-sizeof(params)) < 0) handle_vchan_error("read exec cmd"); + if ((hdr->type == MSG_EXEC_CMDLINE || hdr->type == MSG_JUST_EXEC) && + !strstr(buf, ":nogui:")) { + int child_socket = try_fork_server(hdr->type, + params.connect_domain, params.connect_port, + buf, hdr->len-sizeof(params)); + if (child_socket >= 0) { + register_vchan_connection(-1, child_socket, + params.connect_domain, params.connect_port); + return; + } + } + child_agent = handle_new_process(hdr->type, params.connect_domain, params.connect_port, buf, hdr->len-sizeof(params)); - register_vchan_connection(child_agent, + register_vchan_connection(child_agent, -1, params.connect_domain, params.connect_port); } @@ -280,6 +353,7 @@ void reap_children() int fill_fds_for_select(fd_set * rdset, fd_set * wrset) { int max = -1; + int i; FD_ZERO(rdset); FD_ZERO(wrset); @@ -289,6 +363,14 @@ int fill_fds_for_select(fd_set * rdset, fd_set * wrset) FD_SET(passfd_socket, rdset); if (passfd_socket > max) max = passfd_socket; + + for (i = 0; i < MAX_FDS; i++) { + if (connection_info[i].pid != 0 && connection_info[i].fd != -1) { + FD_SET(connection_info[i].fd, rdset); + if (connection_info[i].fd > max) + max = connection_info[i].fd; + } + } return max; } @@ -329,6 +411,24 @@ void handle_trigger_io() } } +void handle_terminated_fork_client(fd_set *rdset) { + int i, ret; + char buf[2]; + + for (i = 0; i < MAX_FDS; i++) { + if (connection_info[i].pid && connection_info[i].fd >= 0 && + FD_ISSET(connection_info[i].fd, rdset)) { + ret = read(connection_info[i].fd, buf, sizeof(buf)); + if (ret == 0 || (ret == -1 && errno == ECONNRESET)) { + close(connection_info[i].fd); + release_connection(i); + } else { + fprintf(stderr, "Unexpected read on fork-server connection: %d(%s)\n", ret, strerror(errno)); + } + } + } +} + int main() { fd_set rdset, wrset; @@ -362,5 +462,7 @@ int main() if (FD_ISSET(trigger_fd, &rdset)) handle_trigger_io(); + + handle_terminated_fork_client(&rdset); } } diff --git a/qrexec/qrexec-agent.h b/qrexec/qrexec-agent.h index ebf7e8d..d2f10e8 100644 --- a/qrexec/qrexec-agent.h +++ b/qrexec/qrexec-agent.h @@ -19,6 +19,8 @@ * */ +#define QREXEC_FORK_SERVER_SOCKET "/var/run/qubes/qrexec-server.%s.sock" + int handle_handshake(libvchan_t *ctrl); void handle_vchan_error(const char *op); void do_exec(const char *cmd); @@ -26,3 +28,11 @@ void do_exec(const char *cmd); pid_t handle_new_process(int type, int connect_domain, int connect_port, char *cmdline, int cmdline_len); + +struct qrexec_cmd_info { + int type; + int connect_domain; + int connect_port; + int cmdline_len; + char cmdline[0]; +}; diff --git a/qrexec/qrexec-fork-server.c b/qrexec/qrexec-fork-server.c new file mode 100644 index 0000000..da03238 --- /dev/null +++ b/qrexec/qrexec-fork-server.c @@ -0,0 +1,110 @@ +/* + * The Qubes OS Project, http://www.qubes-os.org + * + * Copyright (C) 2015 Marek Marczykowski-Górecki + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include "qrexec.h" +#include +#include "libqrexec-utils.h" +#include "qrexec-agent.h" + +void do_exec(const char *cmd) +{ + char buf[strlen(QUBES_RPC_MULTIPLEXER_PATH) + strlen(cmd) - strlen(RPC_REQUEST_COMMAND) + 1]; + /* replace magic RPC cmd with RPC multiplexer path */ + if (strncmp(cmd, RPC_REQUEST_COMMAND " ", strlen(RPC_REQUEST_COMMAND)+1)==0) { + strcpy(buf, QUBES_RPC_MULTIPLEXER_PATH); + strcpy(buf + strlen(QUBES_RPC_MULTIPLEXER_PATH), cmd + strlen(RPC_REQUEST_COMMAND)); + cmd = buf; + } + signal(SIGCHLD, SIG_DFL); + signal(SIGPIPE, SIG_DFL); + + execl("/bin/sh", "sh", "-c", cmd, NULL); + perror("execl"); + exit(1); +} + +void handle_vchan_error(const char *op) +{ + fprintf(stderr, "Error while vchan %s, exiting\n", op); + exit(1); +} + +void handle_single_command(int fd, struct qrexec_cmd_info *info) { + char cmdline[info->cmdline_len+1]; + + if (!read_all(fd, cmdline, info->cmdline_len)) + return; + cmdline[info->cmdline_len] = 0; + + handle_new_process(info->type, info->connect_domain, + info->connect_port, + cmdline, info->cmdline_len); +} + +int main(int argc, char **argv) { + int s, fd; + char *socket_path; + struct qrexec_cmd_info info; + struct sockaddr_un peer; + unsigned int addrlen; + + + if (argc == 2) { + socket_path = argv[1]; + } else if (argc == 1) { + /* this will be leaked, but we don't care as the process will then terminate */ + if (asprintf(&socket_path, QREXEC_FORK_SERVER_SOCKET, getenv("USER")) < 0) { + fprintf(stderr, "Memory allocation failed\n"); + exit(1); + } + } else { + fprintf(stderr, "Usage: %s [socket path]\n", argv[0]); + exit(1); + } + + s = get_server_socket(socket_path); + if (fcntl(s, F_SETFD, O_CLOEXEC) < 0) { + perror("fcntl"); + exit(1); + } + signal(SIGCHLD, SIG_IGN); + register_exec_func(do_exec); + + while ((fd = accept(s, (struct sockaddr *) &peer, &addrlen)) >= 0) { + if (read_all(fd, &info, sizeof(info))) { + handle_single_command(fd, &info); + } + close(fd); + addrlen = sizeof(peer); + } + close(s); + unlink(socket_path); + return 0; +} diff --git a/rpm_spec/core-vm.spec b/rpm_spec/core-vm.spec index 3584c25..a9484cf 100644 --- a/rpm_spec/core-vm.spec +++ b/rpm_spec/core-vm.spec @@ -400,6 +400,7 @@ rm -f %{name}-%{version} /usr/bin/qvm-mru-entry /usr/bin/xenstore-watch-qubes /usr/bin/qubes-desktop-run +/usr/bin/qrexec-fork-server %dir /usr/lib/qubes /usr/lib/qubes/vusb-ctl.py* /usr/lib/qubes/dispvm-prerun.sh