Skip to content
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 @@ bool negotiate_connection_params(int s, int other_domid, unsigned type,
return true;
}

bool target_refers_to_dom0(const char *target)
{
switch (target[0]) {
case '@':
return strcmp(target + 1, "adminvm") == 0;
case 'd':
return strcmp(target + 1, "om0") == 0;
case 'u':
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 @@ int handle_daemon_handshake(int fd)
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 @@ bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
}
// 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,
"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;
}
if (use_uuid) {
if (resp_len != sizeof("uuid:00000000-0000-0000-0000-000000000000") + 1) {
LOG(ERROR, "invalid UUID length");
return false;
}
if (memcmp(buf + 2, "uuid:", 5) != 0) {
LOG(ERROR, "invalid UUID target %s", buf + 2);
return false;
}
}
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 @@ static
#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);
}

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();
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();
unlink_or_exit(socket_name);
free(socket_name);
}

static void handle_vchan_error(const char *op)
Expand All @@ -161,27 +182,25 @@ static void handle_vchan_error(const char *op)
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;
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);
}
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 @@ static void init(int xid, bool opt_direct)

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 @@ static int parse_policy_response(
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 @@ static int parse_policy_response(
*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();
} 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 @@ static enum policy_response connect_daemon_socket(
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 @@ static enum policy_response connect_daemon_socket(
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);
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 @@ static enum policy_response connect_daemon_socket(
// 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 @@ _Noreturn static void handle_execute_service_child(
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 @@ _Noreturn static void handle_execute_service_child(
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);
if (target_is_dom0) {
char *type;
bool target_is_keyword = target_domain[0] == '@';
Expand All @@ -1178,17 +1204,21 @@ _Noreturn static void handle_execute_service_child(
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;
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,
remote_domain_id,
cmd,
(size_t)service_length + 1,
request_id->ident, false, false)
request_id->ident, false, false,
use_uuid)
? 0 : QREXEC_EXIT_PROBLEM);
}
}
Expand Down Expand Up @@ -1511,7 +1541,7 @@ static int handle_agent_restart(int xid) {
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 @@ static struct option longopts[] = {
{ "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 @@ int main(int argc, char **argv)

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 @@ int main(int argc, char **argv)
case 'D':
opt_direct = 1;
break;
case 'u':
remote_domain_uuid = optarg;
break;
case 'h':
default: /* '?' */
usage(argv[0]);
Expand Down
Loading