Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement UUID support in qrexec #135

Merged
merged 1 commit into from
Oct 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions daemon/qrexec-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ int main(int argc, char **argv)
usage(argv[0]);
}

if (strcmp(domname, "dom0") == 0 || strcmp(domname, "@adminvm") == 0) {
if (target_refers_to_dom0(domname)) {
if (request_id == NULL) {
fprintf(stderr, "ERROR: when target domain is 'dom0', -c must be specified\n");
usage(argv[0]);
Expand All @@ -278,10 +278,11 @@ int main(int argc, char **argv)
exit_with_code);
} else {
if (request_id) {
bool const use_uuid = strncmp(domname, "uuid:", 5) == 0;
rc = qrexec_execute_vm(domname, false, src_domain_id,
remote_cmdline, strlen(remote_cmdline) + 1,
request_id, just_exec,
wait_connection_end) ? 0 : 137;
wait_connection_end, use_uuid) ? 0 : 137;
} else {
s = connect_unix_socket(domname);
if (!negotiate_connection_params(s,
Expand Down
37 changes: 34 additions & 3 deletions daemon/qrexec-daemon-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
#include "qrexec-daemon-common.h"

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

/* ask the daemon to allocate vchan port */
bool negotiate_connection_params(int s, int other_domid, unsigned type,
Expand Down Expand Up @@ -49,6 +51,20 @@
return true;
}

bool target_refers_to_dom0(const char *target)
{
switch (target[0]) {
case '@':
return strcmp(target + 1, "adminvm") == 0;

Check warning on line 58 in daemon/qrexec-daemon-common.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon-common.c#L57-L58

Added lines #L57 - L58 were not covered by tests
case 'd':
return strcmp(target + 1, "om0") == 0;
case 'u':
DemiMarie marked this conversation as resolved.
Show resolved Hide resolved
return strcmp(target + 1, "uid:00000000-0000-0000-0000-000000000000") == 0;
default:
return false;
}
}

int handle_daemon_handshake(int fd)
{
struct msg_header hdr;
Expand Down Expand Up @@ -93,7 +109,7 @@
bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
const char *cmd, size_t const service_length,
const char *request_id, bool just_exec,
bool wait_connection_end)
bool wait_connection_end, bool use_uuid)
{
if (service_length < 2 || (size_t)service_length > MAX_QREXEC_CMD_LEN) {
/* This is arbitrary, but it helps reduce the risk of overflows in other code */
Expand All @@ -119,11 +135,26 @@
}
// Otherwise, we kill the VM immediately after starting it.
wait_connection_end = true;
buf = qubesd_call(target + 8, "admin.vm.CreateDisposable", "", &resp_len);
buf = qubesd_call2(target + QREXEC_DISPVM_PREFIX_SIZE,

Check warning on line 138 in daemon/qrexec-daemon-common.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon-common.c#L138

Added line #L138 was not covered by tests
"admin.vm.CreateDisposable", "",
"uuid", use_uuid ? 4 : 0, &resp_len);
if (buf == NULL) // error already printed by qubesd_call
return false;
if (memcmp(buf, "0", 2) == 0) {
/* we exit later so memory leaks do not matter */
if (strlen(buf + 2) != resp_len - 2) {
LOG(ERROR, "NUL byte in qubesd response");
return false;

Check warning on line 146 in daemon/qrexec-daemon-common.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon-common.c#L144-L146

Added lines #L144 - L146 were not covered by tests
}
if (use_uuid) {
if (resp_len != sizeof("uuid:00000000-0000-0000-0000-000000000000") + 1) {
LOG(ERROR, "invalid UUID length");
return false;

Check warning on line 151 in daemon/qrexec-daemon-common.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon-common.c#L148-L151

Added lines #L148 - L151 were not covered by tests
}
if (memcmp(buf + 2, "uuid:", 5) != 0) {
LOG(ERROR, "invalid UUID target %s", buf + 2);
return false;

Check warning on line 155 in daemon/qrexec-daemon-common.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon-common.c#L153-L155

Added lines #L153 - L155 were not covered by tests
}
}
target = buf + 2;
} else {
if (memcmp(buf, "2", 2) == 0) {
Expand Down
4 changes: 3 additions & 1 deletion daemon/qrexec-daemon-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@ int prepare_local_fds(struct qrexec_parsed_command *command, struct buffer *stdi
__attribute__((warn_unused_result))
bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
const char *cmd, size_t service_length, const char *request_id,
bool just_exec, bool wait_connection_end);
bool just_exec, bool wait_connection_end, bool use_uuid);
/** FD for stdout of remote process */
extern int local_stdin_fd;
__attribute__((warn_unused_result))
bool target_refers_to_dom0(const char *target);
118 changes: 76 additions & 42 deletions daemon/qrexec-daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/

#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
Expand Down Expand Up @@ -135,24 +136,44 @@
#endif
int protocol_version;

static char *remote_domain_name; // guess what
static const char *remote_domain_name; // guess what
static const char *remote_domain_uuid;
static int remote_domain_id;

static void unlink_or_exit(const char *path)
{
int v = unlink(path);
if (v != 0 && !(v == -1 && errno == ENOENT))
err(1, "unlink(%s)", path);

Check warning on line 147 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L147

Added line #L147 was not covered by tests
}

static char __attribute__((format(printf, 1, 2))) *xasprintf(const char *fmt, ...)
{
va_list x;
char *res;
va_start(x, fmt);
int r = vasprintf(&res, fmt, x);
va_end(x);
if (r < 0)
abort();

Check warning on line 158 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L158

Added line #L158 was not covered by tests
return res;
}

static void unlink_qrexec_socket(void)
{
char *socket_address;
char *link_to_socket_name;

if (asprintf(&socket_address, "%s/qrexec.%d", socket_dir, remote_domain_id) < 0)
err(1, "asprintf");
if (unlink(socket_address) != 0 && errno != ENOENT)
err(1, "unlink(%s)", socket_address);
free(socket_address);
if (asprintf(&link_to_socket_name, "%s/qrexec.%s", socket_dir, remote_domain_name) < 0)
err(1, "asprintf");
if (unlink(link_to_socket_name) != 0 && errno != ENOENT)
err(1, "unlink(%s)", link_to_socket_name);
free(link_to_socket_name);
char *socket_name;
const char *p[2] = {remote_domain_name, remote_domain_uuid};
int i;

for (i = 0; i < 2; ++i) {
char *link_to_socket_name = xasprintf("qrexec.%s%s", i > 0 ? "uuid:" : "", p[i]);
unlink_or_exit(link_to_socket_name);
free(link_to_socket_name);
}
if (asprintf(&socket_name, "qrexec.%d", remote_domain_id) < 0)
abort();

Check warning on line 174 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L174

Added line #L174 was not covered by tests
unlink_or_exit(socket_name);
free(socket_name);
}

static void handle_vchan_error(const char *op)
Expand All @@ -161,27 +182,25 @@
exit(1);
}


static int create_qrexec_socket(int domid, const char *domname)
static int create_qrexec_socket(int domid, const char *domname, const char *domuuid)
{
char socket_address[40];
char *link_to_socket_name;

snprintf(socket_address, sizeof(socket_address),
"%s/qrexec.%d", socket_dir, domid);
if (asprintf(&link_to_socket_name,
"%s/qrexec.%s", socket_dir, domname) < 0)
err(1, "asprintf");
unlink(link_to_socket_name);

/* When running as root, make the socket accessible; perms on /var/run/qubes still apply */
umask(0);
if (symlink(socket_address, link_to_socket_name)) {
PERROR("symlink(%s,%s)", socket_address, link_to_socket_name);

const char *p[2] = { domuuid, domname };
char *socket_address = xasprintf("qrexec.%d", domid);
for (int i = 0; i < 2; ++i) {
if (p[i] == NULL)
continue;

Check warning on line 194 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L194

Added line #L194 was not covered by tests
char *link_to_socket_name = xasprintf("qrexec.%s%s", i ? "" : "uuid:", p[i]);
unlink_or_exit(link_to_socket_name);
if (symlink(socket_address, link_to_socket_name)) {
PERROR("symlink(%s,%s)", socket_address, link_to_socket_name);

Check warning on line 198 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L198

Added line #L198 was not covered by tests
}
free(link_to_socket_name);
}
int fd = get_server_socket(socket_address);
umask(0077);
free(link_to_socket_name);
return fd;
}

Expand Down Expand Up @@ -407,7 +426,7 @@

atexit(unlink_qrexec_socket);
qrexec_daemon_unix_socket_fd =
create_qrexec_socket(xid, remote_domain_name);
create_qrexec_socket(xid, remote_domain_name, remote_domain_uuid);

struct sigaction sigchld_action = {
.sa_handler = signal_handler,
Expand Down Expand Up @@ -856,11 +875,12 @@
size_t result_bytes,
bool daemon,
char **user,
char **target_uuid,
char **target,
char **requested_target,
int *autostart
) {
*user = *target = *requested_target = NULL;
*user = *target_uuid = *target = *requested_target = NULL;
int result = *autostart = -1;
const char *const msg = daemon ? "qrexec-policy-daemon" : "qrexec-policy-exec";
// At least one byte must be returned
Expand Down Expand Up @@ -905,6 +925,12 @@
*target = strdup(current_response + (sizeof("target=") - 1));
if (*target == NULL)
abort();
} else if (!strncmp(current_response, "target_uuid=", sizeof("target_uuid=") - 1)) {
if (*target_uuid != NULL)
goto bad_response;
*target_uuid = strdup(current_response + 12);
if (*target_uuid == NULL)
abort();

Check warning on line 933 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L928-L933

Added lines #L928 - L933 were not covered by tests
} else if (!strncmp(current_response, "autostart=", sizeof("autostart=") - 1)) {
current_response += sizeof("autostart=") - 1;
if (*autostart != -1)
Expand Down Expand Up @@ -1001,6 +1027,7 @@
const char *target_domain,
const char *service_name,
char **user,
char **target_uuid,
char **target,
char **requested_target,
int *autostart
Expand Down Expand Up @@ -1028,7 +1055,7 @@
size_t result_bytes;
// this closes the socket
char *result = qubes_read_all_to_malloc(daemon_socket, 64, 4096, &result_bytes);
int policy_result = parse_policy_response(result, result_bytes, true, user, target, requested_target, autostart);
int policy_result = parse_policy_response(result, result_bytes, true, user, target_uuid, target, requested_target, autostart);

Check warning on line 1058 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1058

Added line #L1058 was not covered by tests
if (policy_result != RESPONSE_MALFORMED) {
// This leaks 'result', but as the code execs later anyway this isn't a problem.
// 'result' cannot be freed as 'user', 'target', and 'requested_target' point into
Expand Down Expand Up @@ -1099,7 +1126,7 @@
// This leaks 'result', but as the code execs later anyway this isn't a problem.
// 'result' cannot be freed as 'user', 'target', and 'requested_target' point into
// the same buffer.
return parse_policy_response(result, result_bytes, true, user, target, requested_target, autostart);
return parse_policy_response(result, result_bytes, true, user, target_uuid, target, requested_target, autostart);
}
}

Expand Down Expand Up @@ -1132,11 +1159,11 @@
for (i = 3; i < MAX_FDS; i++)
close(i);

char *user, *target, *requested_target;
int autostart;
char *user = NULL, *target = NULL, *requested_target = NULL, *target_uuid = NULL;
int autostart = -1;
int policy_response =
connect_daemon_socket(remote_domain_name, target_domain, service_name,
&user, &target, &requested_target, &autostart);
&user, &target_uuid, &target, &requested_target, &autostart);

if (policy_response != RESPONSE_ALLOW)
daemon__exit(QREXEC_EXIT_REQUEST_REFUSED);
Expand All @@ -1152,8 +1179,7 @@
const char *const trailer = strchr(service_name, '+') ? "" : "+";

/* Check if the target is dom0, which requires special handling. */
bool target_is_dom0 = strcmp(target, "@adminvm") == 0 ||
strcmp(target, "dom0") == 0;
bool target_is_dom0 = target_refers_to_dom0(target);

Check warning on line 1182 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1182

Added line #L1182 was not covered by tests
if (target_is_dom0) {
char *type;
bool target_is_keyword = target_domain[0] == '@';
Expand All @@ -1178,17 +1204,21 @@
5 /* 5 second timeout */,
false /* return 0 not remote status code */));
} else {
bool const use_uuid = target_uuid != NULL;
const char *const selected_target = use_uuid ? target_uuid : target;

Check warning on line 1208 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1207-L1208

Added lines #L1207 - L1208 were not covered by tests
int service_length = asprintf(&cmd, "%s:QUBESRPC %s%s %s",
user,
service_name,
trailer,
remote_domain_name);
if (service_length < 0)
daemon__exit(QREXEC_EXIT_PROBLEM);
daemon__exit(qrexec_execute_vm(target, autostart, remote_domain_id,
daemon__exit(qrexec_execute_vm(selected_target, autostart,

Check warning on line 1216 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1216

Added line #L1216 was not covered by tests
remote_domain_id,
cmd,
(size_t)service_length + 1,
request_id->ident, false, false)
request_id->ident, false, false,

Check warning on line 1220 in daemon/qrexec-daemon.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1220

Added line #L1220 was not covered by tests
use_uuid)
? 0 : QREXEC_EXIT_PROBLEM);
}
}
Expand Down Expand Up @@ -1511,7 +1541,7 @@
err(1, "sigaction");

qrexec_daemon_unix_socket_fd =
create_qrexec_socket(xid, remote_domain_name);
create_qrexec_socket(xid, remote_domain_name, remote_domain_uuid);
return 0;
}

Expand All @@ -1521,6 +1551,7 @@
{ "socket-dir", required_argument, 0, 'd' + 128 },
{ "policy-program", required_argument, 0, 'p' },
{ "direct", no_argument, 0, 'D' },
{ "uuid", required_argument, 0, 'u' },
{ NULL, 0, 0, 0 },
};

Expand Down Expand Up @@ -1556,7 +1587,7 @@

setup_logging("qrexec-daemon");

while ((opt=getopt_long(argc, argv, "hqp:D", longopts, NULL)) != -1) {
while ((opt=getopt_long(argc, argv, "hqp:Du:", longopts, NULL)) != -1) {
switch (opt) {
case 'q':
opt_quiet = 1;
Expand All @@ -1572,6 +1603,9 @@
case 'D':
opt_direct = 1;
break;
case 'u':
remote_domain_uuid = optarg;
break;
case 'h':
default: /* '?' */
usage(argv[0]);
Expand Down
Loading