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
This commit is contained in:
Marek Marczykowski-Górecki 2016-08-17 02:31:48 +02:00
parent 9192bb0d44
commit 849b295384
No known key found for this signature in database
GPG Key ID: 063938BA42CFA724
2 changed files with 46 additions and 11 deletions

View File

@ -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);

View File

@ -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);