Skip to content

Commit 578de59

Browse files
committed
Implement UUID support in qrexec
This allows using UUIDs in qrexec policy, using the syntax uuid:VM_UUID. This works anywhere a VM name is expected. Since ':' is not allowed in VM names, there is no ambiguity. This requires the corresponding change to qubes-core-admin so that qubesd supports UUIDs in the admin and internal APIs. Fixes: QubesOS/qubes-issues#8510
1 parent 45527be commit 578de59

16 files changed

+615
-291
lines changed

daemon/qrexec-client.c

+3-2
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ int main(int argc, char **argv)
259259
usage(argv[0]);
260260
}
261261

262-
if (strcmp(domname, "dom0") == 0 || strcmp(domname, "@adminvm") == 0) {
262+
if (target_refers_to_dom0(domname)) {
263263
if (request_id == NULL) {
264264
fprintf(stderr, "ERROR: when target domain is 'dom0', -c must be specified\n");
265265
usage(argv[0]);
@@ -278,10 +278,11 @@ int main(int argc, char **argv)
278278
exit_with_code);
279279
} else {
280280
if (request_id) {
281+
bool const use_uuid = strncmp(domname, "uuid:", 5) == 0;
281282
rc = qrexec_execute_vm(domname, false, src_domain_id,
282283
remote_cmdline, strlen(remote_cmdline) + 1,
283284
request_id, just_exec,
284-
wait_connection_end) ? 0 : 137;
285+
wait_connection_end, use_uuid) ? 0 : 137;
285286
} else {
286287
s = connect_unix_socket(domname);
287288
if (!negotiate_connection_params(s,

daemon/qrexec-daemon-common.c

+34-3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include "qrexec-daemon-common.h"
1212

1313
const char *socket_dir = QREXEC_DAEMON_SOCKET_DIR;
14+
#define QREXEC_DISPVM_PREFIX "@dispvm:"
15+
#define QREXEC_DISPVM_PREFIX_SIZE (sizeof QREXEC_DISPVM_PREFIX - 1)
1416

1517
/* ask the daemon to allocate vchan port */
1618
bool negotiate_connection_params(int s, int other_domid, unsigned type,
@@ -49,6 +51,20 @@ bool negotiate_connection_params(int s, int other_domid, unsigned type,
4951
return true;
5052
}
5153

54+
bool target_refers_to_dom0(const char *target)
55+
{
56+
switch (target[0]) {
57+
case '@':
58+
return strcmp(target + 1, "adminvm") == 0;
59+
case 'd':
60+
return strcmp(target + 1, "om0") == 0;
61+
case 'u':
62+
return strcmp(target + 1, "uid:00000000-0000-0000-0000-000000000000") == 0;
63+
default:
64+
return false;
65+
}
66+
}
67+
5268
int handle_daemon_handshake(int fd)
5369
{
5470
struct msg_header hdr;
@@ -93,7 +109,7 @@ int handle_daemon_handshake(int fd)
93109
bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
94110
const char *cmd, size_t const service_length,
95111
const char *request_id, bool just_exec,
96-
bool wait_connection_end)
112+
bool wait_connection_end, bool use_uuid)
97113
{
98114
if (service_length < 2 || (size_t)service_length > MAX_QREXEC_CMD_LEN) {
99115
/* This is arbitrary, but it helps reduce the risk of overflows in other code */
@@ -119,11 +135,26 @@ bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
119135
}
120136
// Otherwise, we kill the VM immediately after starting it.
121137
wait_connection_end = true;
122-
buf = qubesd_call(target + 8, "admin.vm.CreateDisposable", "", &resp_len);
138+
buf = qubesd_call2(target + QREXEC_DISPVM_PREFIX_SIZE,
139+
"admin.vm.CreateDisposable", "",
140+
"uuid", use_uuid ? 4 : 0, &resp_len);
123141
if (buf == NULL) // error already printed by qubesd_call
124142
return false;
125143
if (memcmp(buf, "0", 2) == 0) {
126-
/* we exit later so memory leaks do not matter */
144+
if (strlen(buf + 2) != resp_len - 2) {
145+
LOG(ERROR, "NUL byte in qubesd response");
146+
return false;
147+
}
148+
if (use_uuid) {
149+
if (resp_len != sizeof("uuid:00000000-0000-0000-0000-000000000000") + 1) {
150+
LOG(ERROR, "invalid UUID length");
151+
return false;
152+
}
153+
if (memcmp(buf + 2, "uuid:", 5) != 0) {
154+
LOG(ERROR, "invalid UUID target %s", buf + 2);
155+
return false;
156+
}
157+
}
127158
target = buf + 2;
128159
} else {
129160
if (memcmp(buf, "2", 2) == 0) {

daemon/qrexec-daemon-common.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ int prepare_local_fds(struct qrexec_parsed_command *command, struct buffer *stdi
142142
__attribute__((warn_unused_result))
143143
bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
144144
const char *cmd, size_t service_length, const char *request_id,
145-
bool just_exec, bool wait_connection_end);
145+
bool just_exec, bool wait_connection_end, bool use_uuid);
146146
/** FD for stdout of remote process */
147147
extern int local_stdin_fd;
148+
__attribute__((warn_unused_result))
149+
bool target_refers_to_dom0(const char *target);

daemon/qrexec-daemon.c

+76-42
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*/
2121

2222
#include <inttypes.h>
23+
#include <stdarg.h>
2324
#include <stdio.h>
2425
#include <stdlib.h>
2526
#include <sys/syscall.h>
@@ -135,24 +136,44 @@ static
135136
#endif
136137
int protocol_version;
137138

138-
static char *remote_domain_name; // guess what
139+
static const char *remote_domain_name; // guess what
140+
static const char *remote_domain_uuid;
139141
static int remote_domain_id;
140142

143+
static void unlink_or_exit(const char *path)
144+
{
145+
int v = unlink(path);
146+
if (v != 0 && !(v == -1 && errno == ENOENT))
147+
err(1, "unlink(%s)", path);
148+
}
149+
150+
static char __attribute__((format(printf, 1, 2))) *xasprintf(const char *fmt, ...)
151+
{
152+
va_list x;
153+
char *res;
154+
va_start(x, fmt);
155+
int r = vasprintf(&res, fmt, x);
156+
va_end(x);
157+
if (r < 0)
158+
abort();
159+
return res;
160+
}
161+
141162
static void unlink_qrexec_socket(void)
142163
{
143-
char *socket_address;
144-
char *link_to_socket_name;
145-
146-
if (asprintf(&socket_address, "%s/qrexec.%d", socket_dir, remote_domain_id) < 0)
147-
err(1, "asprintf");
148-
if (unlink(socket_address) != 0 && errno != ENOENT)
149-
err(1, "unlink(%s)", socket_address);
150-
free(socket_address);
151-
if (asprintf(&link_to_socket_name, "%s/qrexec.%s", socket_dir, remote_domain_name) < 0)
152-
err(1, "asprintf");
153-
if (unlink(link_to_socket_name) != 0 && errno != ENOENT)
154-
err(1, "unlink(%s)", link_to_socket_name);
155-
free(link_to_socket_name);
164+
char *socket_name;
165+
const char *p[2] = {remote_domain_name, remote_domain_uuid};
166+
int i;
167+
168+
for (i = 0; i < 2; ++i) {
169+
char *link_to_socket_name = xasprintf("qrexec.%s%s", i > 0 ? "uuid:" : "", p[i]);
170+
unlink_or_exit(link_to_socket_name);
171+
free(link_to_socket_name);
172+
}
173+
if (asprintf(&socket_name, "qrexec.%d", remote_domain_id) < 0)
174+
abort();
175+
unlink_or_exit(socket_name);
176+
free(socket_name);
156177
}
157178

158179
static void handle_vchan_error(const char *op)
@@ -161,27 +182,25 @@ static void handle_vchan_error(const char *op)
161182
exit(1);
162183
}
163184

164-
165-
static int create_qrexec_socket(int domid, const char *domname)
185+
static int create_qrexec_socket(int domid, const char *domname, const char *domuuid)
166186
{
167-
char socket_address[40];
168-
char *link_to_socket_name;
169-
170-
snprintf(socket_address, sizeof(socket_address),
171-
"%s/qrexec.%d", socket_dir, domid);
172-
if (asprintf(&link_to_socket_name,
173-
"%s/qrexec.%s", socket_dir, domname) < 0)
174-
err(1, "asprintf");
175-
unlink(link_to_socket_name);
176-
177187
/* When running as root, make the socket accessible; perms on /var/run/qubes still apply */
178188
umask(0);
179-
if (symlink(socket_address, link_to_socket_name)) {
180-
PERROR("symlink(%s,%s)", socket_address, link_to_socket_name);
189+
190+
const char *p[2] = { domuuid, domname };
191+
char *socket_address = xasprintf("qrexec.%d", domid);
192+
for (int i = 0; i < 2; ++i) {
193+
if (p[i] == NULL)
194+
continue;
195+
char *link_to_socket_name = xasprintf("qrexec.%s%s", i ? "" : "uuid:", p[i]);
196+
unlink_or_exit(link_to_socket_name);
197+
if (symlink(socket_address, link_to_socket_name)) {
198+
PERROR("symlink(%s,%s)", socket_address, link_to_socket_name);
199+
}
200+
free(link_to_socket_name);
181201
}
182202
int fd = get_server_socket(socket_address);
183203
umask(0077);
184-
free(link_to_socket_name);
185204
return fd;
186205
}
187206

@@ -407,7 +426,7 @@ static void init(int xid, bool opt_direct)
407426

408427
atexit(unlink_qrexec_socket);
409428
qrexec_daemon_unix_socket_fd =
410-
create_qrexec_socket(xid, remote_domain_name);
429+
create_qrexec_socket(xid, remote_domain_name, remote_domain_uuid);
411430

412431
struct sigaction sigchld_action = {
413432
.sa_handler = signal_handler,
@@ -856,11 +875,12 @@ static int parse_policy_response(
856875
size_t result_bytes,
857876
bool daemon,
858877
char **user,
878+
char **target_uuid,
859879
char **target,
860880
char **requested_target,
861881
int *autostart
862882
) {
863-
*user = *target = *requested_target = NULL;
883+
*user = *target_uuid = *target = *requested_target = NULL;
864884
int result = *autostart = -1;
865885
const char *const msg = daemon ? "qrexec-policy-daemon" : "qrexec-policy-exec";
866886
// At least one byte must be returned
@@ -905,6 +925,12 @@ static int parse_policy_response(
905925
*target = strdup(current_response + (sizeof("target=") - 1));
906926
if (*target == NULL)
907927
abort();
928+
} else if (!strncmp(current_response, "target_uuid=", sizeof("target_uuid=") - 1)) {
929+
if (*target_uuid != NULL)
930+
goto bad_response;
931+
*target_uuid = strdup(current_response + 12);
932+
if (*target_uuid == NULL)
933+
abort();
908934
} else if (!strncmp(current_response, "autostart=", sizeof("autostart=") - 1)) {
909935
current_response += sizeof("autostart=") - 1;
910936
if (*autostart != -1)
@@ -1001,6 +1027,7 @@ static enum policy_response connect_daemon_socket(
10011027
const char *target_domain,
10021028
const char *service_name,
10031029
char **user,
1030+
char **target_uuid,
10041031
char **target,
10051032
char **requested_target,
10061033
int *autostart
@@ -1028,7 +1055,7 @@ static enum policy_response connect_daemon_socket(
10281055
size_t result_bytes;
10291056
// this closes the socket
10301057
char *result = qubes_read_all_to_malloc(daemon_socket, 64, 4096, &result_bytes);
1031-
int policy_result = parse_policy_response(result, result_bytes, true, user, target, requested_target, autostart);
1058+
int policy_result = parse_policy_response(result, result_bytes, true, user, target_uuid, target, requested_target, autostart);
10321059
if (policy_result != RESPONSE_MALFORMED) {
10331060
// This leaks 'result', but as the code execs later anyway this isn't a problem.
10341061
// 'result' cannot be freed as 'user', 'target', and 'requested_target' point into
@@ -1099,7 +1126,7 @@ static enum policy_response connect_daemon_socket(
10991126
// This leaks 'result', but as the code execs later anyway this isn't a problem.
11001127
// 'result' cannot be freed as 'user', 'target', and 'requested_target' point into
11011128
// the same buffer.
1102-
return parse_policy_response(result, result_bytes, true, user, target, requested_target, autostart);
1129+
return parse_policy_response(result, result_bytes, true, user, target_uuid, target, requested_target, autostart);
11031130
}
11041131
}
11051132

@@ -1132,11 +1159,11 @@ _Noreturn static void handle_execute_service_child(
11321159
for (i = 3; i < MAX_FDS; i++)
11331160
close(i);
11341161

1135-
char *user, *target, *requested_target;
1136-
int autostart;
1162+
char *user = NULL, *target = NULL, *requested_target = NULL, *target_uuid = NULL;
1163+
int autostart = -1;
11371164
int policy_response =
11381165
connect_daemon_socket(remote_domain_name, target_domain, service_name,
1139-
&user, &target, &requested_target, &autostart);
1166+
&user, &target_uuid, &target, &requested_target, &autostart);
11401167

11411168
if (policy_response != RESPONSE_ALLOW)
11421169
daemon__exit(QREXEC_EXIT_REQUEST_REFUSED);
@@ -1152,8 +1179,7 @@ _Noreturn static void handle_execute_service_child(
11521179
const char *const trailer = strchr(service_name, '+') ? "" : "+";
11531180

11541181
/* Check if the target is dom0, which requires special handling. */
1155-
bool target_is_dom0 = strcmp(target, "@adminvm") == 0 ||
1156-
strcmp(target, "dom0") == 0;
1182+
bool target_is_dom0 = target_refers_to_dom0(target);
11571183
if (target_is_dom0) {
11581184
char *type;
11591185
bool target_is_keyword = target_domain[0] == '@';
@@ -1178,17 +1204,21 @@ _Noreturn static void handle_execute_service_child(
11781204
5 /* 5 second timeout */,
11791205
false /* return 0 not remote status code */));
11801206
} else {
1207+
bool const use_uuid = target_uuid != NULL;
1208+
const char *const selected_target = use_uuid ? target_uuid : target;
11811209
int service_length = asprintf(&cmd, "%s:QUBESRPC %s%s %s",
11821210
user,
11831211
service_name,
11841212
trailer,
11851213
remote_domain_name);
11861214
if (service_length < 0)
11871215
daemon__exit(QREXEC_EXIT_PROBLEM);
1188-
daemon__exit(qrexec_execute_vm(target, autostart, remote_domain_id,
1216+
daemon__exit(qrexec_execute_vm(selected_target, autostart,
1217+
remote_domain_id,
11891218
cmd,
11901219
(size_t)service_length + 1,
1191-
request_id->ident, false, false)
1220+
request_id->ident, false, false,
1221+
use_uuid)
11921222
? 0 : QREXEC_EXIT_PROBLEM);
11931223
}
11941224
}
@@ -1511,7 +1541,7 @@ static int handle_agent_restart(int xid) {
15111541
err(1, "sigaction");
15121542

15131543
qrexec_daemon_unix_socket_fd =
1514-
create_qrexec_socket(xid, remote_domain_name);
1544+
create_qrexec_socket(xid, remote_domain_name, remote_domain_uuid);
15151545
return 0;
15161546
}
15171547

@@ -1521,6 +1551,7 @@ static struct option longopts[] = {
15211551
{ "socket-dir", required_argument, 0, 'd' + 128 },
15221552
{ "policy-program", required_argument, 0, 'p' },
15231553
{ "direct", no_argument, 0, 'D' },
1554+
{ "uuid", required_argument, 0, 'u' },
15241555
{ NULL, 0, 0, 0 },
15251556
};
15261557

@@ -1556,7 +1587,7 @@ int main(int argc, char **argv)
15561587

15571588
setup_logging("qrexec-daemon");
15581589

1559-
while ((opt=getopt_long(argc, argv, "hqp:D", longopts, NULL)) != -1) {
1590+
while ((opt=getopt_long(argc, argv, "hqp:Du:", longopts, NULL)) != -1) {
15601591
switch (opt) {
15611592
case 'q':
15621593
opt_quiet = 1;
@@ -1572,6 +1603,9 @@ int main(int argc, char **argv)
15721603
case 'D':
15731604
opt_direct = 1;
15741605
break;
1606+
case 'u':
1607+
remote_domain_uuid = optarg;
1608+
break;
15751609
case 'h':
15761610
default: /* '?' */
15771611
usage(argv[0]);

0 commit comments

Comments
 (0)