Skip to content

Commit e06514b

Browse files
committed
Share qrexec-daemon VM -> VM call code with qrexec-client
This allows the code to be unit-tested, and also reduces duplication.
1 parent 0f7bf45 commit e06514b

File tree

4 files changed

+135
-132
lines changed

4 files changed

+135
-132
lines changed

daemon/qrexec-client.c

+20-50
Original file line numberDiff line numberDiff line change
@@ -143,16 +143,6 @@ static void parse_connect(char *str, char **request_id,
143143
exit(1);
144144
}
145145

146-
static size_t compute_service_length(const char *const remote_cmdline, const char *const prog_name) {
147-
const size_t service_length = strlen(remote_cmdline) + 1;
148-
if (service_length < 2 || service_length > MAX_QREXEC_CMD_LEN) {
149-
/* This is arbitrary, but it helps reduce the risk of overflows in other code */
150-
fprintf(stderr, "Bad command: command line too long or empty: length %zu\n", service_length);
151-
usage(prog_name);
152-
}
153-
return service_length;
154-
}
155-
156146
int main(int argc, char **argv)
157147
{
158148
int opt;
@@ -162,7 +152,6 @@ int main(int argc, char **argv)
162152
int data_domain;
163153
int s;
164154
bool just_exec = false;
165-
int wait_connection_end = -1;
166155
char *local_cmdline = NULL;
167156
char *remote_cmdline = NULL;
168157
char *request_id = NULL;
@@ -174,6 +163,7 @@ int main(int argc, char **argv)
174163
bool kill = false;
175164
bool replace_chars_stdout = false;
176165
bool replace_chars_stderr = false;
166+
bool wait_connection_end = false;
177167
bool exit_with_code = true;
178168
int rc = QREXEC_EXIT_PROBLEM;
179169

@@ -215,7 +205,7 @@ int main(int argc, char **argv)
215205
connection_timeout = parse_int(optarg, "connection timeout");
216206
break;
217207
case 'W':
218-
wait_connection_end = 1;
208+
wait_connection_end = true;
219209
break;
220210
case 'd' + 128:
221211
socket_dir = strdup(optarg);
@@ -259,41 +249,22 @@ int main(int argc, char **argv)
259249
connection_timeout,
260250
exit_with_code);
261251
} else {
262-
s = connect_unix_socket(domname);
263-
if (!negotiate_connection_params(s,
264-
src_domain_id,
265-
just_exec ? MSG_JUST_EXEC : MSG_EXEC_CMDLINE,
266-
remote_cmdline,
267-
compute_service_length(remote_cmdline, argv[0]),
268-
&data_domain,
269-
&data_port)) {
270-
goto cleanup;
271-
}
272252
if (request_id) {
273-
if (wait_connection_end != -1) {
274-
/* save socket fd, 's' will be reused for the other qrexec-daemon
275-
* connection */
276-
wait_connection_end = s;
277-
} else {
278-
close(s);
279-
}
280-
s = connect_unix_socket_by_id(src_domain_id);
281-
if (s == -1) {
282-
goto cleanup;
283-
}
284-
if (!send_service_connect(s, request_id, data_domain, data_port)) {
253+
rc = qrexec_execute_vm(domname, false, src_domain_id,
254+
remote_cmdline, strlen(remote_cmdline) + 1,
255+
request_id, just_exec,
256+
wait_connection_end) ? 0 : 137;
257+
} else {
258+
s = connect_unix_socket(domname);
259+
if (!negotiate_connection_params(s,
260+
src_domain_id,
261+
just_exec ? MSG_JUST_EXEC : MSG_EXEC_CMDLINE,
262+
remote_cmdline,
263+
strlen(remote_cmdline) + 1,
264+
&data_domain,
265+
&data_port)) {
285266
goto cleanup;
286267
}
287-
close(s);
288-
if (wait_connection_end != -1) {
289-
/* wait for EOF */
290-
struct pollfd fds[1] = {
291-
{ .fd = wait_connection_end, .events = POLLIN | POLLHUP, .revents = 0 },
292-
};
293-
poll(fds, 1, -1);
294-
}
295-
rc = 0;
296-
} else {
297268
set_remote_domain(domname);
298269
struct buffer stdin_buffer;
299270
buffer_init(&stdin_buffer);
@@ -334,13 +305,12 @@ int main(int argc, char **argv)
334305
.replace_chars_stderr = replace_chars_stderr,
335306
};
336307
rc = handshake_and_go(&params);
337-
}
338-
}
339-
340308
cleanup:
341-
if (kill && domname) {
342-
size_t l;
343-
qubesd_call(domname, "admin.vm.Kill", "", &l);
309+
if (kill && domname) {
310+
size_t l;
311+
qubesd_call(domname, "admin.vm.Kill", "", &l);
312+
}
313+
}
344314
}
345315

346316
return rc;

daemon/qrexec-daemon-common.c

+100
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,106 @@ int handle_daemon_handshake(int fd)
9090
return 0;
9191
}
9292

93+
bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
94+
const char *cmd, size_t const service_length,
95+
const char *request_id, bool just_exec,
96+
bool wait_connection_end)
97+
{
98+
if (service_length < 2 || (size_t)service_length > MAX_QREXEC_CMD_LEN) {
99+
/* This is arbitrary, but it helps reduce the risk of overflows in other code */
100+
LOG(ERROR, "Bad command: command line too long or empty: length %zu\n",
101+
service_length);
102+
return false;
103+
}
104+
bool rc = false;
105+
bool disposable = strncmp("@dispvm:", target, sizeof("@dispvm:") - 1) == 0;
106+
char *buf = NULL;
107+
size_t resp_len;
108+
if (disposable) {
109+
if (!autostart) {
110+
// Otherwise, we create a disposable VM, do not start it, and
111+
// time out connecting to it.
112+
LOG(ERROR, "Target %s with autostart=False makes no sense", target);
113+
return false;
114+
}
115+
if (just_exec) {
116+
// Otherwise, we kill the VM immediately after starting it.
117+
LOG(ERROR, "Target %s with MSG_JUST_EXEC makes no sense", target);
118+
return false;
119+
}
120+
// Otherwise, we kill the VM immediately after starting it.
121+
wait_connection_end = true;
122+
buf = qubesd_call(target + 8, "admin.vm.CreateDisposable", "", &resp_len);
123+
if (buf == NULL) // error already printed by qubesd_call
124+
return false;
125+
if (memcmp(buf, "0", 2) == 0) {
126+
/* we exit later so memory leaks do not matter */
127+
target = buf + 2;
128+
} else {
129+
if (memcmp(buf, "2", 2) == 0) {
130+
LOG(ERROR, "qubesd could not create disposable VM: %s", buf + 2);
131+
} else {
132+
LOG(ERROR, "invalid response to admin.vm.CreateDisposable");
133+
}
134+
free(buf);
135+
return false;
136+
}
137+
}
138+
if (autostart) {
139+
buf = qubesd_call(target, "admin.vm.Start", "", &resp_len);
140+
if (buf == NULL) // error already printed by qubesd_call
141+
return false;
142+
if (!((memcmp(buf, "0", 2) == 0) ||
143+
(resp_len >= 24 && memcmp(buf, "2\0QubesVMNotHaltedError", 24) == 0))) {
144+
if (memcmp(buf, "2", 2) == 0) {
145+
LOG(ERROR, "qubesd could not start VM %s: %s", target, buf + 2);
146+
} else {
147+
LOG(ERROR, "invalid response to admin.vm.Start");
148+
}
149+
free(buf);
150+
return false;
151+
}
152+
free(buf);
153+
}
154+
int s = connect_unix_socket(target);
155+
if (s == -1)
156+
goto kill;
157+
int data_domain;
158+
int data_port;
159+
if (!negotiate_connection_params(s,
160+
remote_domain_id,
161+
just_exec ? MSG_JUST_EXEC : MSG_EXEC_CMDLINE,
162+
cmd,
163+
service_length,
164+
&data_domain,
165+
&data_port))
166+
goto kill;
167+
int wait_connection_fd = -1;
168+
if (wait_connection_end) {
169+
/* save socket fd, 's' will be reused for the other qrexec-daemon
170+
* connection */
171+
wait_connection_fd = s;
172+
} else {
173+
close(s);
174+
}
175+
176+
s = connect_unix_socket_by_id((unsigned)remote_domain_id);
177+
rc = send_service_connect(s, request_id, data_domain, data_port);
178+
close(s);
179+
if (wait_connection_end) {
180+
/* wait for EOF */
181+
struct pollfd fds[1] = {
182+
{ .fd = wait_connection_fd, .events = POLLIN | POLLHUP, .revents = 0 },
183+
};
184+
poll(fds, 1, -1);
185+
size_t l;
186+
kill:
187+
if (disposable)
188+
qubesd_call(target, "admin.vm.Kill", "", &l);
189+
}
190+
return rc;
191+
}
192+
93193
int connect_unix_socket_by_id(unsigned int domid)
94194
{
95195
char id_str[11];

daemon/qrexec-daemon-common.h

+4
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,7 @@ __attribute__((warn_unused_result))
3838
int handle_agent_handshake(libvchan_t *vchan, bool remote_send_first);
3939
__attribute__((warn_unused_result))
4040
int prepare_local_fds(struct qrexec_parsed_command *command, struct buffer *stdin_buffer);
41+
__attribute__((warn_unused_result))
42+
bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
43+
const char *cmd, size_t service_length, const char *request_id,
44+
bool just_exec, bool wait_connection_end);

daemon/qrexec-daemon.c

+11-82
Original file line numberDiff line numberDiff line change
@@ -1095,15 +1095,6 @@ static enum policy_response connect_daemon_socket(
10951095
}
10961096
}
10971097

1098-
static size_t compute_service_length(const char *const remote_cmdline) {
1099-
const size_t service_length = strlen(remote_cmdline) + 1;
1100-
if (service_length < 2 || service_length > MAX_QREXEC_CMD_LEN) {
1101-
/* This is arbitrary, but it helps reduce the risk of overflows in other code */
1102-
errx(1, "Bad command: command line too long or empty: length %zu\n", service_length);
1103-
}
1104-
return service_length;
1105-
}
1106-
11071098
/* called from do_fork_exec */
11081099
static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused)))
11091100
{
@@ -1139,8 +1130,6 @@ _Noreturn static void handle_execute_service_child(
11391130
/* Replace the target domain with the version normalized by the policy engine */
11401131
target_domain = requested_target;
11411132
char *cmd = NULL;
1142-
bool disposable = false;
1143-
size_t resp_len;
11441133

11451134
/*
11461135
* If there was no service argument, pretend that an empty argument was
@@ -1175,78 +1164,18 @@ _Noreturn static void handle_execute_service_child(
11751164
5 /* 5 second timeout */,
11761165
false /* return 0 not remote status code */));
11771166
} else {
1178-
char *buf;
1179-
if (strncmp("@dispvm:", target, sizeof("@dispvm:") - 1) == 0) {
1180-
disposable = true;
1181-
buf = qubesd_call(target + 8, "admin.vm.CreateDisposable", "", &resp_len);
1182-
if (!buf) // error already printed by qubesd_call
1183-
daemon__exit(QREXEC_EXIT_PROBLEM);
1184-
if (memcmp(buf, "0", 2) == 0) {
1185-
/* we exec later so memory leaks do not matter */
1186-
target = buf + 2;
1187-
} else {
1188-
if (memcmp(buf, "2", 2) == 0) {
1189-
LOG(ERROR, "qubesd could not create disposable VM: %s", buf + 2);
1190-
} else {
1191-
LOG(ERROR, "invalid response to admin.vm.CreateDisposable");
1192-
}
1193-
daemon__exit(QREXEC_EXIT_PROBLEM);
1194-
}
1195-
}
1196-
if (asprintf(&cmd, "%s:QUBESRPC %s%s %s",
1197-
user,
1198-
service_name,
1199-
trailer,
1200-
remote_domain_name) <= 0)
1167+
int service_length = asprintf(&cmd, "%s:QUBESRPC %s%s %s",
1168+
user,
1169+
service_name,
1170+
trailer,
1171+
remote_domain_name);
1172+
if (service_length < 0)
12011173
daemon__exit(QREXEC_EXIT_PROBLEM);
1202-
if (autostart) {
1203-
buf = qubesd_call(target, "admin.vm.Start", "", &resp_len);
1204-
if (!buf) // error already printed by qubesd_call
1205-
daemon__exit(QREXEC_EXIT_PROBLEM);
1206-
if (!((memcmp(buf, "0", 2) == 0) ||
1207-
(resp_len >= 24 && memcmp(buf, "2\0QubesVMNotHaltedError", 24) == 0))) {
1208-
if (memcmp(buf, "2", 2) == 0) {
1209-
LOG(ERROR, "qubesd could not start VM %s: %s", target, buf + 2);
1210-
} else {
1211-
LOG(ERROR, "invalid response to admin.vm.Start");
1212-
}
1213-
daemon__exit(QREXEC_EXIT_PROBLEM);
1214-
}
1215-
free(buf);
1216-
}
1217-
int s = connect_unix_socket(target);
1218-
int data_domain;
1219-
int data_port;
1220-
int rc = QREXEC_EXIT_PROBLEM;
1221-
if (!negotiate_connection_params(s,
1222-
remote_domain_id,
1223-
MSG_EXEC_CMDLINE,
1224-
cmd,
1225-
compute_service_length(cmd),
1226-
&data_domain,
1227-
&data_port))
1228-
daemon__exit(QREXEC_EXIT_PROBLEM);
1229-
int wait_connection_end = -1;
1230-
if (disposable) {
1231-
wait_connection_end = s;
1232-
} else {
1233-
close(s);
1234-
}
1235-
1236-
s = connect_unix_socket_by_id((unsigned)remote_domain_id);
1237-
if (send_service_connect(s, request_id->ident, data_domain, data_port))
1238-
rc = 0;
1239-
close(s);
1240-
if (wait_connection_end != -1) {
1241-
/* wait for EOF */
1242-
struct pollfd fds[1] = {
1243-
{ .fd = wait_connection_end, .events = POLLIN | POLLHUP, .revents = 0 },
1244-
};
1245-
poll(fds, 1, -1);
1246-
size_t l;
1247-
qubesd_call(target, "admin.vm.Kill", "", &l);
1248-
}
1249-
daemon__exit(rc);
1174+
daemon__exit(qrexec_execute_vm(target, autostart, remote_domain_id,
1175+
cmd,
1176+
(size_t)service_length + 1,
1177+
request_id->ident, false, false)
1178+
? 0 : QREXEC_EXIT_PROBLEM);
12501179
}
12511180
}
12521181

0 commit comments

Comments
 (0)