Skip to content

Commit 89ac875

Browse files
committed
Avoid qrexec-client for VM -> dom0 calls
It isn't needed in this case either. This saves an execve() and makes it easier to change the behavior of qrexec-client without breaking existing qrexec-daemon processes. The large refactoring fixes another bug: skip-service-descriptor=true was ignored for dom0 socket services. Thankfully this feature has not made it into a release yet. Fixes: QubesOS/qubes-issues#9066
1 parent 15f4313 commit 89ac875

File tree

8 files changed

+390
-335
lines changed

8 files changed

+390
-335
lines changed

daemon/qrexec-client.c

+33-254
Original file line numberDiff line numberDiff line change
@@ -41,24 +41,8 @@
4141
#include "libqrexec-utils.h"
4242
#include "qrexec-daemon-common.h"
4343

44-
// whether qrexec-client should replace problematic bytes with _ before printing the output
45-
static bool replace_chars_stdout = false;
46-
static bool replace_chars_stderr = false;
47-
48-
static int exit_with_code = 1;
49-
5044
#define VCHAN_BUFFER_SIZE 65536
5145

52-
#define QREXEC_DATA_MIN_VERSION QREXEC_PROTOCOL_V2
53-
54-
static int local_stdin_fd, local_stdout_fd;
55-
static pid_t local_pid = 0;
56-
/* flag if this is "remote" end of service call. In this case swap STDIN/STDOUT
57-
* msg types and send exit code at the end */
58-
static bool is_service = false;
59-
60-
static volatile sig_atomic_t sigchld = 0;
61-
6246
extern char **environ;
6347

6448
static char *xstrdup(const char *arg) {
@@ -77,62 +61,6 @@ static void set_remote_domain(const char *src_domain_name) {
7761
}
7862
}
7963

80-
/* initialize data_protocol_version */
81-
static int handle_agent_handshake(libvchan_t *vchan, bool remote_send_first)
82-
{
83-
struct msg_header hdr;
84-
struct peer_info info;
85-
int data_protocol_version = -1;
86-
int who = 0; // even - send to remote, odd - receive from remote
87-
88-
while (who < 2) {
89-
if ((who+remote_send_first) & 1) {
90-
if (!read_vchan_all(vchan, &hdr, sizeof(hdr))) {
91-
PERROR("daemon handshake");
92-
return -1;
93-
}
94-
if (hdr.type != MSG_HELLO || hdr.len != sizeof(info)) {
95-
LOG(ERROR, "Invalid daemon MSG_HELLO");
96-
return -1;
97-
}
98-
if (!read_vchan_all(vchan, &info, sizeof(info))) {
99-
PERROR("daemon handshake");
100-
return -1;
101-
}
102-
103-
data_protocol_version = info.version < QREXEC_PROTOCOL_VERSION ?
104-
info.version : QREXEC_PROTOCOL_VERSION;
105-
if (data_protocol_version < QREXEC_DATA_MIN_VERSION) {
106-
LOG(ERROR, "Incompatible daemon protocol version "
107-
"(daemon %d, client %d)",
108-
info.version, QREXEC_PROTOCOL_VERSION);
109-
return -1;
110-
}
111-
} else {
112-
hdr.type = MSG_HELLO;
113-
hdr.len = sizeof(info);
114-
info.version = QREXEC_PROTOCOL_VERSION;
115-
116-
if (!write_vchan_all(vchan, &hdr, sizeof(hdr))) {
117-
LOG(ERROR, "Failed to send MSG_HELLO hdr to daemon");
118-
return -1;
119-
}
120-
if (!write_vchan_all(vchan, &info, sizeof(info))) {
121-
LOG(ERROR, "Failed to send MSG_HELLO to daemon");
122-
return -1;
123-
}
124-
}
125-
who++;
126-
}
127-
return data_protocol_version;
128-
}
129-
130-
static void sigchld_handler(int x __attribute__((__unused__)))
131-
{
132-
sigchld = 1;
133-
signal(SIGCHLD, sigchld_handler);
134-
}
135-
13664
/* called from do_fork_exec */
13765
static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused)))
13866
{
@@ -145,128 +73,6 @@ static _Noreturn void do_exec(const char *prog, const char *username __attribute
14573
exit(1);
14674
}
14775

148-
149-
/* See also qrexec-agent.c:wait_for_session_maybe() */
150-
static bool wait_for_session_maybe(char *cmdline)
151-
{
152-
struct qrexec_parsed_command *cmd;
153-
pid_t pid;
154-
int status;
155-
bool rc = false;
156-
157-
cmd = parse_qubes_rpc_command(cmdline, false);
158-
if (!cmd)
159-
goto out;
160-
161-
if (cmd->nogui) {
162-
rc = true;
163-
goto out;
164-
}
165-
166-
if (!cmd->service_descriptor) {
167-
rc = true;
168-
goto out;
169-
}
170-
171-
if (load_service_config_v2(cmd) < 0)
172-
goto out;
173-
if (!cmd->wait_for_session) {
174-
rc = true;
175-
goto out;
176-
}
177-
178-
pid = fork();
179-
switch (pid) {
180-
case 0:
181-
close(0);
182-
exec_wait_for_session(cmd->source_domain);
183-
PERROR("exec");
184-
_exit(1);
185-
case -1:
186-
PERROR("fork");
187-
goto out;
188-
default:
189-
rc = true;
190-
}
191-
192-
if (waitpid(local_pid, &status, 0) > 0) {
193-
if (status != 0)
194-
LOG(ERROR, "wait-for-session exited with status %d", status);
195-
} else
196-
PERROR("waitpid");
197-
198-
out:
199-
destroy_qrexec_parsed_command(cmd);
200-
return rc;
201-
}
202-
203-
static int prepare_local_fds(char *cmdline, struct buffer *stdin_buffer)
204-
{
205-
if (stdin_buffer == NULL)
206-
abort();
207-
if (!cmdline) {
208-
local_stdin_fd = 1;
209-
local_stdout_fd = 0;
210-
return 0;
211-
}
212-
signal(SIGCHLD, sigchld_handler);
213-
return execute_qubes_rpc_command(cmdline, &local_pid, &local_stdin_fd, &local_stdout_fd,
214-
NULL, false, stdin_buffer);
215-
}
216-
217-
// See also qrexec-agent/qrexec-agent-data.c
218-
static _Noreturn void handle_failed_exec(libvchan_t *data_vchan)
219-
{
220-
int exit_code = 127;
221-
struct msg_header hdr = {
222-
.type = MSG_DATA_STDOUT,
223-
.len = 0,
224-
};
225-
226-
LOG(ERROR, "failed to spawn process, exiting");
227-
/*
228-
* TODO: In case we fail to execute a *local* process (is_service false),
229-
* we should either
230-
* - exit even before connecting to remote domain, or
231-
* - send stdin EOF and keep waiting for remote exit code.
232-
*
233-
* That will require a slightly bigger refactoring. Right now it's not
234-
* important, because this function should handle QUBESRPC command failure
235-
* only (normal commands go through fork+exec), but it will be necessary
236-
* when we support sockets as a local process.
237-
*/
238-
if (is_service) {
239-
libvchan_send(data_vchan, &hdr, sizeof(hdr));
240-
send_exit_code(data_vchan, exit_code);
241-
libvchan_close(data_vchan);
242-
}
243-
exit(exit_code);
244-
}
245-
static void select_loop(libvchan_t *vchan, int data_protocol_version, struct buffer *stdin_buf)
246-
{
247-
struct process_io_request req = { 0 };
248-
int exit_code;
249-
250-
req.vchan = vchan;
251-
req.stdin_buf = stdin_buf;
252-
req.stdin_fd = local_stdin_fd;
253-
req.stdout_fd = local_stdout_fd;
254-
req.stderr_fd = -1;
255-
req.local_pid = local_pid;
256-
req.is_service = is_service;
257-
req.replace_chars_stdout = replace_chars_stdout;
258-
req.replace_chars_stderr = replace_chars_stderr;
259-
req.data_protocol_version = data_protocol_version;
260-
req.sigchld = &sigchld;
261-
req.sigusr1 = NULL;
262-
req.prefix_data.data = NULL;
263-
req.prefix_data.len = 0;
264-
265-
exit_code = process_io(&req);
266-
libvchan_close(vchan);
267-
exit(exit_with_code ? exit_code : 0);
268-
}
269-
27076
static struct option longopts[] = {
27177
{ "help", no_argument, 0, 'h' },
27278
{ "socket-dir", required_argument, 0, 'd'+128 },
@@ -337,12 +143,6 @@ static void parse_connect(char *str, char **request_id,
337143
exit(1);
338144
}
339145

340-
static void sigalrm_handler(int x __attribute__((__unused__)))
341-
{
342-
LOG(ERROR, "vchan connection timeout");
343-
exit(1);
344-
}
345-
346146
static const long BILLION_NANOSECONDS = 1000000000L;
347147

348148
static void wait_for_vchan_client_with_timeout(libvchan_t *conn, time_t timeout) {
@@ -404,23 +204,6 @@ static size_t compute_service_length(const char *const remote_cmdline, const cha
404204
return service_length;
405205
}
406206

407-
static void handshake_and_go(libvchan_t *data_vchan,
408-
struct buffer *stdin_buffer,
409-
bool remote_send_first,
410-
int prepare_ret)
411-
{
412-
if (data_vchan == NULL || !libvchan_is_open(data_vchan)) {
413-
LOG(ERROR, "Failed to open data vchan connection");
414-
exit(1);
415-
}
416-
int data_protocol_version = handle_agent_handshake(data_vchan, remote_send_first);
417-
if (data_protocol_version < 0)
418-
exit(1);
419-
if (prepare_ret < 0)
420-
handle_failed_exec(data_vchan);
421-
select_loop(data_vchan, data_protocol_version, stdin_buffer);
422-
}
423-
424207
int main(int argc, char **argv)
425208
{
426209
int opt;
@@ -440,6 +223,9 @@ int main(int argc, char **argv)
440223
struct service_params svc_params;
441224
int prepare_ret;
442225
bool kill = false;
226+
bool replace_chars_stdout = false;
227+
bool replace_chars_stderr = false;
228+
bool exit_with_code = true;
443229
int rc = 126;
444230

445231
if (argc < 3) {
@@ -461,15 +247,14 @@ int main(int argc, char **argv)
461247
just_exec = true;
462248
break;
463249
case 'E':
464-
exit_with_code = 0;
250+
exit_with_code = false;
465251
break;
466252
case 'c':
467253
if (request_id != NULL) {
468254
warnx("ERROR: -c passed more than once");
469255
usage(argv[0]);
470256
}
471257
parse_connect(optarg, &request_id, &src_domain_name, &src_domain_id);
472-
is_service = true;
473258
break;
474259
case 't':
475260
replace_chars_stdout = true;
@@ -518,39 +303,12 @@ int main(int argc, char **argv)
518303
LOG(ERROR, "internal error: src_domain_name should not be NULL here");
519304
abort();
520305
}
521-
set_remote_domain(src_domain_name);
522-
s = connect_unix_socket_by_id(src_domain_id);
523-
if (s < 0) {
524-
goto cleanup;
525-
}
526-
if (!negotiate_connection_params(s,
527-
0, /* dom0 */
528-
MSG_SERVICE_CONNECT,
529-
(void*)&svc_params,
530-
sizeof(svc_params),
531-
&data_domain,
532-
&data_port)) {
533-
goto cleanup;
534-
}
535-
536-
struct buffer stdin_buffer;
537-
buffer_init(&stdin_buffer);
538-
if (!wait_for_session_maybe(remote_cmdline)) {
539-
LOG(ERROR, "Cannot load service configuration, or forking process failed");
540-
prepare_ret = -1;
541-
} else {
542-
prepare_ret = prepare_local_fds(remote_cmdline, &stdin_buffer);
543-
}
544-
void (*old_handler)(int);
545-
546-
/* libvchan_client_init is blocking and does not support connection
547-
* timeout, so use alarm(2) for that... */
548-
old_handler = signal(SIGALRM, sigalrm_handler);
549-
alarm(connection_timeout);
550-
data_vchan = libvchan_client_init(data_domain, data_port);
551-
alarm(0);
552-
signal(SIGALRM, old_handler);
553-
handshake_and_go(data_vchan, &stdin_buffer, true, prepare_ret);
306+
rc = run_qrexec_to_dom0(&svc_params,
307+
src_domain_id,
308+
src_domain_name,
309+
remote_cmdline,
310+
connection_timeout,
311+
exit_with_code);
554312
} else {
555313
s = connect_unix_socket(domname);
556314
if (!negotiate_connection_params(s,
@@ -590,15 +348,36 @@ int main(int argc, char **argv)
590348
set_remote_domain(domname);
591349
struct buffer stdin_buffer;
592350
buffer_init(&stdin_buffer);
593-
prepare_ret = prepare_local_fds(local_cmdline, &stdin_buffer);
351+
if (local_cmdline != NULL) {
352+
struct qrexec_parsed_command *command =
353+
parse_qubes_rpc_command(local_cmdline, false);
354+
if (!command)
355+
prepare_ret = 127;
356+
else {
357+
prepare_ret = prepare_local_fds(command, &stdin_buffer);
358+
destroy_qrexec_parsed_command(command);
359+
}
360+
} else {
361+
prepare_ret = 0;
362+
}
363+
594364
data_vchan = libvchan_server_init(data_domain, data_port,
595365
VCHAN_BUFFER_SIZE, VCHAN_BUFFER_SIZE);
596366
if (!data_vchan) {
597367
LOG(ERROR, "Failed to start data vchan server");
598368
exit(1);
599369
}
600370
wait_for_vchan_client_with_timeout(data_vchan, connection_timeout);
601-
handshake_and_go(data_vchan, &stdin_buffer, false, prepare_ret);
371+
struct handshake_params params = {
372+
.data_vchan = data_vchan,
373+
.stdin_buffer = &stdin_buffer,
374+
.remote_send_first = false,
375+
.prepare_ret = prepare_ret,
376+
.exit_with_code = exit_with_code,
377+
.replace_chars_stdout = replace_chars_stdout,
378+
.replace_chars_stderr = replace_chars_stderr,
379+
};
380+
rc = handshake_and_go(&params);
602381
}
603382
}
604383

0 commit comments

Comments
 (0)