Skip to content

Commit

Permalink
Stop using qubes-rpc-multiplexer for service calls
Browse files Browse the repository at this point in the history
Instead, directly execute the command from C.

Environment variables with names beginning with QREXEC are stripped from
the environment, except for QREXEC_SERVICE_PATH and QREXEC_AGENT_PID.
This stripping happens before qrexec-specific environment variables are
set, so the following variables are still set as before:

- QREXEC_SERVICE_FULL_NAME
- QREXEC_REMOTE_DOMAIN
- QREXEC_SERVICE_ARGUMENT
- QREXEC_REQUESTED_TARGET_TYPE
- QREXEC_REQUESTED_TARGET (dom0 only)
- QREXEC_REQUESTED_TARGET_KEYWORD (dom0 only)

This is a backwards-incompatible change to
exec_qubes_rpc_if_requested(), which now takes an extra argument.
Therefore, it cannot be backported to R4.2.  It also requires changing
the SELinux policy so that the labels on /etc/qubes-rpc/ and
/usr/local/etc/qubes-rpc/ (and their contents) are correct.

qubes-rpc-multiplexer is still present because it has legacy uses in
Python code and for compatibility.

Fixes: QubesOS/qubes-issues#9062
  • Loading branch information
DemiMarie committed Jan 29, 2025
1 parent 9ea7ea2 commit 6819e1c
Show file tree
Hide file tree
Showing 19 changed files with 342 additions and 110 deletions.
33 changes: 27 additions & 6 deletions agent/qrexec-agent-data.c
Original file line number Diff line number Diff line change
Expand Up @@ -138,34 +138,52 @@ int handle_handshake(libvchan_t *ctrl)

static int handle_just_exec(struct qrexec_parsed_command *cmd)
{
int fdn, pid;
int fdn, pid, log_fd;

if (cmd == NULL)
return QREXEC_EXIT_PROBLEM;

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;
struct buffer stdin_buffer;
buffer_init(&stdin_buffer);
int status = find_qrexec_service(cmd, &socket_fd, &stdin_buffer);
int status = find_qrexec_service(cmd, &socket_fd, &stdin_buffer, &buf);
if (status == -1)
return QREXEC_EXIT_SERVICE_NOT_FOUND;
if (status != 0)
return QREXEC_EXIT_PROBLEM;
if (socket_fd != -1)
return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ? 0 : QREXEC_EXIT_PROBLEM;
return write_all(socket_fd, stdin_buffer.data, stdin_buffer.buflen) ?
0 : QREXEC_EXIT_PROBLEM;
} else {
buf.data = NULL;
}
switch (pid = fork()) {
case -1:
PERROR("fork");
return QREXEC_EXIT_PROBLEM;
case 0:
fdn = open("/dev/null", O_RDWR);
fix_fds(fdn, fdn, fdn);
do_exec(cmd->command, cmd->username);
if (fdn < 0) {
LOG(ERROR, "open /dev/null failed");
_exit(QREXEC_EXIT_PROBLEM);
}
int other_pid;
log_fd = cmd->service_descriptor ? open_logger(cmd, &other_pid) : fdn;
if (log_fd < 0)
_exit(QREXEC_EXIT_PROBLEM);
fix_fds(fdn, fdn, log_fd);
do_exec(buf.data, cmd->command, cmd->username);
default:;
}
LOG(INFO, "executed (nowait): %s (pid %d)", cmd->command, pid);
if (buf.data)
LOG(INFO, "executed (nowait): %s %s (pid %d)", buf.data, cmd->command, pid);
else
LOG(INFO, "executed (nowait): %s (pid %d)", cmd->command, pid);
return 0;
}

Expand Down Expand Up @@ -261,6 +279,7 @@ static int handle_new_process_common(
return 0;
}

int logger_pid = 0;
req.vchan = data_vchan;
req.stdin_buf = &stdin_buf;

Expand All @@ -280,6 +299,8 @@ static int handle_new_process_common(

req.prefix_data.data = NULL;
req.prefix_data.len = 0;
if (cmd->service_descriptor != NULL)
req.logger_fd = open_logger(cmd, &logger_pid);

exit_code = qrexec_process_io(&req, cmd);

Expand Down
15 changes: 9 additions & 6 deletions agent/qrexec-agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ static struct pam_conv conv = {
* If dom0 sends overly long cmd, it will probably crash qrexec-agent (unless
* process can allocate up to 4GB on both stack and heap), sorry.
*/
_Noreturn void do_exec(const char *cmd, const char *user)
_Noreturn void do_exec(const char *prog, const char *cmd, const char *user)
{
#ifdef HAVE_PAM
int retval, status;
Expand Down Expand Up @@ -172,7 +172,8 @@ _Noreturn void do_exec(const char *cmd, const char *user)
exit(1);
}
/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, environ);
/* no point in creating a login shell for test environments */
exec_qubes_rpc_if_requested2(prog, cmd, environ, false);

/* otherwise exec shell */
execl("/bin/sh", "sh", "-c", cmd, NULL);
Expand Down Expand Up @@ -278,8 +279,9 @@ _Noreturn void do_exec(const char *cmd, const char *user)
if (retval == -1)
warn("chdir(%s)", pw->pw_dir);

/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, env);
/* Call QUBESRPC if requested, using a login shell to set up
* environment variables. */
exec_qubes_rpc_if_requested2(prog, cmd, env, true);

/* otherwise exec shell */
execle(pw->pw_shell, arg0, "-c", cmd, (char*)NULL, env);
Expand Down Expand Up @@ -316,8 +318,9 @@ _Noreturn void do_exec(const char *cmd, const char *user)
pam_end(pamh, PAM_ABORT);
exit(1);
#else
/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, environ);
/* Call QUBESRPC if requested, using a login shell to set up
* environment variables. */
exec_qubes_rpc_if_requested2(prog, cmd, environ, true);

/* otherwise exec shell */
execl("/bin/su", "su", "-", user, "-c", cmd, NULL);
Expand Down
2 changes: 1 addition & 1 deletion agent/qrexec-agent.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

int handle_handshake(libvchan_t *ctrl);
void handle_vchan_error(const char *op);
_Noreturn void do_exec(const char *cmd, const char *user);
_Noreturn void do_exec(const char *prog, const char *cmd, const char *user);
/* call before fork() for service handling process (either end) */
void prepare_child_env(void);

Expand Down
2 changes: 1 addition & 1 deletion agent/qrexec-client-vm.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ _Noreturn void handle_vchan_error(const char *op)
exit(1);
}

_Noreturn void do_exec(const char *cmd __attribute__((unused)), char const* user __attribute__((__unused__))) {
_Noreturn void do_exec(const char *prog __attribute__((unused)), const char *cmd __attribute__((unused)), char const* user __attribute__((__unused__))) {
LOG(ERROR, "BUG: do_exec function shouldn't be called!");
abort();
}
Expand Down
6 changes: 3 additions & 3 deletions agent/qrexec-fork-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,15 @@
extern char **environ;
const bool qrexec_is_fork_server = true;

void do_exec(const char *cmd, const char *user __attribute__((unused)))
void do_exec(const char *prog, const char *cmd, const char *user __attribute__((unused)))
{
char *shell;

signal(SIGCHLD, SIG_DFL);
signal(SIGPIPE, SIG_DFL);

/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, environ);
/* Call QUBESRPC if requested. This code already runs in a login session. */
exec_qubes_rpc_if_requested2(prog, cmd, environ, false);

/* otherwise, pass it to shell */
shell = getenv("SHELL");
Expand Down
13 changes: 8 additions & 5 deletions daemon/qrexec-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,16 @@ static void set_remote_domain(const char *src_domain_name) {
}

/* called from do_fork_exec */
static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused)))
static _Noreturn void do_exec(const char *prog,
const char *cmdline,
const char *username __attribute__((unused)))
{
/* avoid calling qubes-rpc-multiplexer through shell */
exec_qubes_rpc_if_requested(prog, environ);
/* Avoid calling RPC command through shell.
* Qrexec-client is always in a login session. */
exec_qubes_rpc_if_requested2(prog, cmdline, environ, false);

/* if above haven't executed qubes-rpc-multiplexer, pass it to shell */
execl("/bin/bash", "bash", "-c", prog, NULL);
/* if above haven't executed RPC command, pass it to shell */
execl("/bin/bash", "bash", "-c", cmdline, NULL);
PERROR("exec bash");
exit(1);
}
Expand Down
12 changes: 7 additions & 5 deletions daemon/qrexec-daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -1131,14 +1131,16 @@ static enum policy_response connect_daemon_socket(
}

/* called from do_fork_exec */
static _Noreturn void do_exec(const char *prog, const char *username __attribute__((unused)))
static _Noreturn void do_exec(const char *prog, const char *cmd, const char *username __attribute__((unused)))
{
/* avoid calling qubes-rpc-multiplexer through shell */
exec_qubes_rpc_if_requested(prog, environ);
/* Avoid calling RPC command through shell.
* Qrexec-daemon is always in a login session already. */
exec_qubes_rpc_if_requested2(prog, cmd, environ, true);

/* if above haven't executed qubes-rpc-multiplexer, pass it to shell */
execl("/bin/bash", "bash", "-c", prog, NULL);
/* if above haven't executed RPC command, pass it to shell */
execl("/bin/bash", "bash", "-c", cmd, NULL);
PERROR("exec bash");
/* treat ENOENT as "problem" because bash should always exist */
_exit(QREXEC_EXIT_PROBLEM);
}

Expand Down
17 changes: 17 additions & 0 deletions doc/execution.rst
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,23 @@ between service types is mostly invisible to callers.
with ``force-user=`` in the configuration file. The username must be a string
user due to PAM limitations.

If a non-empty string is passed as the service argument, it is passed as the
first argument to the service. The service environment is modified as follows:

1. All environment variables with names starting with ``QREXEC`` are stripped.
2. ``QREXEC_REMOTE_DOMAIN`` is set to the name of the calling VM.
3. ``QREXEC_SERVICE_FULL_NAME`` is set to the full name of the service,
including the argument if any.
4. If the service *is not* running in dom0, ``QREXEC_REQUESTED_TARGET_TYPE`` is
set to an empty value.
5. If the service *is* running in dom0, and the requested target starts with ``@``,
``QREXEC_REQUESTED_TARGET_TYPE`` is set to ``keyword`` and
``QREXEC_REQUESTED_TARGET_KEYWORD`` is set to the requested target with the
leading ``@`` removed.
6. If the service *is* running in dom0, and the requested target *does not* start with ``@``,
``QREXEC_REQUESTED_TARGET_TYPE`` is set to ``name`` and
``QREXEC_REQUESTED_TARGET`` is set to the requested target.

2. Socket-based services. These are ``AF_UNIX`` stream sockets on the filesystem.
Data passed via stdin will be written to the socket, and data from the socket will
will be written to stdout.
Expand Down
2 changes: 1 addition & 1 deletion libqrexec/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ endif


all: libqrexec-utils.so
libqrexec-utils.so.$(SO_VER): unix-server.o ioall.o buffer.o exec.o txrx-vchan.o write-stdin.o replace.o remote.o process_io.o log.o toml.o vchan_timeout.o
libqrexec-utils.so.$(SO_VER): unix-server.o ioall.o buffer.o exec.o txrx-vchan.o write-stdin.o replace.o remote.o process_io.o log.o toml.o open_logger.o vchan_timeout.o
$(CC) $(LDFLAGS) -Wl,-soname,$@ -o $@ $^ $(VCHANLIBS)

libqrexec-utils.so: libqrexec-utils.so.$(SO_VER)
Expand Down
Loading

0 comments on commit 6819e1c

Please sign in to comment.