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 @@

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);

Check warning on line 173 in agent/qrexec-agent-data.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-agent-data.c#L172-L173

Added lines #L172 - L173 were not covered by tests
}
int other_pid;
log_fd = cmd->service_descriptor ? open_logger(cmd, &other_pid) : fdn;
if (log_fd < 0)
_exit(QREXEC_EXIT_PROBLEM);

Check warning on line 178 in agent/qrexec-agent-data.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-agent-data.c#L178

Added line #L178 was not covered by tests
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 @@
return 0;
}

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

Expand All @@ -280,6 +299,8 @@

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 @@
* 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 @@
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 @@
warn("chdir(%s)", pw->pw_dir);

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

if (prog) {

Check warning on line 285 in agent/qrexec-agent.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-agent.c#L285

Added line #L285 was not covered by tests
/* Set up environment variables for a login shell. */
exec_qubes_rpc2(prog, cmd, env, true);

Check warning on line 287 in agent/qrexec-agent.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-agent.c#L287

Added line #L287 was not covered by tests
}
/* 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 @@
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 @@
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__))) {

Check warning on line 46 in agent/qrexec-client-vm.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-client-vm.c#L46

Added line #L46 was not covered by tests
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)))

Check warning on line 40 in agent/qrexec-fork-server.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-fork-server.c#L40

Added line #L40 was not covered by tests
{
char *shell;

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

/* call QUBESRPC if requested */
exec_qubes_rpc_if_requested(cmd, environ);
if (prog != NULL) {

Check warning on line 48 in agent/qrexec-fork-server.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-fork-server.c#L48

Added line #L48 was not covered by tests
/* Already in login session. */
exec_qubes_rpc2(prog, cmd, environ, false);

Check warning on line 50 in agent/qrexec-fork-server.c

View check run for this annotation

Codecov / codecov/patch

agent/qrexec-fork-server.c#L50

Added line #L50 was not covered by tests
}

/* 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 @@
}

/* 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 @@
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)",

Check warning on line 142 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L142

Added line #L142 was not covered by tests
sizeof(struct service_params)-1);
*token = 0;
*request_id = str;
Expand Down Expand Up @@ -196,6 +201,10 @@
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);

Check warning on line 206 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L205-L206

Added lines #L205 - L206 were not covered by tests
}
break;
case 't':
replace_chars_stdout = true;
Expand Down Expand Up @@ -256,22 +265,77 @@
}

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);

Check warning on line 271 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L270-L271

Added lines #L270 - L271 were not covered by tests
}
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();

Check warning on line 277 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L276-L277

Added lines #L276 - L277 were not covered by tests
}
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");

Check warning on line 290 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L289-L290

Added lines #L289 - L290 were not covered by tests
}
/*
* 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 ":");

Check warning on line 302 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L302

Added line #L302 was not covered by tests
}
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;

Check warning on line 313 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L312-L313

Added lines #L312 - L313 were not covered by tests
} else if (!wait_for_session_maybe(command)) {
LOG(ERROR, "Cannot load service configuration, or forking process failed");
prepare_ret = -2;

Check warning on line 316 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L315-L316

Added lines #L315 - L316 were not covered by tests
} 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;

Check warning on line 325 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L325

Added line #L325 was not covered by tests
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);

Check warning on line 334 in daemon/qrexec-client.c

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-client.c#L334

Added line #L334 was not covered by tests
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 @@
}

/* 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) {

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

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1137

Added line #L1137 was not covered by tests
/* qrexec-daemon is always in a login session already */
exec_qubes_rpc2(prog, cmd, environ, false);

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

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1139

Added line #L1139 was not covered by tests
}

/* 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);

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

View check run for this annotation

Codecov / codecov/patch

daemon/qrexec-daemon.c#L1143

Added line #L1143 was not covered by tests
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