Skip to content

Commit 17be9de

Browse files
committed
Avoid qubes-rpc-multiplexer for dom0 -> dom0 calls
Instead, just use qrexec-client, as with any other service call.
1 parent d4011a9 commit 17be9de

File tree

6 files changed

+154
-24
lines changed

6 files changed

+154
-24
lines changed

daemon/qrexec-client.c

Lines changed: 74 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,10 @@ int main(int argc, char **argv)
201201
usage(argv[0], 1);
202202
}
203203
parse_connect(optarg, &request_id, &src_domain_name, &src_domain_id);
204+
if (target_refers_to_dom0(src_domain_name) || src_domain_id == 0) {
205+
warnx("ERROR: -c cannot be used for requests to dom0");
206+
usage(argv[0], 1);
207+
}
204208
break;
205209
case 't':
206210
replace_chars_stdout = true;
@@ -261,22 +265,77 @@ int main(int argc, char **argv)
261265
}
262266

263267
if (target_refers_to_dom0(domname)) {
264-
if (request_id == NULL) {
265-
fprintf(stderr, "ERROR: when target domain is 'dom0', -c must be specified\n");
266-
usage(argv[0], 1);
267-
}
268-
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident) - 1);
269-
svc_params.ident[sizeof(svc_params.ident) - 1] = '\0';
270-
if (src_domain_name == NULL) {
271-
LOG(ERROR, "internal error: src_domain_name should not be NULL here");
272-
abort();
268+
if (request_id != NULL) {
269+
if (request_id[0] == '\0') {
270+
warnx("ERROR: request ID cannot be empty");
271+
usage(argv[0], 1);
272+
}
273+
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident) - 1);
274+
svc_params.ident[sizeof(svc_params.ident) - 1] = '\0';
275+
if (src_domain_name == NULL) {
276+
LOG(ERROR, "internal error: src_domain_name should not be NULL here");
277+
abort();
278+
}
279+
rc = run_qrexec_to_dom0(&svc_params,
280+
src_domain_id,
281+
src_domain_name,
282+
remote_cmdline,
283+
connection_timeout,
284+
exit_with_code);
285+
} else {
286+
/* dom0 -> dom0 fake service call */
287+
assert(src_domain_id == 0);
288+
if (local_cmdline != NULL) {
289+
warnx("dom0 -> dom0 qrexec calls with LOCAL_COMMAND not yet implemented");
290+
errx(QREXEC_EXIT_PROBLEM, "please file an issue if you need this");
291+
}
292+
/*
293+
* Normally calls to dom0 omit the username, but in this case
294+
* that would require the caller to pass the user if and only if the target is _not_
295+
* dom0, and that's annoying. In the past, qrexec-client was called by qrexec-daemon
296+
* which got it right, but now the main caller of qrexec-client is Python scripts
297+
* that don't have access to the C target_refers_to_dom0() function.
298+
* Therefore, parse the username and fail if it is not "DEFAULT".
299+
*/
300+
#define DEFAULT_USER "DEFAULT"
301+
if (strncmp(remote_cmdline, DEFAULT_USER ":", sizeof(DEFAULT_USER)) != 0) {
302+
errx(QREXEC_EXIT_PROBLEM, "dom0 -> dom0 commands must be prefixed with " DEFAULT_USER ":");
303+
}
304+
remote_cmdline += sizeof(DEFAULT_USER);
305+
struct qrexec_parsed_command *command = parse_qubes_rpc_command(remote_cmdline, false);
306+
int prepare_ret;
307+
char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN];
308+
struct buffer buf = { .data = file_path, .buflen = (int)sizeof(file_path) };
309+
if (command == NULL) {
310+
prepare_ret = -2;
311+
} else if (command->service_descriptor == NULL) {
312+
LOG(ERROR, "For dom0 -> dom0 commands, only proper qrexec calls are allowed");
313+
prepare_ret = -2;
314+
} else if (!wait_for_session_maybe(command)) {
315+
LOG(ERROR, "Cannot load service configuration, or forking process failed");
316+
prepare_ret = -2;
317+
} else {
318+
prepare_ret = find_qrexec_service(command, NULL, NULL, &buf);
319+
}
320+
switch (prepare_ret) {
321+
case -2:
322+
rc = QREXEC_EXIT_PROBLEM;
323+
break;
324+
case -1:
325+
rc = QREXEC_EXIT_SERVICE_NOT_FOUND;
326+
break;
327+
case 0:
328+
assert(command->username == NULL);
329+
assert(command->command);
330+
/* qrexec-client is always in a login session. */
331+
exec_qubes_rpc2(buf.data, command->command, environ, false);
332+
/* not reached */
333+
default:
334+
assert(false);
335+
rc = QREXEC_EXIT_PROBLEM;
336+
break;
337+
}
273338
}
274-
rc = run_qrexec_to_dom0(&svc_params,
275-
src_domain_id,
276-
src_domain_name,
277-
remote_cmdline,
278-
connection_timeout,
279-
exit_with_code);
280339
} else {
281340
if (request_id) {
282341
bool const use_uuid = strncmp(domname, "uuid:", 5) == 0;

daemon/qrexec-daemon-common.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ static void sigchld_handler(int x __attribute__((__unused__)))
364364
}
365365

366366
/* See also qrexec-agent.c:wait_for_session_maybe() */
367-
static bool wait_for_session_maybe(struct qrexec_parsed_command *cmd)
367+
bool wait_for_session_maybe(struct qrexec_parsed_command *cmd)
368368
{
369369
pid_t pid;
370370
int status;

daemon/qrexec-daemon-common.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,3 +147,7 @@ bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
147147
extern int local_stdin_fd;
148148
__attribute__((warn_unused_result))
149149
bool target_refers_to_dom0(const char *target);
150+
151+
/** Wait for a session if needed. */
152+
__attribute__((warn_unused_result))
153+
bool wait_for_session_maybe(struct qrexec_parsed_command *cmd);

libqrexec/exec.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ static bool should_strip_env_var(const char *var)
6262

6363
void exec_qubes_rpc2(const char *program, const char *cmd, char *const envp[],
6464
bool use_shell) {
65+
assert(cmd);
6566
/* avoid calling RPC service through shell */
6667
char *prog_copy;
6768
char *tok, *savetok;
@@ -743,7 +744,9 @@ int find_qrexec_service(
743744
const char *qrexec_service_path = getenv("QREXEC_SERVICE_PATH");
744745
if (!qrexec_service_path)
745746
qrexec_service_path = QREXEC_SERVICE_PATH;
746-
*socket_fd = -1;
747+
if (socket_fd != NULL) {
748+
*socket_fd = -1;
749+
}
747750

748751
struct stat statbuf;
749752

@@ -764,6 +767,9 @@ int find_qrexec_service(
764767

765768
if (S_ISSOCK(statbuf.st_mode)) {
766769
/* Socket-based service. */
770+
if (socket_fd == NULL) {
771+
goto bad_socket_service;
772+
}
767773
int s;
768774
if ((s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) {
769775
PERROR("socket");
@@ -784,6 +790,9 @@ int find_qrexec_service(
784790
*socket_fd = s;
785791
return 0;
786792
} else if (S_ISLNK(statbuf.st_mode)) {
793+
if (socket_fd == NULL) {
794+
goto bad_socket_service;
795+
}
787796
/* TCP-based service */
788797
assert(path_buffer->buflen >= (int)sizeof("/dev/tcp") - 1);
789798
assert(memcmp(path_buffer->data, "/dev/tcp", sizeof("/dev/tcp") - 1) == 0);
@@ -873,6 +882,11 @@ int find_qrexec_service(
873882
LOG(ERROR, "Unknown service type (not executable, not a socket): %.*s",
874883
path_buffer->buflen, path_buffer->data);
875884
return -2;
885+
bad_socket_service:
886+
LOG(ERROR, "Service %.*s is a socket or /dev/tcp symlink, but this is not "
887+
"supported for dom0 -> dom0 calls",
888+
path_buffer->buflen, path_buffer->data);
889+
return -2;
876890
}
877891

878892
int exec_wait_for_session(const char *source_domain) {

qrexec/client.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424

2525
QREXEC_CLIENT_DOM0 = "/usr/bin/qrexec-client"
2626
QREXEC_CLIENT_VM = "/usr/bin/qrexec-client-vm"
27-
RPC_MULTIPLEXER = "/usr/lib/qubes/qubes-rpc-multiplexer"
2827

2928
VERSION = None
3029

@@ -105,11 +104,6 @@ def make_command(dest, rpcname, arg):
105104
assert " " not in arg
106105
rpcname = f"{rpcname}+{arg}"
107106

108-
if VERSION == "dom0" and dest == "dom0":
109-
# Invoke qubes-rpc-multiplexer directly. This will work for non-socket
110-
# services only.
111-
return [RPC_MULTIPLEXER, rpcname, "dom0"]
112-
113107
if VERSION == "dom0":
114108
return [
115109
QREXEC_CLIENT_DOM0,

qrexec/tests/socket/daemon.py

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,7 @@ def setUp(self):
641641
def make_executable_service(self, *args):
642642
util.make_executable_service(self.tempdir, *args)
643643

644-
def start_client(self, args):
644+
def start_client(self, args, stderr=None):
645645
env = os.environ.copy()
646646
env["LD_LIBRARY_PATH"] = os.path.join(ROOT_PATH, "libqrexec")
647647
env["VCHAN_DOMAIN"] = "0"
@@ -664,6 +664,7 @@ def start_client(self, args):
664664
env=env,
665665
stdin=subprocess.PIPE,
666666
stdout=subprocess.PIPE,
667+
stderr=stderr,
667668
)
668669
self.addCleanup(self.stop_client)
669670

@@ -1020,6 +1021,64 @@ def assertExpectedStdout(
10201021
],
10211022
)
10221023

1024+
def _test_run_service_in_dom0(self, requested_target):
1025+
util.make_executable_service(
1026+
self.tempdir,
1027+
"rpc",
1028+
"qubes.Service",
1029+
"""\
1030+
#!/bin/sh -eu
1031+
read input
1032+
echo "arg: $1, remote domain: $QREXEC_REMOTE_DOMAIN, input: $input, service path: $QREXEC_SERVICE_PATH"
1033+
case $QREXEC_REQUESTED_TARGET_TYPE in
1034+
(name) echo "requested target name: $QREXEC_REQUESTED_TARGET";;
1035+
(keyword) echo "requested target keyword: $QREXEC_REQUESTED_TARGET_KEYWORD";;
1036+
esac
1037+
""",
1038+
)
1039+
requested_target_type = "name"
1040+
if requested_target.startswith("@"):
1041+
requested_target = requested_target[1:]
1042+
requested_target_type = "keyword"
1043+
1044+
cmd = f"DEFAULT:QUBESRPC qubes.Service+arg src_domain {requested_target_type} {requested_target}"
1045+
self.start_client(["-d", "dom0", cmd], stderr=subprocess.PIPE)
1046+
stdout, stderr = self.client.communicate(b"stdin data\n")
1047+
self.client.wait()
1048+
self.assertFalse(stderr, stderr)
1049+
self.assertEqual(stderr, b"")
1050+
self.assertEqual(self.client.returncode, 0)
1051+
service_path = (
1052+
":".join(
1053+
[
1054+
os.path.join(self.tempdir, "local-rpc"),
1055+
os.path.join(self.tempdir, "rpc"),
1056+
]
1057+
)
1058+
).encode("ascii", "strict")
1059+
self.assertEqual(
1060+
stdout,
1061+
b"arg: arg, remote domain: src_domain, "
1062+
b"input: stdin data, service path: %s\n"
1063+
b"requested target %s: %s\n"
1064+
% (
1065+
service_path,
1066+
requested_target_type.encode("ascii", "strict"),
1067+
requested_target.encode("ascii", "strict"),
1068+
),
1069+
)
1070+
1071+
def test_dom0_to_dom0(self):
1072+
self._test_run_service_in_dom0("@adminvm")
1073+
1074+
def test_dom0_to_dom0_v1(self):
1075+
self._test_run_service_in_dom0("dom0")
1076+
1077+
def test_dom0_to_dom0_v2(self):
1078+
self._test_run_service_in_dom0(
1079+
"uuid:00000000-0000-0000-0000-000000000000"
1080+
)
1081+
10231082
def _test_run_dom0_service_exec(self, nogui, requested_target="src_domain"):
10241083
util.make_executable_service(
10251084
self.tempdir,

0 commit comments

Comments
 (0)