From 849b29538436cb3beaa36049cc5190a692ed529f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Marczykowski-G=C3=B3recki?= Date: Wed, 17 Aug 2016 02:31:48 +0200 Subject: [PATCH] qrexec: add option to wait for VM-VM connection termination Normally when qrexec-client setup VM-VM connection it exits immediatelly. But it may be useful to wait for the connection to terminate - for example to cleanup DispVM. qrexec-daemon (the one that allocated vchan port) do receive such notification, so expose such option to qrexec-client. QubesOS/qubes-issues#2253 --- qrexec/qrexec-client.c | 23 ++++++++++++++++++++--- qrexec/qrexec-daemon.c | 34 ++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 11 deletions(-) diff --git a/qrexec/qrexec-client.c b/qrexec/qrexec-client.c index c062470..cf6d69a 100644 --- a/qrexec/qrexec-client.c +++ b/qrexec/qrexec-client.c @@ -537,11 +537,12 @@ static void select_loop(libvchan_t *vchan) static void usage(char *name) { fprintf(stderr, - "usage: %s [-w timeout] [-t] [-T] -d domain_name [" + "usage: %s [-w timeout] [-W] [-t] [-T] -d domain_name [" "-l local_prog|" "-c request_id,src_domain_name,src_domain_id|" "-e] remote_cmdline\n" "-e means exit after sending cmd,\n" + "-W waits for connection end even in case of VM-VM (-c)\n" "-t enables replacing ESC character with '_' in command output, -T is the same for stderr\n" "-c: connect to existing process (response to trigger service call)\n" "-w timeout: override default connection timeout of 5s (set 0 for no timeout)\n", @@ -641,6 +642,7 @@ int main(int argc, char **argv) int msg_type; int s; int just_exec = 0; + int wait_connection_end = 0; int connect_existing = 0; char *local_cmdline = NULL; char *remote_cmdline = NULL; @@ -649,7 +651,7 @@ int main(int argc, char **argv) int src_domain_id = 0; /* if not -c given, the process is run in dom0 */ int connection_timeout = 5; struct service_params svc_params; - while ((opt = getopt(argc, argv, "d:l:ec:tTw:")) != -1) { + while ((opt = getopt(argc, argv, "d:l:ec:tTw:W")) != -1) { switch (opt) { case 'd': domname = strdup(optarg); @@ -674,6 +676,9 @@ int main(int argc, char **argv) case 'w': connection_timeout = atoi(optarg); break; + case 'W': + wait_connection_end = 1; + break; default: usage(argv[0]); } @@ -749,13 +754,25 @@ int main(int argc, char **argv) strlen(remote_cmdline) + 1, &data_domain, &data_port); - close(s); + if (wait_connection_end && connect_existing) + /* save socket fd, 's' will be reused for the other qrexec-daemon + * connection */ + wait_connection_end = s; + else + close(s); setenv("QREXEC_REMOTE_DOMAIN", domname, 1); prepare_local_fds(local_cmdline); if (connect_existing) { s = connect_unix_socket(src_domain_name); send_service_connect(s, request_id, data_domain, data_port); close(s); + if (wait_connection_end) { + /* wait for EOF */ + fd_set read_fd; + FD_ZERO(&read_fd); + FD_SET(wait_connection_end, &read_fd); + select(wait_connection_end+1, &read_fd, NULL, NULL, 0); + } } else { data_vchan = libvchan_server_init(data_domain, data_port, VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE); diff --git a/qrexec/qrexec-daemon.c b/qrexec/qrexec-daemon.c index c53bac2..964ffff 100644 --- a/qrexec/qrexec-daemon.c +++ b/qrexec/qrexec-daemon.c @@ -71,6 +71,12 @@ int policy_pending_max = -1; * either VCHAN_PORT_* or remote domain id for used port */ int used_vchan_ports[MAX_CLIENTS]; +/* notify client (close its connection) when connection initiated by it was + * terminated - used by qrexec-policy to cleanup (disposable) VM; indexed with + * vchan port number relative to VCHAN_BASE_DATA_PORT; stores fd of given + * client or -1 if none requested */ +int vchan_port_notify_client[MAX_CLIENTS]; + int max_client_fd = -1; // current max fd of all clients; so that we need not to scan all the "clients" table int qrexec_daemon_unix_socket_fd; // /var/run/qubes/qrexec.xid descriptor const char *default_user = "user"; @@ -312,6 +318,7 @@ void init(int xid) clients[i].state = CLIENT_INVALID; policy_pending[i].pid = 0; used_vchan_ports[i] = VCHAN_PORT_UNUSED; + vchan_port_notify_client[i] = VCHAN_PORT_UNUSED; } /* When running as root, make the socket accessible; perms on /var/run/qubes still apply */ @@ -358,14 +365,6 @@ static int allocate_vchan_port(int new_state) return -1; } -static void release_vchan_port(int port, int expected_remote_id) -{ - /* release only if was reserved for connection to given domain */ - if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) { - used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED; - } -} - static void handle_new_client() { int fd = do_accept(qrexec_daemon_unix_socket_fd); @@ -387,8 +386,25 @@ static void handle_new_client() static void terminate_client(int fd) { + int port; clients[fd].state = CLIENT_INVALID; close(fd); + /* if client requested vchan connection end notify, cancel it */ + for (port = 0; port < MAX_CLIENTS; port++) { + if (vchan_port_notify_client[port] == fd) + vchan_port_notify_client[port] = VCHAN_PORT_UNUSED; + } +} + +static void release_vchan_port(int port, int expected_remote_id) +{ + /* release only if was reserved for connection to given domain */ + if (used_vchan_ports[port-VCHAN_BASE_DATA_PORT] == expected_remote_id) { + used_vchan_ports[port-VCHAN_BASE_DATA_PORT] = VCHAN_PORT_UNUSED; + /* notify client if requested - it will clear notification request */ + if (vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT] != VCHAN_PORT_UNUSED) + terminate_client(vchan_port_notify_client[port-VCHAN_BASE_DATA_PORT]); + } } static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr) @@ -433,6 +449,8 @@ static int handle_cmdline_body_from_client(int fd, struct msg_header *hdr) terminate_client(fd); return 0; } + /* notify the client when this connection got terminated */ + vchan_port_notify_client[params.connect_port-VCHAN_BASE_DATA_PORT] = fd; client_params.connect_port = params.connect_port; client_params.connect_domain = remote_domain_id; hdr->len = sizeof(client_params);