Skip to content

Commit

Permalink
Support socket services with MSG_JUST_EXEC
Browse files Browse the repository at this point in the history
This separates _finding_ a qrexec service (and, if necessary, connecting
to a socket) from actually _executing_ the service and processing I/O.
This allows qrexec-agent to handle MSG_JUST_EXEC correctly, because it
can connect to a socket-based service without having to be prepared for
an executable service to be run immediately.  Instead, qrexec-agent
spawns an executable service using its own spawning routines that do
support MSG_JUST_EXEC.

This also adds tests for RPC services (both executable and socket) with
MSG_JUST_EXEC.  Previously, these were not tested, despite being
essential to a running system.  For instance, the Qubes app menu uses
MSG_JUST_EXEC to invoke the qubes.StartApp service every time it
launches an application in a non-disposable VM from dom0.
  • Loading branch information
DemiMarie committed Apr 10, 2024
1 parent 2be9adc commit bb9d649
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 84 deletions.
14 changes: 14 additions & 0 deletions agent/qrexec-agent-data.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ static int handle_just_exec(struct qrexec_parsed_command *cmd)

if (cmd == NULL)
return 127;

char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN];
struct buffer buf = { .data = file_path, .buflen = (int)sizeof(file_path) };
struct buffer stdin_buffer;
buffer_init(&stdin_buffer);
if (cmd->service_descriptor) {
int socket_fd;
if (!find_qrexec_service(cmd, &socket_fd, &stdin_buffer, &buf))
return 127;
if (socket_fd != -1)
return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? 0 : 127;
} else {
buf.data = NULL;
}
switch (pid = fork()) {
case -1:
PERROR("fork");
Expand Down
10 changes: 5 additions & 5 deletions libqrexec/buffer.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,12 @@ static char *limited_malloc(int len)
(total_mem > BUFFER_LIMIT) || (len <= 0))
{
LOG(ERROR, "attempt to allocate >BUFFER_LIMIT");
exit(1);
abort();

Check warning on line 38 in libqrexec/buffer.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/buffer.c#L38

Added line #L38 was not covered by tests
}
ret = malloc((size_t)len);
if (!ret) {
PERROR("malloc");
exit(1);
abort();

Check warning on line 43 in libqrexec/buffer.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/buffer.c#L43

Added line #L43 was not covered by tests
}
return ret;
}
Expand Down Expand Up @@ -83,11 +83,11 @@ void buffer_append(struct buffer *b, const char *data, int len)
assert(data != NULL && "NULL data");
if (b->buflen < 0 || b->buflen > BUFFER_LIMIT) {
LOG(ERROR, "buffer_append buflen %d", len);
exit(1);
abort();

Check warning on line 86 in libqrexec/buffer.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/buffer.c#L86

Added line #L86 was not covered by tests
}
if (len < 0 || len > BUFFER_LIMIT) {
LOG(ERROR, "buffer_append %d", len);
exit(1);
abort();

Check warning on line 90 in libqrexec/buffer.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/buffer.c#L90

Added line #L90 was not covered by tests
}
if (len == 0)
return;
Expand All @@ -108,7 +108,7 @@ void buffer_remove(struct buffer *b, int len)
char *qdata = NULL;
if (len < 0 || len > b->buflen) {
LOG(ERROR, "buffer_remove %d/%d", len, b->buflen);
exit(1);
abort();

Check warning on line 111 in libqrexec/buffer.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/buffer.c#L111

Added line #L111 was not covered by tests
}
newsize = b->buflen - len;
if (newsize > 0) {
Expand Down
74 changes: 34 additions & 40 deletions libqrexec/exec.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,8 +162,6 @@ static int do_fork_exec(const char *user,
return retval;
}

#define QUBES_SOCKADDR_UN_MAX_PATH_LEN 1024

static int qubes_connect(int s, const char *connect_path, const size_t total_path_length) {
// Avoiding an extra copy is NOT worth it!
#define QUBES_TMP_DIRECTORY "/tmp/qrexec-XXXXXX"
Expand Down Expand Up @@ -205,11 +203,6 @@ static int qubes_connect(int s, const char *connect_path, const size_t total_pat
return result;
}

static int execute_qrexec_service(
const struct qrexec_parsed_command *cmd,
int *pid, int *stdin_fd, int *stdout_fd, int *stderr_fd,
struct buffer *stdin_buffer);

/*
Find a file in the ':'-delimited list of paths given in path_list.
Returns 0 on success, -1 if the file is definitely absent in all of the
Expand Down Expand Up @@ -461,83 +454,84 @@ int execute_parsed_qubes_rpc_command(
int *stdout_fd, int *stderr_fd, struct buffer *stdin_buffer) {
if (cmd->service_descriptor) {
// Proper Qubes RPC call
return execute_qrexec_service(
cmd, pid, stdin_fd, stdout_fd, stderr_fd, stdin_buffer);
char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN];
struct buffer buf = { .data = file_path, .buflen = (int)sizeof(file_path) };
if (!find_qrexec_service(cmd, stdin_fd, stdin_buffer, &buf))
return -1;
if (*stdin_fd > -1) {
*stdout_fd = *stdin_fd;
if (stderr_fd)
*stderr_fd = -1;
*pid = 0;
return 0;
}
return do_fork_exec(cmd->username, cmd->command,
pid, stdin_fd, stdout_fd, stderr_fd);
} else {
// Legacy qrexec behavior: spawn shell directly
return do_fork_exec(cmd->username, cmd->command,
pid, stdin_fd, stdout_fd, stderr_fd);
}
}

static int execute_qrexec_service(
bool find_qrexec_service(
const struct qrexec_parsed_command *cmd,
int *pid, int *stdin_fd, int *stdout_fd, int *stderr_fd,
struct buffer *stdin_buffer) {

int *socket_fd, struct buffer *stdin_buffer,
struct buffer *path_buffer) {
assert(cmd->service_descriptor);
assert(path_buffer->buflen > NAME_MAX);

const char *qrexec_service_path = getenv("QREXEC_SERVICE_PATH");
if (!qrexec_service_path)
qrexec_service_path = QREXEC_SERVICE_PATH;
*socket_fd = -1;

char service_full_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN];
struct stat statbuf;

int ret = find_file(qrexec_service_path, cmd->service_descriptor,
service_full_path, sizeof(service_full_path),
path_buffer->data, (size_t)path_buffer->buflen,
&statbuf);
if (ret == -1)
ret = find_file(qrexec_service_path, cmd->service_name,
service_full_path, sizeof(service_full_path),
path_buffer->data, (size_t)path_buffer->buflen,
&statbuf);
if (ret < 0) {
LOG(ERROR, "Service not found: %s",
cmd->service_descriptor);
return -1;
return false;
}

if (S_ISSOCK(statbuf.st_mode)) {
/* Socket-based service. */
int s;
if ((s = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
if ((s = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0)) == -1) {
PERROR("socket");
return -1;
return false;

Check warning on line 509 in libqrexec/exec.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/exec.c#L509

Added line #L509 was not covered by tests
}
if (qubes_connect(s, service_full_path, strlen(service_full_path))) {
if (qubes_connect(s, path_buffer->data, strlen(path_buffer->data))) {
PERROR("qubes_connect");
close(s);
return -1;
return false;

Check warning on line 514 in libqrexec/exec.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/exec.c#L514

Added line #L514 was not covered by tests
}

*stdout_fd = *stdin_fd = s;
if (stderr_fd)
*stderr_fd = -1;
*pid = 0;
set_nonblock(s);

if (cmd->send_service_descriptor) {
/* send part after "QUBESRPC ", including trailing NUL */
const char *desc = cmd->command + RPC_REQUEST_COMMAND_LEN + 1;
buffer_append(stdin_buffer, desc, strlen(desc) + 1);
}
return 0;
}

if (euidaccess(service_full_path, X_OK) == 0) {
/*
Executable-based service.
*socket_fd = s;
return true;
}

Note that this delegates to qubes-rpc-multiplexer, which, for the
moment, searches for the right file again.
*/
return do_fork_exec(cmd->username, cmd->command,
pid, stdin_fd, stdout_fd, stderr_fd);
if (euidaccess(path_buffer->data, X_OK) == 0) {
/* Executable-based service. */
return true;
}

LOG(ERROR, "Unknown service type (not executable, not a socket): %s",
service_full_path);
return -1;
LOG(ERROR, "Unknown service type (not executable, not a socket): %.*s",

Check warning on line 532 in libqrexec/exec.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/exec.c#L532

Added line #L532 was not covered by tests
path_buffer->buflen, path_buffer->data);
return false;

Check warning on line 534 in libqrexec/exec.c

View check run for this annotation

Codecov / codecov/patch

libqrexec/exec.c#L534

Added line #L534 was not covered by tests
}

int exec_wait_for_session(const char *source_domain) {
Expand Down
24 changes: 23 additions & 1 deletion libqrexec/libqrexec-utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ int fork_and_flush_stdin(int fd, struct buffer *buffer);

/**
* @brief Execute an already-parsed Qubes RPC command.
* @param cmdline Null-terminated command to execute.
* @param cmd Already-parsed command to execute.
* @param pid On return, holds the PID of the child process.
* @param stdin_fd On return, holds a file descriptor connected to the child's
* stdin.
Expand All @@ -145,6 +145,28 @@ int execute_parsed_qubes_rpc_command(
const struct qrexec_parsed_command *cmd, int *pid, int *stdin_fd,
int *stdout_fd, int *stderr_fd, struct buffer *stdin_buffer);

/**
* @brief Find the implementation of a Qubes RPC command. If it is a socket,
* connect to it.
* @param[in] cmdline Null-terminated command to execute.
* @param[out] socket_fd On return, holds a file descriptor connected to the socket,
* or -1 for executable services.
* @param stdin_buffer This buffer will need to be prepended to the child process’s
* stdin.
* @param path_buffer This buffer (NUL-terminated) holds the service's path. On
* entry it must be at least NAME_MAX bytes. It will not be freed or reallocated.
* Its contents should be ignored if stdout_fd is not -1.
* @return true if the implementation is found (and, for sockets, connected to)
* successfully, false on failure.
*/
bool find_qrexec_service(
const struct qrexec_parsed_command *cmd,
int *socket_fd, struct buffer *stdin_buffer,
struct buffer *path_buffer);

/** Suggested buffer size for the path buffer of find_qrexec_service. */
#define QUBES_SOCKADDR_UN_MAX_PATH_LEN 1024

/**
* @brief Execute a Qubes RPC command.
* @param cmdline Null-terminated command to execute.
Expand Down
Loading

0 comments on commit bb9d649

Please sign in to comment.