Implementation of qrexec in Qubes R3
This page describes implementation of the qrexec framework in Qubes OS R3.
Qrexec framework consists of a number of processes communicating with each other using common IPC protocol (described in detail below). Components residing in the same domain use pipes as the underlying transport medium, while components in separate domains use vchan link.
Dom0 tools implementation
/usr/lib/qubes/qrexec-daemon
<- one instance is required for every active domain. Responsible for:- Handling execution and service requests from dom0 (source:
qrexec-client
). - Handling service requests from the associated domain (source:
qrexec-client-vm
, thenqrexec-agent
).
- Handling execution and service requests from dom0 (source:
Command line:
qrexec-daemon domain-id domain-name [default user]
domain-id: numeric Qubes ID assigned to the associated domain.
domain-name: associated domain name.
default user: optional. If passed,
qrexec-daemon
uses this user as default for all execution requests that don’t specify one.
/usr/lib/qubes/qrexec-policy
<- internal program used to evaluate the RPC policy and deciding whether a RPC call should be allowed./usr/lib/qubes/qrexec-client
<- used to pass execution and service requests toqrexec-daemon
. Command line parameters:
-d target-domain-name
Specifies the target for the execution/service request.
-l local-program
Optional. If present,local-program
is executed and its stdout/stdin are used when sending/receiving data to/from the remote peer.
-e
Optional. If present, stdout/stdin are not connected to the remote peer. Only process creation status code is received.
-c <request-id,src-domain-name,src-domain-id>
Used for connecting a VM-VM service request byqrexec-policy
. Details described below in the service example.
cmdline
Command line to pass toqrexec-daemon
as the execution/service request. Service request format is described below in the service example.
Note: none of the above tools are designed to be used by users directly.
VM tools implementation
qrexec-agent
<- one instance runs in each active domain. Responsible for:- Handling service requests from
qrexec-client-vm
and passing them to connectedqrexec-daemon
in dom0. - Executing associated
qrexec-daemon
execution/service requests.
- Handling service requests from
Command line parameters: none.
qrexec-client-vm
<- runs in an active domain. Used to pass service requests toqrexec-agent
.
Command line:
qrexec-client-vm target-domain-name service-name local-program [local program arguments]
target-domain-name
Target domain for the service request. Source is the current domain.
service-name
Requested service name.
local-program
local-program is executed locally and its stdin/stdout are connected to the remote service endpoint.
Qrexec protocol details
Qrexec protocol is message-based. All messages share a common header followed by an optional data packet.
/* uniform for all peers, data type depends on message type */
struct msg_header {
uint32_t type; /* message type */
uint32_t len; /* data length */
};
When two peers establish connection, the server sends MSG_HELLO
followed by peer_info
struct:
struct peer_info {
uint32_t version; /* qrexec protocol version */
};
The client then should reply with its own MSG_HELLO
and peer_info
. If protocol versions don’t match, the connection is closed. TODO: fallback for backwards compatibility, don’t do handshake in the same domain?.
Details of all possible use cases and the messages involved are described below.
dom0: request execution of some_command in domX and pass stdin/stdout
- dom0:
qrexec-client
is invoked in dom0 as follows:
qrexec-client -d domX [-l local_program] user:some_command
user
may be substituted with the literalDEFAULT
. In that case, default Qubes user will be used to executesome_command
.
- dom0:
qrexec-client
setsQREXEC_REMOTE_DOMAIN
environment variable to domX. - dom0: If
local_program
is set,qrexec-client
executes it and uses that child’s stdin/stdout in place of its own when exchanging data withqrexec-agent
later. - dom0:
qrexec-client
connects to domX’sqrexec-daemon
. - dom0:
qrexec-daemon
sendsMSG_HELLO
header followed bypeer_info
toqrexec-client
. - dom0:
qrexec-client
replies withMSG_HELLO
header followed bypeer_info
toqrexec-daemon
. - dom0:
qrexec-client
sendsMSG_EXEC_CMDLINE
header followed byexec_params
toqrexec-daemon
/* variable size */
struct exec_params {
uint32_t connect_domain; /* target domain id */
uint32_t connect_port; /* target vchan port for i/o exchange */
char cmdline[0]; /* command line to execute, size = msg_header.len - sizeof(struct exec_params) */
};
In this case, `connect_domain` and `connect_port` are set to 0.
- dom0:
qrexec-daemon
replies toqrexec-client
withMSG_EXEC_CMDLINE
header followed byexec_params
, but with emptycmdline
field.connect_domain
is set to Qubes ID of domX andconnect_port
is set to a vchan port allocated byqrexec-daemon
. - dom0:
qrexec-daemon
sendsMSG_EXEC_CMDLINE
header followed byexec_params
to the associated domXqrexec-agent
over vchan.connect_domain
is set to 0 (dom0),connect_port
is the same as sent toqrexec-client
.cmdline
is unchanged except that the literalDEFAULT
is replaced with actual user name, if present. - dom0:
qrexec-client
disconnects fromqrexec-daemon
. - dom0:
qrexec-client
starts a vchan server using the details received fromqrexec-daemon
and waits for connection from domX’sqrexec-agent
. - domX:
qrexec-agent
receivesMSG_EXEC_CMDLINE
header followed byexec_params
fromqrexec-daemon
over vchan. - domX:
qrexec-agent
connects toqrexec-client
over vchan using the details fromexec_params
. - domX:
qrexec-agent
executessome_command
in domX and connects the child’s stdin/stdout to the data vchan. If the process creation fails,qrexec-agent
sendsMSG_DATA_EXIT_CODE
toqrexec-client
followed by the status code (int) and disconnects from the data vchan. - Data read from
some_command
’s stdout is sent to the data vchan usingMSG_DATA_STDOUT
byqrexec-agent
.qrexec-client
passes data received asMSG_DATA_STDOUT
to its own stdout (or tolocal_program
’s stdin if used). qrexec-client
sends data read from local stdin (orlocal_program
’s stdout if used) toqrexec-agent
over the data vchan usingMSG_DATA_STDIN
.qrexec-agent
passes data received asMSG_DATA_STDIN
tosome_command
’s stdin.MSG_DATA_STDOUT
orMSG_DATA_STDIN
with datalen
field set to 0 inmsg_header
is an EOF marker. Peer receiving such message should close the associated input/output pipe.- When
some_command
terminates, domX’sqrexec-agent
sendsMSG_DATA_EXIT_CODE
header toqrexec-client
followed by the exit code (int).qrexec-agent
then disconnects from the data vchan.
domY: invoke execution of qubes service qubes.SomeRpc? in domX and pass stdin/stdout
- domY:
qrexec-client-vm
is invoked as follows:
qrexec-client-vm domX qubes.SomeRpc local_program [params]
- domY:
qrexec-client-vm
connects toqrexec-agent
(via local socket/named pipe). - domY:
qrexec-client-vm
sendstrigger_service_params
data toqrexec-agent
(without filling therequest_id
field):
struct trigger_service_params {
char service_name[64];
char target_domain[32];
struct service_params request_id; /* service request id */
};
struct service_params {
char ident[32];
};
- domY:
qrexec-agent
allocates a locally-unique (for this domain)request_id
(let’s say13
) and fills it in thetrigger_service_params
struct received fromqrexec-client-vm
. - domY:
qrexec-agent
sendsMSG_TRIGGER_SERVICE
header followed bytrigger_service_params
toqrexec-daemon
in dom0 via vchan. - dom0: domY’s
qrexec-daemon
executesqrexec-policy
:qrexec-policy domY_id domY domX qubes.SomeRpc 13
. - dom0:
qrexec-policy
evaluates if the RPC should be allowed or denied. If the action is allowed it returns0
, if the action is denied it returns1
. - dom0: domY’s
qrexec-daemon
checks the exit code ofqrexec-policy
.- If
qrexec-policy
returned not0
: domY’sqrexec-daemon
sendsMSG_SERVICE_REFUSED
header followed byservice_params
to domY’sqrexec-agent
.service_params.ident
is identical to the one received. domY’sqrexec-agent
disconnects itsqrexec-client-vm
and RPC processing is finished. - If
qrexec-policy
returned0
, RPC processing continues.
- If
- dom0: if
qrexec-policy
allowed the RPC, it executedqrexec-client -d domX -c 13,domY,domY_id user:QUBESRPC qubes.SomeRpc domY
. - dom0:
qrexec-client
setsQREXEC_REMOTE_DOMAIN
environment variable to domX. - dom0:
qrexec-client
connects to domX’sqrexec-daemon
. - dom0: domX’s
qrexec-daemon
sendsMSG_HELLO
header followed bypeer_info
toqrexec-client
. - dom0:
qrexec-client
replies withMSG_HELLO
header followed bypeer_info
to domX’sqrexec-daemon
. - dom0:
qrexec-client
sendsMSG_EXEC_CMDLINE
header followed byexec_params
to domX’sqrexec-daemon
/* variable size */
struct exec_params {
uint32_t connect_domain; /* target domain id */
uint32_t connect_port; /* target vchan port for i/o exchange */
char cmdline[0]; /* command line to execute, size = msg_header.len - sizeof(struct exec_params) */
};
In this case, `connect_domain` is set to id of **domY** (from the `-c` parameter) and `connect_port` is set to 0. `cmdline` field contains the RPC to execute, in this case `user:QUBESRPC qubes.SomeRpc domY`.
- dom0: domX’s
qrexec-daemon
replies toqrexec-client
withMSG_EXEC_CMDLINE
header followed byexec_params
, but with emptycmdline
field.connect_domain
is set to Qubes ID of domX andconnect_port
is set to a vchan port allocated by domX’sqrexec-daemon
. - dom0: domX’s
qrexec-daemon
sendsMSG_EXEC_CMDLINE
header followed byexec_params
to domX’sqrexec-agent
.connect_domain
andconnect_port
fields are the same as in the step above.cmdline
is set to the one received fromqrexec-client
, in this caseuser:QUBESRPC qubes.SomeRpc domY
. - dom0:
qrexec-client
disconnects from domX’sqrexec-daemon
after receiving connection details. - dom0:
qrexec-client
connects to domY’sqrexec-daemon
and exchanges MSG_HELLO as usual. - dom0:
qrexec-client
sendsMSG_SERVICE_CONNECT
header followed byexec_params
to domY’sqrexec-daemon
.connect_domain
is set to ID of domX (received from domX’sqrexec-daemon
) andconnect_port
is the one received as well.cmdline
is set to request ID (13
in this case). - dom0: domY’s
qrexec-daemon
sendsMSG_SERVICE_CONNECT
header followed byexec_params
to domY’sqrexec-agent
. Data fields are unchanged from the step above. - domY:
qrexec-agent
starts a vchan server on the port received in the step above. It acts as aqrexec-client
in this case because this is a VM-VM connection. - domX:
qrexec-agent
connects to the vchan server of domY’sqrexec-agent
(connection details were received before from domX’sqrexec-daemon
). - After that, connection follows the flow of the previous example (dom0-VM).