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
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
19 changes: 13 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,10 @@ _Noreturn void do_exec(const char *cmd, const char *user)
exit(1);
}
/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, environ);
if (prog) {
/* no point in creating a login shell for test environments */
exec_qubes_rpc2(prog, cmd, environ, false);
}

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

/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, env);

if (prog) {
/* Set up environment variables for a login shell. */
exec_qubes_rpc2(prog, cmd, env, true);
}
/* otherwise exec shell */
execle(pw->pw_shell, arg0, "-c", cmd, (char*)NULL, env);
_exit(QREXEC_EXIT_PROBLEM);
Expand Down Expand Up @@ -317,8 +322,10 @@ _Noreturn void do_exec(const char *cmd, const char *user)
exit(1);
#else
/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, environ);

if (prog) {
/* Set up environment variables for a login session. */
exec_qubes_rpc2(prog, cmd, environ, true);
}
/* otherwise exec shell */
execl("/bin/su", "su", "-", user, "-c", cmd, NULL);
PERROR("execl");
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
7 changes: 5 additions & 2 deletions agent/qrexec-fork-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,18 @@
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);
if (prog != NULL) {
/* Already in login session. */
exec_qubes_rpc2(prog, cmd, environ, false);
}

/* otherwise, pass it to shell */
shell = getenv("SHELL");
Expand Down
106 changes: 85 additions & 21 deletions daemon/qrexec-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,18 @@ 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 service through shell */
if (prog) {
/* qrexec-client is always in a login session. */
exec_qubes_rpc2(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 service, pass it to shell */
execl("/bin/bash", "bash", "-c", cmdline, NULL);
PERROR("exec bash");
exit(1);
}
Expand Down Expand Up @@ -134,7 +139,7 @@ static void parse_connect(char *str, char **request_id,
if (token == NULL)
goto bad_c_param;
if ((size_t)(token - str) >= sizeof(struct service_params))
errx(1, "Invalid -c parameter (request_id too long, max %zu)\n",
errx(1, "Invalid -c parameter (request_id too long, max %zu)",
sizeof(struct service_params)-1);
*token = 0;
*request_id = str;
Expand Down Expand Up @@ -196,6 +201,10 @@ int main(int argc, char **argv)
usage(argv[0], 1);
}
parse_connect(optarg, &request_id, &src_domain_name, &src_domain_id);
if (target_refers_to_dom0(src_domain_name) || src_domain_id == 0) {
warnx("ERROR: -c cannot be used for requests to dom0");
usage(argv[0], 1);
}
break;
case 't':
replace_chars_stdout = true;
Expand Down Expand Up @@ -256,22 +265,77 @@ int main(int argc, char **argv)
}

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], 1);
}
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident) - 1);
svc_params.ident[sizeof(svc_params.ident) - 1] = '\0';
if (src_domain_name == NULL) {
LOG(ERROR, "internal error: src_domain_name should not be NULL here");
abort();
if (request_id != NULL) {
if (request_id[0] == '\0') {
warnx("ERROR: request ID cannot be empty");
usage(argv[0], 1);
}
strncpy(svc_params.ident, request_id, sizeof(svc_params.ident) - 1);
svc_params.ident[sizeof(svc_params.ident) - 1] = '\0';
if (src_domain_name == NULL) {
LOG(ERROR, "internal error: src_domain_name should not be NULL here");
abort();
}
rc = run_qrexec_to_dom0(&svc_params,
src_domain_id,
src_domain_name,
remote_cmdline,
connection_timeout,
exit_with_code);
} else {
/* dom0 -> dom0 fake service call */
assert(src_domain_id == 0);
if (local_cmdline != NULL) {
warnx("dom0 -> dom0 qrexec calls with LOCAL_COMMAND not yet implemented");
errx(QREXEC_EXIT_PROBLEM, "please file an issue if you need this");
}
/*
* Normally calls to dom0 omit the username, but in this case
* that would require the caller to pass the user if and only if the target is _not_
* dom0, and that's annoying. In the past, qrexec-client was called by qrexec-daemon
* which got it right, but now the main caller of qrexec-client is Python scripts
* that don't have access to the C target_refers_to_dom0() function.
* Therefore, parse the username and fail if it is not "DEFAULT".
*/
#define DEFAULT_USER "DEFAULT"
if (strncmp(remote_cmdline, DEFAULT_USER ":", sizeof(DEFAULT_USER)) != 0) {
errx(QREXEC_EXIT_PROBLEM, "dom0 -> dom0 commands must be prefixed with " DEFAULT_USER ":");
}
remote_cmdline += sizeof(DEFAULT_USER);
struct qrexec_parsed_command *command = parse_qubes_rpc_command(remote_cmdline, false);
int prepare_ret;
char file_path[QUBES_SOCKADDR_UN_MAX_PATH_LEN];
struct buffer buf = { .data = file_path, .buflen = (int)sizeof(file_path) };
if (command == NULL) {
prepare_ret = -2;
} else if (command->service_descriptor == NULL) {
LOG(ERROR, "For dom0 -> dom0 commands, only proper qrexec calls are allowed");
prepare_ret = -2;
} else if (!wait_for_session_maybe(command)) {
LOG(ERROR, "Cannot load service configuration, or forking process failed");
prepare_ret = -2;
} else {
prepare_ret = find_qrexec_service(command, NULL, NULL, &buf);
}
switch (prepare_ret) {
case -2:
rc = QREXEC_EXIT_PROBLEM;
break;
case -1:
rc = QREXEC_EXIT_SERVICE_NOT_FOUND;
break;
case 0:
assert(command->username == NULL);
assert(command->command);
/* qrexec-client is always in a login session. */
exec_qubes_rpc2(buf.data, command->command, environ, false);
/* not reached */
default:
assert(false);
rc = QREXEC_EXIT_PROBLEM;
break;
}
}
rc = run_qrexec_to_dom0(&svc_params,
src_domain_id,
src_domain_name,
remote_cmdline,
connection_timeout,
exit_with_code);
} else {
if (request_id) {
bool const use_uuid = strncmp(domname, "uuid:", 5) == 0;
Expand Down
2 changes: 1 addition & 1 deletion daemon/qrexec-daemon-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ static void sigchld_handler(int x __attribute__((__unused__)))
}

/* See also qrexec-agent.c:wait_for_session_maybe() */
static bool wait_for_session_maybe(struct qrexec_parsed_command *cmd)
bool wait_for_session_maybe(struct qrexec_parsed_command *cmd)
{
pid_t pid;
int status;
Expand Down
4 changes: 4 additions & 0 deletions daemon/qrexec-daemon-common.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,7 @@ bool qrexec_execute_vm(const char *target, bool autostart, int remote_domain_id,
extern int local_stdin_fd;
__attribute__((warn_unused_result))
bool target_refers_to_dom0(const char *target);

/** Wait for a session if needed. */
__attribute__((warn_unused_result))
bool wait_for_session_maybe(struct qrexec_parsed_command *cmd);
14 changes: 9 additions & 5 deletions daemon/qrexec-daemon.c
Original file line number Diff line number Diff line change
Expand Up @@ -1131,14 +1131,18 @@ 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 */
if (prog) {
/* qrexec-daemon is always in a login session already */
exec_qubes_rpc2(prog, cmd, 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", cmd, NULL);
PERROR("exec bash");
/* treat ENOENT as "problem" because bash should always exist */
_exit(QREXEC_EXIT_PROBLEM);
}

Expand Down
6 changes: 3 additions & 3 deletions debian/control
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Homepage: https://www.qubes-os.org
Package: qubes-core-qrexec
Architecture: any
Depends:
libqrexec-utils2 (= ${binary:Version}),
libqrexec-utils4 (= ${binary:Version}),
python3-qrexec,
${shlibs:Depends},
${misc:Depends}
Expand All @@ -36,7 +36,7 @@ Description: Qubes qrexec agent
Agent part of Qubes RPC system. A daemon responsible for starting processes as
requested by dom0 or other VMs, according to dom0-enforced policy.

Package: libqrexec-utils2
Package: libqrexec-utils4
Architecture: any
Depends: ${shlibs:Depends}, ${misc:Depends}
Breaks: qubes-utils (<< 3.1.4)
Expand All @@ -47,7 +47,7 @@ Description: Library of common functions of qrexec agent and daemon
Package: libqrexec-utils-dev
Architecture: any
Section: libdevel
Depends: libqrexec-utils2 (= ${binary:Version}), ${misc:Depends}
Depends: libqrexec-utils4 (= ${binary:Version}), ${misc:Depends}
Breaks: qubes-utils (<< 3.1.4)
Replaces: qubes-utils (<< 3.1.4)
Description: Development headers for libqrexec-utils
Expand Down
1 change: 0 additions & 1 deletion debian/libqrexec-utils2.install

This file was deleted.

1 change: 0 additions & 1 deletion debian/libqrexec-utils2.shlibs

This file was deleted.

1 change: 1 addition & 0 deletions debian/libqrexec-utils4.install
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
usr/lib/libqrexec-utils.so.4*
1 change: 1 addition & 0 deletions debian/libqrexec-utils4.shlibs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
libqrexec-utils 4 libqrexec-utils4 (>=4.2.18)
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
Loading