Skip to content

Commit 07ec597

Browse files
committed
Cleanly terminate connections if command or config is invalid
Send exit status 127 in this case, as if the command could not be found. This prevents the caller from hanging, and allows this situation to be tested. Fixes: QubesOS/qubes-issues#9073
1 parent 2080749 commit 07ec597

File tree

3 files changed

+73
-16
lines changed

3 files changed

+73
-16
lines changed

agent/qrexec-agent-data.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -134,10 +134,12 @@ static int handle_just_exec(struct qrexec_parsed_command *cmd)
134134
{
135135
int fdn, pid;
136136

137+
if (cmd == NULL)
138+
return 127;
137139
switch (pid = fork()) {
138140
case -1:
139141
PERROR("fork");
140-
return -1;
142+
return 127;
141143
case 0:
142144
fdn = open("/dev/null", O_RDWR);
143145
fix_fds(fdn, fdn, fdn);
@@ -271,7 +273,8 @@ static int handle_new_process_common(
271273
return 0;
272274
case MSG_EXEC_CMDLINE:
273275
buffer_init(&stdin_buf);
274-
if (execute_parsed_qubes_rpc_command(cmd, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &stdin_buf) < 0) {
276+
if (cmd == NULL ||
277+
execute_parsed_qubes_rpc_command(cmd, &pid, &stdin_fd, &stdout_fd, &stderr_fd, &stdin_buf) < 0) {
275278
struct msg_header hdr = {
276279
.type = MSG_DATA_STDOUT,
277280
.len = 0,

agent/qrexec-agent.c

+27-14
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ static const char *fork_server_path = QREXEC_FORK_SERVER_SOCKET;
8585
static void handle_server_exec_request_do(int type, int connect_domain, int connect_port,
8686
struct qrexec_parsed_command *cmd,
8787
char *cmdline);
88+
static void terminate_connection(uint32_t domain, uint32_t port);
8889

8990
const bool qrexec_is_fork_server = false;
9091

@@ -587,14 +588,16 @@ static void handle_server_exec_request_init(struct msg_header *hdr)
587588
cmd = parse_qubes_rpc_command(buf, true);
588589
if (cmd == NULL) {
589590
LOG(ERROR, "Could not parse command line: %s", buf);
590-
return;
591+
goto doit;
591592
}
592593

593594
/* load service config only for service requests */
594595
if (cmd->service_descriptor) {
595596
if (load_service_config_v2(cmd) < 0) {
596597
LOG(ERROR, "Could not load config for command %s", buf);
597-
return;
598+
destroy_qrexec_parsed_command(cmd);
599+
cmd = NULL;
600+
goto doit;
598601
}
599602

600603
/* "nogui:" prefix has priority */
@@ -620,6 +623,7 @@ static void handle_server_exec_request_init(struct msg_header *hdr)
620623
}
621624
}
622625

626+
doit:
623627
handle_server_exec_request_do(hdr->type, params.connect_domain, params.connect_port, cmd, buf);
624628
destroy_qrexec_parsed_command(cmd);
625629
free(buf);
@@ -663,7 +667,7 @@ static void handle_server_exec_request_do(int type,
663667
return;
664668
}
665669

666-
if (!cmd->nogui) {
670+
if (cmd != NULL && !cmd->nogui) {
667671
/* try fork server */
668672
int child_socket = try_fork_server(type,
669673
params.connect_domain, params.connect_port,
@@ -757,20 +761,29 @@ static int find_connection(int pid)
757761
}
758762

759763
static void release_connection(int id) {
760-
struct msg_header hdr;
761-
struct exec_params params;
762-
763-
hdr.type = MSG_CONNECTION_TERMINATED;
764-
hdr.len = sizeof(struct exec_params);
765-
params.connect_domain = connection_info[id].connect_domain;
766-
params.connect_port = connection_info[id].connect_port;
767-
if (libvchan_send(ctrl_vchan, &hdr, sizeof(hdr)) != sizeof(hdr))
768-
handle_vchan_error("send (MSG_CONNECTION_TERMINATED hdr)");
769-
if (libvchan_send(ctrl_vchan, &params, sizeof(params)) != sizeof(params))
770-
handle_vchan_error("send (MSG_CONNECTION_TERMINATED data)");
764+
terminate_connection(connection_info[id].connect_domain,
765+
connection_info[id].connect_port);
771766
connection_info[id].pid = 0;
772767
}
773768

769+
static void terminate_connection(uint32_t domain, uint32_t port) {
770+
struct {
771+
struct msg_header hdr;
772+
struct exec_params params;
773+
} data = {
774+
.hdr = {
775+
.type = MSG_CONNECTION_TERMINATED,
776+
.len = sizeof(struct exec_params),
777+
},
778+
.params = {
779+
.connect_domain = domain,
780+
.connect_port = port,
781+
},
782+
};
783+
if (libvchan_send(ctrl_vchan, &data, sizeof(data)) != sizeof(data))
784+
handle_vchan_error("send (MSG_CONNECTION_TERMINATED)");
785+
}
786+
774787
static void reap_children(void)
775788
{
776789
int status;

qrexec/tests/socket/agent.py

+41
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,47 @@ def test_exec_service_fail(self):
453453
)
454454
self.check_dom0(dom0)
455455

456+
def exec_service_with_invalid_config(self, invalid_config):
457+
util.make_executable_service(
458+
self.tempdir,
459+
"rpc",
460+
"qubes.Service",
461+
"""\
462+
#!/bin/sh
463+
echo "arg: $1, remote domain: $QREXEC_REMOTE_DOMAIN"
464+
""",
465+
)
466+
with open(
467+
os.path.join(self.tempdir, "rpc-config", "qubes.Service+arg"), "w"
468+
) as f:
469+
f.write(invalid_config)
470+
target, dom0 = self.execute_qubesrpc("qubes.Service+arg", "domX")
471+
messages = target.recv_all_messages()
472+
self.assertListEqual(
473+
util.sort_messages(messages),
474+
[
475+
(qrexec.MSG_DATA_STDOUT, b""),
476+
(qrexec.MSG_DATA_STDERR, b""),
477+
(qrexec.MSG_DATA_EXIT_CODE, b"\177\0\0\0"),
478+
],
479+
)
480+
self.check_dom0(dom0)
481+
482+
def test_exec_service_with_invalid_config_1(self):
483+
self.exec_service_with_invalid_config("wait-for-session = 00\n")
484+
485+
def test_exec_service_with_invalid_config_2(self):
486+
self.exec_service_with_invalid_config("wait-for-session = 01\n")
487+
488+
def test_exec_service_with_invalid_config_3(self):
489+
self.exec_service_with_invalid_config("wait-for-session = \n")
490+
491+
def test_exec_service_with_invalid_config_4(self):
492+
self.exec_service_with_invalid_config("wait-for-session = \"a\"\n")
493+
494+
def test_exec_service_with_invalid_config_5(self):
495+
self.exec_service_with_invalid_config("wait-for-session\n")
496+
456497
def test_exec_service_with_arg(self):
457498
self.make_executable_service(
458499
"local-rpc",

0 commit comments

Comments
 (0)