mirror of
https://github.com/QubesOS/qubes-doc.git
synced 2024-10-01 01:25:40 -04:00
Rewrite qrexec docs (fixes QubesOS/qubes-issues#1392)
This commit is contained in:
parent
6b9223ec9a
commit
3fe2f6e6aa
@ -1,165 +0,0 @@
|
||||
---
|
||||
layout: doc
|
||||
title: Qrexec
|
||||
permalink: /doc/qrexec/
|
||||
redirect_from:
|
||||
- /en/doc/qrexec/
|
||||
- /doc/Qrexec/
|
||||
- /wiki/Qrexec/
|
||||
---
|
||||
|
||||
Command execution in VM (and Qubes RPC)
|
||||
=======================================
|
||||
|
||||
Qubes **qrexec** is a framework for implementing inter-VM (incl. Dom0-VM) services. It offers a mechanism to start programs in VMs, redirect their stdin/stdout, and a policy framework to control this all.
|
||||
|
||||
Basic Dom0-VM command execution
|
||||
-------------------------------
|
||||
|
||||
During each domain creation a process named `qrexec-daemon` is started in dom0, and a process named `qrexec-agent` is started in the VM. They are connected over `vchan` channel.
|
||||
|
||||
Typically, the first thing that a `qrexec-client` instance does is to send a request to `qrexec-agent` to start a process in the VM. From then on, the stdin/stdout/stderr from this remote process will be passed to the `qrexec-client` process.
|
||||
|
||||
E.g., to start a primitive shell in a VM type the following in Dom0 console:
|
||||
|
||||
~~~
|
||||
[user@dom0 ~]$ /usr/lib/qubes/qrexec-client -d <vm name> user:bash
|
||||
~~~
|
||||
|
||||
The string before first semicolon specifies what user to run the command as.
|
||||
|
||||
Adding `-e` on the `qrexec-client` command line results in mere command execution (no data passing), and `qrexec-client` exits immediately after sending the execution request.
|
||||
|
||||
There is also the `-l <local program>` flag, which directs `qrexec-client` to pass stdin/stdout of the remote program not to its stdin/stdout, but to the (spawned for this purpose) `<local program>`.
|
||||
|
||||
The `qvm-run` command is heavily based on `qrexec-client`. It also takes care of additional activities (e.g., starting the domain, if it is not up yet, and starting the GUI daemon). Thus, it is usually more convenient to use `qvm-run`.
|
||||
|
||||
There can be almost arbitrary number of `qrexec-client` processes for a domain (i.e., `qrexec-client` processes connected to the same `qrexec-daemon`); their data is multiplexed independently.
|
||||
|
||||
There is a similar command line utility avilable inside Linux AppVMs (note the `-vm` suffix): `qrexec-client-vm` that will be described in subsequent sections.
|
||||
|
||||
Qubes Inter-VM Services (Qubes RPC)
|
||||
-----------------------------------
|
||||
|
||||
Apart from simple Dom0-\>VM command executions, as discussed above, it is also useful to have more advanced infrastructure for controlled inter-VM RPC/services. This might be used for simple things like inter-VM file copy operations, as well as more complex tasks like starting a DispVM, and requesting it to do certain operations on a handed file(s).
|
||||
|
||||
Instead of implementing complex RPC-like mechanisms for inter-VM communication, Qubes takes a much simpler and pragmatic approach and aims to only provide simple *pipes* between the VMs, plus ability to request *pre-defined* programs (servers) to be started on the other end of such pipes, and a centralized policy (enforced by the `qrexec-policy` process running in dom0) which says which VMs can request what services from what VMs.
|
||||
|
||||
Thanks to the framework and automatic stdin/stdout redirection, RPC programs are very simple; both the client and server just use their stdin/stdout to pass data. The framework does all the inner work to connect these file descriptors to each other via `qrexec-daemon` and `qrexec-agent`. Additionally, DispVMs are tightly integrated; RPC to a DispVM is a simple matter of using a magic `$dispvm` keyword as the target VM name.
|
||||
|
||||
All services in Qubes are identified by a single string, which by convention takes a form of `qubes.ServiceName`. Each VM can provide handlers for each of the known services by providing a file in `/etc/qubes-rpc/` directory with the same name as the service it is supposed to handle. This file will then be executed by the qrexec service, if the dom0 policy allowed the service to be requested (see below). Typically, the files in `/etc/qubes-rpc/` contain just one line, which is a path to the specific binary that acts as a server for the incoming request, however they might also be the actual executable themselves. Qrexec framework is careful about connecting the stdin/stdout of the server process with the corresponding stdin/stdout of the requesting process in the requesting VM (see example Hello World service described below).
|
||||
|
||||
Qubes Services (RPC) policy
|
||||
---------------------------
|
||||
|
||||
Besides each VM needing to provide explicit programs to serve each supported service, the inter-VM service RPC is also governed by a central policy in Dom0.
|
||||
|
||||
In dom0, there is a bunch of files in `/etc/qubes-rpc/policy/` directory, whose names describe the available RPC actions; their content is the RPC access policy database. Some example of the default services in Qubes are:
|
||||
|
||||
- qubes.Filecopy
|
||||
- qubes.OpenInVM
|
||||
- qubes.ReceiveUpdates
|
||||
- qubes.SyncAppMenus
|
||||
- qubes.VMShell
|
||||
- qubes.ClipboardPaste
|
||||
- qubes.Gpg
|
||||
- qubes.NotifyUpdates
|
||||
- qubes.PdfConvert
|
||||
|
||||
These files contain lines with the following format:
|
||||
|
||||
~~~
|
||||
srcvm destvm (allow|deny|ask)[,user=user_to_run_as][,target=VM_to_redirect_to]
|
||||
~~~
|
||||
|
||||
You can specify `srcvm` and `destvm` by name, or by one of `$anyvm`, `$dispvm`, `dom0` reserved keywords (note string `dom0` does not match the `$anyvm` pattern; all other names do). Only `$anyvm` keyword makes sense in the `srcvm` field (service calls from dom0 are currently always allowed, `$dispvm` means "new VM created for this particular request" - so it is never a source of request). Currently, there is no way to specify source VM by type, but this is planned for Qubes R3.
|
||||
|
||||
Whenever a RPC request for service named "XYZ" is received, the first line in `/etc/qubes-rpc/policy/XYZ` that matches the actual `srcvm`/`destvm` is consulted to determine whether to allow RPC, what user account the program should run in target VM under, and what VM to redirect the execution to. If the policy file does not exits, user is prompted to create one *manually*; if still there is no policy file after prompting, the action is denied.
|
||||
|
||||
On the target VM, the `/etc/qubes-rpc/XYZ` must exist, containing the file name of the program that will be invoked.
|
||||
|
||||
Requesting VM-VM (and VM-Dom0) services execution
|
||||
-------------------------------------------------
|
||||
|
||||
In a src VM, one should invoke the qrexec client via the following command:
|
||||
|
||||
~~~
|
||||
/usr/lib/qubes/qrexec-client-vm <target vm name> <service name> <local program path> [local program arguments]`
|
||||
~~~
|
||||
|
||||
Note that only stdin/stdout is passed between RPC server and client - notably, no cmdline argument are passed.
|
||||
|
||||
The source VM name can be accessed in the server process via QREXEC\_REMOTE\_DOMAIN environment variable. (Note the source VM has *no* control over the name provided in this variable--the name of the VM is provided by dom0, and so is trusted.)
|
||||
|
||||
By default, stderr of client and server is logged to respective `/var/log/qubes/qrexec.XID` files, in each of the VM.
|
||||
|
||||
Be very careful when coding and adding a new RPC service! Any vulnerability in a RPC server can be fatal to security of the target VM!
|
||||
|
||||
Requesting VM-VM (and VM-Dom0) services execution (without cmdline helper)
|
||||
--------------------------------------------------------------------------
|
||||
|
||||
Connect directly to `/var/run/qubes/qrexec-agent-fdpass` socket as described [here](/doc/Qrexec2Implementation#Allthepiecestogetheratwork).
|
||||
|
||||
### Revoking "Yes to All" authorization
|
||||
|
||||
Qubes RPC policy supports an "ask" action, that will prompt the user whether a given RPC call should be allowed. It is set as default for services such as inter-VM file copy. A prompt window launches in dom0, that gives the user option to click "Yes to All", which allows the action and adds a new entry to the policy file, which will unconditionally allow further calls for given (service, srcVM, dstVM) tuple.
|
||||
|
||||
In order to remove such authorization, issue this command from a Dom0 terminal (example below for qubes.Filecopy service):
|
||||
|
||||
~~~
|
||||
sudo nano /etc/qubes-rpc/policy/qubes.Filecopy
|
||||
~~~
|
||||
|
||||
and then remove any line(s) ending in "allow" (before the first \#\# comment) which are the "Yes to All" results.
|
||||
|
||||
A user might also want to set their own policies in this section. This may mostly serve to prevent the user from mistakenly copying files or text from a trusted to untrusted domain, or vice-versa.
|
||||
|
||||
### Qubes RPC "Hello World" service
|
||||
|
||||
We will show the necessary files to create a simple RPC call that adds two integers on the target VM and returns back the result to the invoking VM.
|
||||
|
||||
- Client code on source VM (`/usr/bin/our_test_add_client`)
|
||||
|
||||
~~~
|
||||
#!/bin/sh
|
||||
echo $1 $2 # pass data to rpc server
|
||||
exec cat >&$SAVED_FD_1 # print result to the original stdout, not to the other rpc endpoint
|
||||
~~~
|
||||
|
||||
- Server code on target VM (`/usr/bin/our_test_add_server`)
|
||||
|
||||
~~~
|
||||
#!/bin/sh
|
||||
read arg1 arg2 # read from stdin, which is received from the rpc client
|
||||
echo $(($arg1+$arg2)) # print to stdout - so, pass to the rpc client
|
||||
~~~
|
||||
|
||||
- Policy file in dom0 (`/etc/qubes-rpc/policy/test.Add`)
|
||||
|
||||
~~~
|
||||
$anyvm $anyvm ask
|
||||
~~~
|
||||
|
||||
- Server path definition on target VM (`/etc/qubes-rpc/test.Add`)
|
||||
|
||||
~~~
|
||||
/usr/bin/our_test_add_server
|
||||
~~~
|
||||
|
||||
- To test this service, run the following in the source VM:
|
||||
|
||||
~~~
|
||||
/usr/lib/qubes/qrexec-client-vm <target VM> test.Add /usr/bin/our_test_add_client 1 2
|
||||
~~~
|
||||
|
||||
and we should get "3" as answer, provided dom0 policy allows the call to pass through, which would happen after we click "Yes" in the popup that should appear after the invocation of this command. If we changed the policy from "ask" to "allow", then no popup should be presented, and the call will always be allowed.
|
||||
|
||||
More high-level RPCs?
|
||||
---------------------
|
||||
|
||||
As previously noted, Qubes aims to provide mechanisms that are very simple and thus with very small attack surface. This is the reason why the inter-VM RPC framework is very primitive and doesn't include any serialization or other function arguments passing, etc. We should remember, however, that users/app developers are always free to run more high-level RPC protocols on top of qrexec. Care should be taken, however, to consider potential attack surfaces that are exposed to untrusted or less trusted VMs in that case.
|
||||
|
||||
Qubes RPC internals
|
||||
-------------------
|
||||
|
||||
The internal implementation of qrexec in Qubes R2 is described [here](/doc/qrexec2-implementation/), and in Qubes R3 [here](/doc/qrexec3-implementation/).
|
@ -1,75 +0,0 @@
|
||||
---
|
||||
layout: doc
|
||||
title: Qrexec2Implementation
|
||||
permalink: /doc/qrexec2-implementation/
|
||||
redirect_from:
|
||||
- /en/doc/qrexec2-implementation/
|
||||
- /doc/Qrexec2Implementation/
|
||||
- /wiki/Qrexec2Implementation/
|
||||
---
|
||||
|
||||
Implementation of qrexec in Qubes R2
|
||||
====================================
|
||||
|
||||
This page describes implementation of the [qrexec framework](/doc/qrexec/) in Qubes OS R2. Note that the implementation has changed significantly in Qubes R3 (see [Qrexec3Implementation](/doc/qrexec3-implementation/)), although the user API reminded backwards compatible (i.e. qrexec apps written for Qubes R2 should run without modifications on Qubes R3).
|
||||
|
||||
Dom0 tools implementation
|
||||
-------------------------
|
||||
|
||||
Players:
|
||||
|
||||
- `/usr/lib/qubes/qrexec-daemon` \<- started by mgmt stack (qubes.py) when a VM is started.
|
||||
- `/usr/lib/qubes/qrexec-policy` \<- internal program used to evaluate the policy file and making the 2nd half of the connection.
|
||||
- `/usr/lib/qubes/qrexec-client` \<- raw command line tool that talks to the daemon via unix socket (`/var/run/qubes/qrexec.XID`)
|
||||
|
||||
Note: none of the above tools are designed to be used by users.
|
||||
|
||||
Linux VMs implementation
|
||||
------------------------
|
||||
|
||||
Players:
|
||||
|
||||
- `/usr/lib/qubes/qrexec-agent` \<- started by VM bootup scripts, a daemon.
|
||||
- `/usr/lib/qubes/qubes-rpc-multiplexer` \<- executes the actual service program, as specified in VM's `/etc/qubes-rpc/qubes.XYZ`.
|
||||
- `/usr/lib/qubes/qrexec-client-vm` \<- raw command line tool that talks to the agent.
|
||||
|
||||
Note: none of the above tools are designed to be used by users. `qrexec-client-vm` is designed to be wrapped up by Qubes apps.
|
||||
|
||||
Windows VMs implemention
|
||||
------------------------
|
||||
|
||||
%QUBES\_DIR% is the installation path (`c:\Program Files\Invisible Things Lab\Qubes OS Windows Tools` by default).
|
||||
|
||||
- `%QUBES_DIR%\bin\qrexec-agent.exe` \<- runs as a system service. Responsible both for raw command execution and interpreting RPC service requests.
|
||||
- `%QUBES_DIR%\qubes-rpc` \<- directory with `qubes.XYZ` files that contain commands for executing RPC services. Binaries for the services are contained in `%QUBES_DIR%\qubes-rpc-services`.
|
||||
- `%QUBES_DIR%\bin\qrexec-client-vm` \<- raw command line tool that talks to the agent.
|
||||
|
||||
Note: neither of the above tools are designed to be used by users. `qrexec-client-vm` is designed to be wrapped up by Qubes apps.
|
||||
|
||||
All the pieces together at work
|
||||
-------------------------------
|
||||
|
||||
Note: this section is not needed to use qrexec for writing Qubes apps. Also note the qrexec framework implemention in Qubes R3 significantly differs from what is described in this section.
|
||||
|
||||
The VM-VM channels in Qubes R2 are made via "gluing" two VM-Dom0 and Dom0-VM vchan connections:
|
||||
|
||||
![qrexec2-internals.png](/attachment/wiki/Qrexec2Implementation/qrexec2-internals.png)
|
||||
|
||||
Note: Dom0 never examines the actual data flowing in neither of the two vchan connections.
|
||||
|
||||
When a user in a source VM executes `qrexec-client-vm` utility, the following steps are taken:
|
||||
|
||||
- `qrexec-client-vm` connects to `qrexec-agent`'s `/var/run/qubes/qrexec-agent-fdpass` unix socket 3 times. Reads 4 bytes from each of them, which is the fd number of the accepted socket in agent. These 3 integers, in text, concatenated, form "connection identifier" (CID)
|
||||
- `qrexec-client-vm` writes to `/var/run/qubes/qrexec-agent` fifo a blob, consisting of target vmname, rpc action, and CID
|
||||
- `qrexec-client-vm` executes the rpc client, passing the above mentioned unix sockets as process stdin/stdout, and optionally stderr (if the PASS\_LOCAL\_STDERR env variable is set)
|
||||
- `qrexec-agent` passes the blob to `qrexec-daemon`, via MSG\_AGENT\_TO\_SERVER\_TRIGGER\_CONNECT\_EXISTING message over vchan
|
||||
- `qrexec-daemon` executes `qrexec-policy`, passing source vmname, target vmname, rpc action, and CID as cmdline arguments
|
||||
- `qrexec-policy` evaluates the policy file. If successful, creates a pair of `qrexec-client` processes, whose stdin/stdout are cross-connencted.
|
||||
- The first `qrexec-client` connects to the src VM, using the `-c ClientID` parameter, which results in not creating a new process, but connecting to the existing process file descriptors (these are the fds of unix socket created in step 1).
|
||||
- The second `qrexec-client` connects to the target VM, and executes `qubes-rpc-multiplexer` command there with the rpc action as the cmdline argument. Finally, `qubes-rpc-multiplexer` executes the correct rpc server on the target.
|
||||
- In the above step, if the target VM is `$dispvm`, the DispVM is created via the `qfile-daemon-dvm` program. The latter waits for the `qrexec-client` process to exit, and then destroys the DispVM.
|
||||
|
||||
Protocol description ("wire-level" spec)
|
||||
----------------------------------------
|
||||
|
||||
TODO
|
330
developers/services/qrexec2.md
Normal file
330
developers/services/qrexec2.md
Normal file
@ -0,0 +1,330 @@
|
||||
---
|
||||
layout: doc
|
||||
title: Qrexec2
|
||||
permalink: /doc/qrexec2/
|
||||
redirect_from:
|
||||
- /doc/qrexec2-implementation/
|
||||
- /en/doc/qrexec2-implementation/
|
||||
- /doc/Qrexec2Implementation/
|
||||
- /wiki/Qrexec2Implementation/
|
||||
---
|
||||
|
||||
# Command execution in VMs #
|
||||
|
||||
(*This page is about qrexec v2. For qrexec v3, see
|
||||
[here](/doc/qrexec3/).*)
|
||||
|
||||
Qubes **qrexec** is a framework for implementing inter-VM (incl. Dom0-VM)
|
||||
services. It offers a mechanism to start programs in VMs, redirect their
|
||||
stdin/stdout, and a policy framework to control this all.
|
||||
|
||||
|
||||
## Qrexec basics ##
|
||||
|
||||
During each domain creation a process named `qrexec-daemon` is started in
|
||||
dom0, and a process named `qrexec-agent` is started in the VM. They are
|
||||
connected over `vchan` channel.
|
||||
|
||||
Typically, the first thing that a `qrexec-client` instance does is to send
|
||||
a request to `qrexec-agent` to start a process in the VM. From then on,
|
||||
the stdin/stdout/stderr from this remote process will be passed to the
|
||||
`qrexec-client` process.
|
||||
|
||||
E.g., to start a primitive shell in a VM type the following in Dom0 console:
|
||||
|
||||
[user@dom0 ~]$ /usr/lib/qubes/qrexec-client -d <vm name> user:bash
|
||||
|
||||
The string before first semicolon specifies what user to run the command as.
|
||||
|
||||
Adding `-e` on the `qrexec-client` command line results in mere command
|
||||
execution (no data passing), and `qrexec-client` exits immediately after
|
||||
sending the execution request.
|
||||
|
||||
There is also the `-l <local program>` flag, which directs `qrexec-client`
|
||||
to pass stdin/stdout of the remote program not to its stdin/stdout, but to
|
||||
the (spawned for this purpose) `<local program>`.
|
||||
|
||||
The `qvm-run` command is heavily based on `qrexec-client`. It also takes care
|
||||
of additional activities (e.g., starting the domain, if it is not up yet, and
|
||||
starting the GUI daemon). Thus, it is usually more convenient to use `qvm-run`.
|
||||
|
||||
There can be almost arbitrary number of `qrexec-client` processes for a domain
|
||||
(i.e., `qrexec-client` processes connected to the same `qrexec-daemon`);
|
||||
their data is multiplexed independently.
|
||||
|
||||
There is a similar command line utility avilable inside Linux AppVMs (note
|
||||
the `-vm` suffix): `qrexec-client-vm` that will be described in subsequent
|
||||
sections.
|
||||
|
||||
|
||||
## Qubes RPC services ##
|
||||
|
||||
Apart from simple Dom0-\>VM command executions, as discussed above, it is
|
||||
also useful to have more advanced infrastructure for controlled inter-VM
|
||||
RPC/services. This might be used for simple things like inter-VM file
|
||||
copy operations, as well as more complex tasks like starting a DispVM,
|
||||
and requesting it to do certain operations on a handed file(s).
|
||||
|
||||
Instead of implementing complex RPC-like mechanisms for inter-VM communication,
|
||||
Qubes takes a much simpler and pragmatic approach and aims to only provide
|
||||
simple *pipes* between the VMs, plus ability to request *pre-defined* programs
|
||||
(servers) to be started on the other end of such pipes, and a centralized
|
||||
policy (enforced by the `qrexec-policy` process running in dom0) which says
|
||||
which VMs can request what services from what VMs.
|
||||
|
||||
Thanks to the framework and automatic stdin/stdout redirection, RPC programs
|
||||
are very simple; both the client and server just use their stdin/stdout to pass
|
||||
data. The framework does all the inner work to connect these file descriptors
|
||||
to each other via `qrexec-daemon` and `qrexec-agent`. Additionally, DispVMs
|
||||
are tightly integrated; RPC to a DispVM is a simple matter of using a magic
|
||||
`$dispvm` keyword as the target VM name.
|
||||
|
||||
All services in Qubes are identified by a single string, which by convention
|
||||
takes a form of `qubes.ServiceName`. Each VM can provide handlers for each of
|
||||
the known services by providing a file in `/etc/qubes-rpc/` directory with
|
||||
the same name as the service it is supposed to handle. This file will then
|
||||
be executed by the qrexec service, if the dom0 policy allowed the service to
|
||||
be requested (see below). Typically, the files in `/etc/qubes-rpc/` contain
|
||||
just one line, which is a path to the specific binary that acts as a server
|
||||
for the incoming request, however they might also be the actual executable
|
||||
themselves. Qrexec framework is careful about connecting the stdin/stdout
|
||||
of the server process with the corresponding stdin/stdout of the requesting
|
||||
process in the requesting VM (see example Hello World service described below).
|
||||
|
||||
|
||||
## Qubes RPC administration ##
|
||||
|
||||
Besides each VM needing to provide explicit programs to serve each supported
|
||||
service, the inter-VM service RPC is also governed by a central policy in Dom0.
|
||||
|
||||
In dom0, there is a bunch of files in `/etc/qubes-rpc/policy/` directory,
|
||||
whose names describe the available RPC actions; their content is the RPC
|
||||
access policy database. Some example of the default services in Qubes are:
|
||||
|
||||
qubes.Filecopy
|
||||
qubes.OpenInVM
|
||||
qubes.ReceiveUpdates
|
||||
qubes.SyncAppMenus
|
||||
qubes.VMShell
|
||||
qubes.ClipboardPaste
|
||||
qubes.Gpg
|
||||
qubes.NotifyUpdates
|
||||
qubes.PdfConvert
|
||||
|
||||
These files contain lines with the following format:
|
||||
|
||||
srcvm destvm (allow|deny|ask)[,user=user_to_run_as][,target=VM_to_redirect_to]
|
||||
|
||||
You can specify `srcvm` and `destvm` by name, or by one of `$anyvm`,
|
||||
`$dispvm`, `dom0` reserved keywords (note string `dom0` does not match the
|
||||
`$anyvm` pattern; all other names do). Only `$anyvm` keyword makes sense
|
||||
in the `srcvm` field (service calls from dom0 are currently always allowed,
|
||||
`$dispvm` means "new VM created for this particular request" - so it is never
|
||||
a source of request). Currently, there is no way to specify source VM by type,
|
||||
but this is planned for Qubes R3.
|
||||
|
||||
Whenever a RPC request for service named "XYZ" is received, the first line
|
||||
in `/etc/qubes-rpc/policy/XYZ` that matches the actual `srcvm`/`destvm` is
|
||||
consulted to determine whether to allow RPC, what user account the program
|
||||
should run in target VM under, and what VM to redirect the execution to. If
|
||||
the policy file does not exits, user is prompted to create one *manually*;
|
||||
if still there is no policy file after prompting, the action is denied.
|
||||
|
||||
On the target VM, the `/etc/qubes-rpc/XYZ` must exist, containing the file
|
||||
name of the program that will be invoked.
|
||||
|
||||
|
||||
### Requesting VM-VM (and VM-Dom0) services execution ###
|
||||
|
||||
In a src VM, one should invoke the qrexec client via the following command:
|
||||
|
||||
/usr/lib/qubes/qrexec-client-vm <target vm name> <service name> <local program path> [local program arguments]
|
||||
|
||||
Note that only stdin/stdout is passed between RPC server and client --
|
||||
notably, no cmdline argument are passed.
|
||||
|
||||
The source VM name can be accessed in the server process via
|
||||
`QREXEC_REMOTE_DOMAIN` environment variable. (Note the source VM has *no*
|
||||
control over the name provided in this variable--the name of the VM is
|
||||
provided by dom0, and so is trusted.)
|
||||
|
||||
By default, stderr of client and server is logged to respective
|
||||
`/var/log/qubes/qrexec.XID` files, in each of the VM.
|
||||
|
||||
Be very careful when coding and adding a new RPC service! Any vulnerability
|
||||
in a RPC server can be fatal to security of the target VM!
|
||||
|
||||
If requesting VM-VM (and VM-Dom0) services execution *without cmdline helper*,
|
||||
connect directly to `/var/run/qubes/qrexec-agent-fdpass` socket as described
|
||||
[below](#all-the-pieces-together-at-work).
|
||||
|
||||
### Revoking "Yes to All" authorization ###
|
||||
|
||||
Qubes RPC policy supports an "ask" action, that will prompt the user whether
|
||||
a given RPC call should be allowed. It is set as default for services such
|
||||
as inter-VM file copy. A prompt window launches in dom0, that gives the user
|
||||
option to click "Yes to All", which allows the action and adds a new entry
|
||||
to the policy file, which will unconditionally allow further calls for given
|
||||
(service, srcVM, dstVM) tuple.
|
||||
|
||||
In order to remove such authorization, issue this command from a Dom0 terminal
|
||||
(example below for `qubes.Filecopy` service):
|
||||
|
||||
sudo nano /etc/qubes-rpc/policy/qubes.Filecopy
|
||||
|
||||
and then remove any line(s) ending in "allow" (before the first `##` comment)
|
||||
which are the "Yes to All" results.
|
||||
|
||||
A user might also want to set their own policies in this section. This may
|
||||
mostly serve to prevent the user from mistakenly copying files or text from
|
||||
a trusted to untrusted domain, or vice-versa.
|
||||
|
||||
|
||||
### Qubes RPC "Hello World" service ###
|
||||
|
||||
We will show the necessary files to create a simple RPC call that adds two
|
||||
integers on the target VM and returns back the result to the invoking VM.
|
||||
|
||||
* Client code on source VM (`/usr/bin/our_test_add_client`)
|
||||
|
||||
#!/bin/sh
|
||||
echo $1 $2 # pass data to rpc server
|
||||
exec cat >&$SAVED_FD_1 # print result to the original stdout, not to the other rpc endpoint
|
||||
|
||||
* Server code on target VM (`/usr/bin/our_test_add_server`)
|
||||
|
||||
#!/bin/sh
|
||||
read arg1 arg2 # read from stdin, which is received from the rpc client
|
||||
echo $(($arg1+$arg2)) # print to stdout - so, pass to the rpc client
|
||||
|
||||
* Policy file in dom0 (`/etc/qubes-rpc/policy/test.Add`)
|
||||
|
||||
$anyvm $anyvm ask
|
||||
|
||||
* Server path definition on target VM (`/etc/qubes-rpc/test.Add`)
|
||||
|
||||
/usr/bin/our_test_add_server
|
||||
|
||||
* To test this service, run the following in the source VM:
|
||||
|
||||
/usr/lib/qubes/qrexec-client-vm <target VM> test.Add /usr/bin/our_test_add_client 1 2
|
||||
|
||||
and we should get "3" as answer, provided dom0 policy allows the call to pass
|
||||
through, which would happen after we click "Yes" in the popup that should
|
||||
appear after the invocation of this command. If we changed the policy from
|
||||
"ask" to "allow", then no popup should be presented, and the call will always
|
||||
be allowed.
|
||||
|
||||
**Note:** For a real world example of writing a qrexec service, see this
|
||||
[blog post](http://theinvisiblethings.blogspot.com/2013/02/converting-untrusted-pdfs-into-trusted.html).
|
||||
|
||||
### More high-level RPCs? ###
|
||||
|
||||
As previously noted, Qubes aims to provide mechanisms that are very simple
|
||||
and thus with very small attack surface. This is the reason why the inter-VM
|
||||
RPC framework is very primitive and doesn't include any serialization or
|
||||
other function arguments passing, etc. We should remember, however, that
|
||||
users/app developers are always free to run more high-level RPC protocols on
|
||||
top of qrexec. Care should be taken, however, to consider potential attack
|
||||
surfaces that are exposed to untrusted or less trusted VMs in that case.
|
||||
|
||||
|
||||
## Qubes RPC internals ##
|
||||
|
||||
(*This is about the implementation of qrexec v2. For the implementation of
|
||||
qrexec v3, see [here](/doc/qrexec3/). Note that the user API in v3 is backward
|
||||
compatible: qrexec apps written for Qubes R2 should run without modification
|
||||
on Qubes R3.*)
|
||||
|
||||
|
||||
### Dom0 tools implementation ###
|
||||
|
||||
Players:
|
||||
|
||||
* `/usr/lib/qubes/qrexec-daemon`: started by mgmt stack (qubes.py) when a
|
||||
VM is started.
|
||||
* `/usr/lib/qubes/qrexec-policy`: internal program used to evaluate the
|
||||
policy file and making the 2nd half of the connection.
|
||||
* `/usr/lib/qubes/qrexec-client`: raw command line tool that talks to the
|
||||
daemon via unix socket (`/var/run/qubes/qrexec.XID`)
|
||||
|
||||
**Note:** None of the above tools are designed to be used by users.
|
||||
|
||||
|
||||
### Linux VMs implementation ###
|
||||
|
||||
Players:
|
||||
|
||||
* `/usr/lib/qubes/qrexec-agent`: started by VM bootup scripts, a daemon.
|
||||
* `/usr/lib/qubes/qubes-rpc-multiplexer`: executes the actual service program,
|
||||
as specified in VM's `/etc/qubes-rpc/qubes.XYZ`.
|
||||
* `/usr/lib/qubes/qrexec-client-vm`: raw command line tool that talks to
|
||||
the agent.
|
||||
|
||||
**Note:** None of the above tools are designed to be used by
|
||||
users. `qrexec-client-vm` is designed to be wrapped up by Qubes apps.
|
||||
|
||||
|
||||
### Windows VMs implemention ###
|
||||
|
||||
`%QUBES_DIR%` is the installation path (`c:\Program Files\Invisible Things
|
||||
Lab\Qubes OS Windows Tools` by default).
|
||||
|
||||
* `%QUBES_DIR%\bin\qrexec-agent.exe`: runs as a system service. Responsible
|
||||
both for raw command execution and interpreting RPC service requests.
|
||||
* `%QUBES_DIR%\qubes-rpc`: directory with `qubes.XYZ` files that contain
|
||||
commands for executing RPC services. Binaries for the services are contained
|
||||
in `%QUBES_DIR%\qubes-rpc-services`.
|
||||
* `%QUBES_DIR%\bin\qrexec-client-vm`: raw command line tool that talks to
|
||||
the agent.
|
||||
|
||||
**Note:** None of the above tools are designed to be used by
|
||||
users. `qrexec-client-vm` is designed to be wrapped up by Qubes apps.
|
||||
|
||||
|
||||
### All the pieces together at work ###
|
||||
|
||||
**Note:** This section is not needed to use qrexec for writing Qubes
|
||||
apps. Also note the [qrexec framework implemention in Qubes R3](/doc/qrexec3/)
|
||||
significantly differs from what is described in this section.
|
||||
|
||||
The VM-VM channels in Qubes R2 are made via "gluing" two VM-Dom0 and Dom0-VM
|
||||
vchan connections:
|
||||
|
||||
![qrexec2-internals.png](/attachment/wiki/Qrexec2Implementation/qrexec2-internals.png)
|
||||
|
||||
Note that Dom0 never examines the actual data flowing in neither of the two
|
||||
vchan connections.
|
||||
|
||||
When a user in a source VM executes `qrexec-client-vm` utility, the following
|
||||
steps are taken:
|
||||
|
||||
* `qrexec-client-vm` connects to `qrexec-agent`'s
|
||||
`/var/run/qubes/qrexec-agent-fdpass` unix socket 3 times. Reads 4 bytes from
|
||||
each of them, which is the fd number of the accepted socket in agent. These
|
||||
3 integers, in text, concatenated, form "connection identifier" (CID)
|
||||
* `qrexec-client-vm` writes to `/var/run/qubes/qrexec-agent` fifo a blob,
|
||||
consisting of target vmname, rpc action, and CID
|
||||
* `qrexec-client-vm` executes the rpc client, passing the above mentioned
|
||||
unix sockets as process stdin/stdout, and optionally stderr (if the
|
||||
`PASS_LOCAL_STDERR` env variable is set)
|
||||
* `qrexec-agent` passes the blob to `qrexec-daemon`, via
|
||||
`MSG_AGENT_TO_SERVER_TRIGGER_CONNECT_EXISTING` message over vchan
|
||||
* `qrexec-daemon` executes `qrexec-policy`, passing source vmname, target
|
||||
vmname, rpc action, and CID as cmdline arguments
|
||||
* `qrexec-policy` evaluates the policy file. If successful, creates a pair of
|
||||
`qrexec-client` processes, whose stdin/stdout are cross-connencted.
|
||||
* The first `qrexec-client` connects to the src VM, using the `-c ClientID`
|
||||
parameter, which results in not creating a new process, but connecting to
|
||||
the existing process file descriptors (these are the fds of unix socket
|
||||
created in step 1).
|
||||
* The second `qrexec-client` connects to the target VM, and executes
|
||||
`qubes-rpc-multiplexer` command there with the rpc action as the cmdline
|
||||
argument. Finally, `qubes-rpc-multiplexer` executes the correct rpc server
|
||||
on the target.
|
||||
* In the above step, if the target VM is `$dispvm`, the DispVM is created
|
||||
via the `qfile-daemon-dvm` program. The latter waits for the `qrexec-client`
|
||||
process to exit, and then destroys the DispVM.
|
||||
|
||||
*TODO: Protocol description ("wire-level" spec)*
|
||||
|
@ -1,185 +0,0 @@
|
||||
---
|
||||
layout: doc
|
||||
title: Qrexec3Implementation
|
||||
permalink: /doc/qrexec3-implementation/
|
||||
redirect_from:
|
||||
- /en/doc/qrexec3-implementation/
|
||||
- /doc/Qrexec3Implementation/
|
||||
- /wiki/Qrexec3Implementation/
|
||||
---
|
||||
|
||||
Implementation of qrexec in Qubes R3
|
||||
====================================
|
||||
|
||||
This page describes implementation of the [qrexec framework](/doc/qrexec/) 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`, then `qrexec-agent`).
|
||||
|
||||
> 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 to `qrexec-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 by `qrexec-policy`. Details described below in the service example.
|
||||
|
||||
> `cmdline` Command line to pass to `qrexec-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 connected `qrexec-daemon` in **dom0**.
|
||||
- Executing associated `qrexec-daemon` execution/service requests.
|
||||
|
||||
> Command line parameters: none.
|
||||
|
||||
- `qrexec-client-vm` \<- runs in an active domain. Used to pass service requests to `qrexec-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 literal `DEFAULT`. In that case, default Qubes user will be used to execute `some_command`.
|
||||
|
||||
- **dom0**: `qrexec-client` sets `QREXEC_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 with `qrexec-agent` later.
|
||||
- **dom0**: `qrexec-client` connects to **domX**'s `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-daemon` sends `MSG_HELLO` header followed by `peer_info` to `qrexec-client`.
|
||||
- **dom0**: `qrexec-client` replies with `MSG_HELLO` header followed by `peer_info` to `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-client` sends `MSG_EXEC_CMDLINE` header followed by `exec_params` to `qrexec-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 to `qrexec-client` with `MSG_EXEC_CMDLINE` header followed by `exec_params`, but with empty `cmdline` field. `connect_domain` is set to Qubes ID of **domX** and `connect_port` is set to a vchan port allocated by `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-daemon` sends `MSG_EXEC_CMDLINE` header followed by `exec_params` to the associated **domX** `qrexec-agent` over vchan. `connect_domain` is set to 0 (**dom0**), `connect_port` is the same as sent to `qrexec-client`. `cmdline` is unchanged except that the literal `DEFAULT` is replaced with actual user name, if present.
|
||||
- **dom0**: `qrexec-client` disconnects from `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-client` starts a vchan server using the details received from `qrexec-daemon` and waits for connection from **domX**'s `qrexec-agent`.
|
||||
- **domX**: `qrexec-agent` receives `MSG_EXEC_CMDLINE` header followed by `exec_params` from `qrexec-daemon` over vchan.
|
||||
- **domX**: `qrexec-agent` connects to `qrexec-client` over vchan using the details from `exec_params`.
|
||||
- **domX**: `qrexec-agent` executes `some_command` in **domX** and connects the child's stdin/stdout to the data vchan. If the process creation fails, `qrexec-agent` sends `MSG_DATA_EXIT_CODE` to `qrexec-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 using `MSG_DATA_STDOUT` by `qrexec-agent`. `qrexec-client` passes data received as `MSG_DATA_STDOUT` to its own stdout (or to `local_program`'s stdin if used).
|
||||
- `qrexec-client` sends data read from local stdin (or `local_program`'s stdout if used) to `qrexec-agent` over the data vchan using `MSG_DATA_STDIN`. `qrexec-agent` passes data received as `MSG_DATA_STDIN` to `some_command`'s stdin.
|
||||
- `MSG_DATA_STDOUT` or `MSG_DATA_STDIN` with data `len` field set to 0 in `msg_header` is an EOF marker. Peer receiving such message should close the associated input/output pipe.
|
||||
- When `some_command` terminates, **domX**'s `qrexec-agent` sends `MSG_DATA_EXIT_CODE` header to `qrexec-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 to `qrexec-agent` (via local socket/named pipe).
|
||||
- **domY**: `qrexec-client-vm` sends `trigger_service_params` data to `qrexec-agent` (without filling the `request_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 say `13`) and fills it in the `trigger_service_params` struct received from `qrexec-client-vm`.
|
||||
- **domY**: `qrexec-agent` sends `MSG_TRIGGER_SERVICE` header followed by `trigger_service_params` to `qrexec-daemon` in **dom0** via vchan.
|
||||
- **dom0**: **domY**'s `qrexec-daemon` executes `qrexec-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 returns `0`, if the action is denied it returns `1`.
|
||||
- **dom0**: **domY**'s `qrexec-daemon` checks the exit code of `qrexec-policy`.
|
||||
- If `qrexec-policy` returned **not** `0`: **domY**'s `qrexec-daemon` sends `MSG_SERVICE_REFUSED` header followed by `service_params` to **domY**'s `qrexec-agent`. `service_params.ident` is identical to the one received. **domY**'s `qrexec-agent` disconnects its `qrexec-client-vm` and RPC processing is finished.
|
||||
- If `qrexec-policy` returned `0`, RPC processing continues.
|
||||
- **dom0**: if `qrexec-policy` allowed the RPC, it executed `qrexec-client -d domX -c 13,domY,domY_id user:QUBESRPC qubes.SomeRpc domY`.
|
||||
- **dom0**: `qrexec-client` sets `QREXEC_REMOTE_DOMAIN` environment variable to **domX**.
|
||||
- **dom0**: `qrexec-client` connects to **domX**'s `qrexec-daemon`.
|
||||
- **dom0**: **domX**'s `qrexec-daemon` sends `MSG_HELLO` header followed by `peer_info` to `qrexec-client`.
|
||||
- **dom0**: `qrexec-client` replies with `MSG_HELLO` header followed by `peer_info` to **domX**'s`qrexec-daemon`.
|
||||
- **dom0**: `qrexec-client` sends `MSG_EXEC_CMDLINE` header followed by `exec_params` to **domX**'s`qrexec-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 to `qrexec-client` with `MSG_EXEC_CMDLINE` header followed by `exec_params`, but with empty `cmdline` field. `connect_domain` is set to Qubes ID of **domX** and `connect_port` is set to a vchan port allocated by **domX**'s `qrexec-daemon`.
|
||||
- **dom0**: **domX**'s `qrexec-daemon` sends `MSG_EXEC_CMDLINE` header followed by `exec_params` to **domX**'s `qrexec-agent`. `connect_domain` and `connect_port` fields are the same as in the step above. `cmdline` is set to the one received from `qrexec-client`, in this case `user:QUBESRPC qubes.SomeRpc domY`.
|
||||
- **dom0**: `qrexec-client` disconnects from **domX**'s `qrexec-daemon` after receiving connection details.
|
||||
- **dom0**: `qrexec-client` connects to **domY**'s `qrexec-daemon` and exchanges MSG\_HELLO as usual.
|
||||
- **dom0**: `qrexec-client` sends `MSG_SERVICE_CONNECT` header followed by `exec_params` to **domY**'s `qrexec-daemon`. `connect_domain` is set to ID of **domX** (received from **domX**'s `qrexec-daemon`) and `connect_port` is the one received as well. `cmdline` is set to request ID (`13` in this case).
|
||||
- **dom0**: **domY**'s `qrexec-daemon` sends `MSG_SERVICE_CONNECT` header followed by `exec_params` to **domY**'s `qrexec-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 a `qrexec-client` in this case because this is a VM-VM connection.
|
||||
- **domX**: `qrexec-agent` connects to the vchan server of **domY**'s `qrexec-agent` (connection details were received before from **domX**'s `qrexec-daemon`).
|
||||
- After that, connection follows the flow of the previous example (dom0-VM).
|
||||
|
@ -6,125 +6,444 @@ redirect_from:
|
||||
- /en/doc/qrexec3/
|
||||
- /doc/Qrexec3/
|
||||
- /wiki/Qrexec3/
|
||||
- /doc/qrexec/
|
||||
- /en/doc/qrexec/
|
||||
- /doc/Qrexec/
|
||||
- /wiki/Qrexec/
|
||||
- /doc/qrexec3-implementation/
|
||||
- /en/doc/qrexec3-implementation/
|
||||
- /doc/Qrexec3Implementation/
|
||||
- /wiki/Qrexec3Implementation/
|
||||
---
|
||||
|
||||
Command execution in VM (and Qubes RPC)
|
||||
=======================================
|
||||
# Command execution in VMs #
|
||||
|
||||
*[Note: this documents describes Qrexec v3 (Odyssey)]*
|
||||
(*This page is about qrexec v3. For qrexec v2, see
|
||||
[here](/doc/qrexec2/).*)
|
||||
|
||||
Qrexec framework is used by core Qubes components to implement communication between domains. Qubes domains are isolated by design but there is a need for a mechanism to allow administrative domain (dom0) to force command execution in another domain (VM). For instance, when user selects an application from KDE menu, it should be started in the selected VM. Also it is often useful to be able to pass stdin/stdout/stderr from an application running in VM to dom0 (and the other way around). In specific circumstances Qubes allows VMs to be initiators of such communication (so for example a VM can notify dom0 that there are updates available for it).
|
||||
The **qrexec** framework is used by core Qubes components to implement
|
||||
communication between domains. Qubes domains are isolated by design, but
|
||||
there is a need for a mechanism to allow the administrative domain (dom0) to
|
||||
force command execution in another domain (VM). For instance, when user
|
||||
selects an application from the KDE menu, it should be started in the selected
|
||||
VM. Also, it is often useful to be able to pass stdin/stdout/stderr from an
|
||||
application running in a VM to dom0 (and the other way around). In specific
|
||||
circumstances, Qubes allows VMs to be initiators of such communications (so,
|
||||
for example, a VM can notify dom0 that there are updates available for it).
|
||||
|
||||
Qrexec basics
|
||||
-------------
|
||||
|
||||
Qrexec is built on top of vchan (a library providing data links between VMs). During domain creation a process named *qrexec-daemon* is started in dom0, and a process named *qrexec-agent* is started in the VM. They are connected over *vchan* channel. *qrexec-daemon* listens for connections from dom0 utility named *qrexec-client*. Typically, the first thing that a *qrexec-client* instance does is to send a request to *qrexec-daemon* to start a process (let's name it VMprocess) with a given command line in a specified VM (someVM). *qrexec-daemon* assigns unique vchan connection details and sends them both to *qrexec-client* (in dom0) and *qrexec-agent* (in someVM). *qrexec-client* starts a vchan server which *qrexec-agent* connects to. Since then, stdin/stdout/stderr from the VMprocess is passed via vchan between *qrexec-agent* and the *qrexec-client* process.
|
||||
## Qrexec basics ##
|
||||
|
||||
So, for example, executing in dom0
|
||||
Qrexec is built on top of vchan (a library providing data links between
|
||||
VMs). During domain creation a process named `qrexec-daemon` is started
|
||||
in dom0, and a process named `qrexec-agent` is started in the VM. They are
|
||||
connected over **vchan** channel. `qrexec-daemon` listens for connections
|
||||
from dom0 utility named `qrexec-client`. Typically, the first thing that a
|
||||
`qrexec-client` instance does is to send a request to `qrexec-daemon` to
|
||||
start a process (let's name it `VMprocess`) with a given command line in
|
||||
a specified VM (`someVM`). `qrexec-daemon` assigns unique vchan connection
|
||||
details and sends them both to `qrexec-client` (in dom0) and `qrexec-agent`
|
||||
(in `someVM`). `qrexec-client` starts a vchan server which `qrexec-agent`
|
||||
connects to. Since then, stdin/stdout/stderr from the VMprocess is passed
|
||||
via vchan between `qrexec-agent` and the `qrexec-client` process.
|
||||
|
||||
`qrexec-client -d someVM user:bash`
|
||||
So, for example, executing in dom0:
|
||||
|
||||
allows to work with the remote shell. The string before the first semicolon specifies what user to run the command as. Adding `-e` on the *qrexec-client* command line results in mere command execution (no data passing), and *qrexec-client* exits immediately after sending the execution request and receiving status code from *qrexec-agent* (whether the process creation succeeded). There is also the `-l local_program` flag -- with it, *qrexec-client* passes stdin/stdout of the remote process to the (spawned for this purpose) *local\_program*, not to its own stdin/stdout.
|
||||
qrexec-client -d someVM user:bash
|
||||
|
||||
The `qvm-run` command is heavily based on *qrexec-client*. It also takes care of additional activities, e.g. starting the domain if it is not up yet and starting the GUI daemon. Thus, it is usually more convenient to use `qvm-run`.
|
||||
allows to work with the remote shell. The string before the first
|
||||
semicolon specifies what user to run the command as. Adding `-e` on the
|
||||
`qrexec-client` command line results in mere command execution (no data
|
||||
passing), and `qrexec-client` exits immediately after sending the execution
|
||||
request and receiving status code from `qrexec-agent` (whether the process
|
||||
creation succeeded). There is also the `-l local_program` flag -- with it,
|
||||
`qrexec-client` passes stdin/stdout of the remote process to the (spawned
|
||||
for this purpose) `local_program`, not to its own stdin/stdout.
|
||||
|
||||
There can be almost arbitrary number of *qrexec-client* processes for a domain (so, connected to the same *qrexec-daemon*, same domain) -- their data is multiplexed independently. Number of available vchan channels is the limiting factor here, it depends on the underlying hypervisor.
|
||||
The `qvm-run` command is heavily based on `qrexec-client`. It also takes care
|
||||
of additional activities, e.g. starting the domain if it is not up yet and
|
||||
starting the GUI daemon. Thus, it is usually more convenient to use `qvm-run`.
|
||||
|
||||
Qubes RPC services
|
||||
------------------
|
||||
There can be almost arbitrary number of `qrexec-client` processes for a
|
||||
domain (so, connected to the same `qrexec-daemon`, same domain) -- their
|
||||
data is multiplexed independently. Number of available vchan channels is
|
||||
the limiting factor here, it depends on the underlying hypervisor.
|
||||
|
||||
Some tasks (like intervm file copy) share the same rpc-like structure: a process in one VM (say, file sender) needs to invoke and send/receive data to some process in other VM (say, file receiver). Thus, the Qubes RPC framework was created, facilitating such actions.
|
||||
|
||||
Obviously, inter-VM communication must be tightly controlled to prevent one VM from taking control over other, possibly more privileged, VM. Therefore the design decision was made to pass all control communication via dom0, that can enforce proper authorization. Then, it is natural to reuse the already-existing qrexec framework.
|
||||
## Qubes RPC services ##
|
||||
|
||||
Also, note that bare qrexec provides VM\<-\>dom0 connectivity, but the command execution is always initiated by dom0. There are cases when VM needs to invoke and send data to a command in dom0 (e.g. to pass information on newly installed .desktop files). Thus, the framework allows dom0 to be the rpc target as well.
|
||||
Some tasks (like inter-vm file copy) share the same rpc-like structure:
|
||||
a process in one VM (say, file sender) needs to invoke and send/receive
|
||||
data to some process in other VM (say, file receiver). Thus, the Qubes RPC
|
||||
framework was created, facilitating such actions.
|
||||
|
||||
Thanks to the framework, RPC programs are very simple -- both rpc client and server just use their stdin/stdout to pass data. The framework does all the inner work to connect these processes to each other via *qrexec-daemon* and *qrexec-agent*. Additionally, disposable VMs are tightly integrated -- rpc to a disposableVM is identical to rpc to a normal domain, all one needs is to pass "\$dispvm" as the remote domain name.
|
||||
Obviously, inter-VM communication must be tightly controlled to prevent one
|
||||
VM from taking control over other, possibly more privileged, VM. Therefore
|
||||
the design decision was made to pass all control communication via dom0,
|
||||
that can enforce proper authorization. Then, it is natural to reuse the
|
||||
already-existing qrexec framework.
|
||||
|
||||
Qubes RPC administration
|
||||
------------------------
|
||||
Also, note that bare qrexec provides `VM <-> dom0` connectivity, but the
|
||||
command execution is always initiated by dom0. There are cases when VM needs
|
||||
to invoke and send data to a command in dom0 (e.g. to pass information on
|
||||
newly installed `.desktop` files). Thus, the framework allows dom0 to be
|
||||
the rpc target as well.
|
||||
|
||||
[TODO: fix for non-linux dom0]
|
||||
Thanks to the framework, RPC programs are very simple -- both rpc client
|
||||
and server just use their stdin/stdout to pass data. The framework does all
|
||||
the inner work to connect these processes to each other via `qrexec-daemon`
|
||||
and `qrexec-agent`. Additionally, disposable VMs are tightly integrated --
|
||||
rpc to a DisposableVM is identical to rpc to a normal domain, all one needs
|
||||
is to pass `$dispvm` as the remote domain name.
|
||||
|
||||
In dom0, there is a bunch of files in */etc/qubes-rpc/policy* directory, whose names describe the available rpc actions; their content is the rpc access policy database. Currently defined actions are:
|
||||
|
||||
- qubes.Filecopy
|
||||
- qubes.OpenInVM
|
||||
- qubes.ReceiveUpdates
|
||||
- qubes.SyncAppMenus
|
||||
- qubes.VMShell
|
||||
- qubes.ClipboardPaste
|
||||
- qubes.Gpg
|
||||
- qubes.NotifyUpdates
|
||||
- qubes.PdfConvert
|
||||
## Qubes RPC administration ##
|
||||
|
||||
(*TODO: fix for non-linux dom0*)
|
||||
|
||||
In dom0, there is a bunch of files in `/etc/qubes-rpc/policy` directory,
|
||||
whose names describe the available rpc actions. Their content is the rpc
|
||||
access policy database. Currently defined actions are:
|
||||
|
||||
qubes.Filecopy
|
||||
qubes.OpenInVM
|
||||
qubes.ReceiveUpdates
|
||||
qubes.SyncAppMenus
|
||||
qubes.VMShell
|
||||
qubes.ClipboardPaste
|
||||
qubes.Gpg
|
||||
qubes.NotifyUpdates
|
||||
qubes.PdfConvert
|
||||
|
||||
These files contain lines with the following format:
|
||||
|
||||
srcvm destvm (allow|deny|ask)[,user=user\_to\_run\_as][,target=VM\_to\_redirect\_to]
|
||||
srcvm destvm (allow|deny|ask)[,user=user_to_run_as][,target=VM_to_redirect_to]
|
||||
|
||||
You can specify srcvm and destvm by name, or by one of "\$anyvm", "\$dispvm", "dom0" reserved keywords (note string "dom0" does not match the \$anyvm pattern; all other names do). Only "\$anyvm" keyword makes sense in srcvm field (service calls from dom0 are currently always allowed, "\$dispvm" means "new VM created for this particular request" - so it is never a source of request). Currently there is no way to specify source VM by type. Whenever a rpc request for action X is received, the first line in /etc/qubes-rpc/policy/X that match srcvm/destvm is consulted to determine whether to allow rpc, what user account the program should run in target VM under, and what VM to redirect the execution to. If the policy file does not exits, user is prompted to create one; if still there is no policy file after prompting, the action is denied.
|
||||
You can specify srcvm and destvm by name, or by one of `$anyvm`, `$dispvm`,
|
||||
`dom0` reserved keywords (note string `dom0` does not match the `$anyvm`
|
||||
pattern; all other names do). Only `$anyvm` keyword makes sense in srcvm
|
||||
field (service calls from dom0 are currently always allowed, `$dispvm`
|
||||
means "new VM created for this particular request," so it is never a
|
||||
source of request). Currently there is no way to specify source VM by
|
||||
type. Whenever a rpc request for action X is received, the first line in
|
||||
`/etc/qubes-rpc/policy/X` that match srcvm/destvm is consulted to determine
|
||||
whether to allow rpc, what user account the program should run in target
|
||||
VM under, and what VM to redirect the execution to. If the policy file does
|
||||
not exits, user is prompted to create one; if still there is no policy file
|
||||
after prompting, the action is denied.
|
||||
|
||||
In the target VM, the */etc/qubes-rpc/RPC\_ACTION\_NAME* must exist, containing the file name of the program that will be invoked.
|
||||
In the target VM, the `/etc/qubes-rpc/RPC_ACTION_NAME` must exist, containing
|
||||
the file name of the program that will be invoked.
|
||||
|
||||
In the src VM, one should invoke the client via
|
||||
In the src VM, one should invoke the client via:
|
||||
|
||||
`/usr/lib/qubes/qrexec-client-vm target_vm_name RPC_ACTION_NAME rpc_client_path client arguments`
|
||||
/usr/lib/qubes/qrexec-client-vm target_vm_name RPC_ACTION_NAME rpc_client_path client arguments
|
||||
|
||||
Note that only stdin/stdout is passed between rpc server and client -- notably, no command line argument are passed. Source VM name is specified by QREXEC\_REMOTE\_DOMAIN environment variable. By default, stderr of client and server is logged to respective /var/log/qubes/qrexec.XID files.
|
||||
Note that only stdin/stdout is passed between rpc server and client --
|
||||
notably, no command line argument are passed. Source VM name is specified by
|
||||
`QREXEC_REMOTE_DOMAIN` environment variable. By default, stderr of client
|
||||
and server is logged to respective `/var/log/qubes/qrexec.XID` files.
|
||||
|
||||
Be very careful when coding and adding a new rpc service. Unless the offered functionality equals full control over the target (it is the case with e.g. qubes.VMShell action), any vulnerability in a rpc server can be fatal to qubes security. On the other hand, this mechanism allows to delegate processing of untrusted input to less privileged (or throwaway) AppVMs, thus wise usage of it increases security.
|
||||
Be very careful when coding and adding a new rpc service. Unless the
|
||||
offered functionality equals full control over the target (it is the case
|
||||
with e.g. `qubes.VMShell` action), any vulnerability in a rpc server can
|
||||
be fatal to Qubes security. On the other hand, this mechanism allows to
|
||||
delegate processing of untrusted input to less privileged (or throwaway)
|
||||
AppVMs, thus wise usage of it increases security.
|
||||
|
||||
### Revoking "Yes to All" authorization
|
||||
|
||||
Qubes RPC policy supports "ask" action. This will prompt the user whether given RPC call should be allowed. That prompt window has also "Yes to All" option, which will allow the action and add new entry to the policy file, which will unconditionally allow further calls for given service-srcVM-dstVM tuple.
|
||||
### Revoking "Yes to All" authorization ###
|
||||
|
||||
In order to remove such authorization, issue this command from a Dom0 terminal (for qubes.Filecopy service):
|
||||
Qubes RPC policy supports "ask" action. This will prompt the user whether given
|
||||
RPC call should be allowed. That prompt window has also "Yes to All" option,
|
||||
which will allow the action and add new entry to the policy file, which will
|
||||
unconditionally allow further calls for given service-srcVM-dstVM tuple.
|
||||
|
||||
`sudo nano /etc/qubes-rpc/policy/qubes.Filecopy`
|
||||
In order to remove such authorization, issue this command from a dom0 terminal
|
||||
(for `qubes.Filecopy` service):
|
||||
|
||||
and then remove the first line/s (before the first \#\# comment) which are the "Yes to All" results.
|
||||
sudo nano /etc/qubes-rpc/policy/qubes.Filecopy
|
||||
|
||||
### Qubes RPC example
|
||||
and then remove the first line(s) (before the first `##` comment) which are
|
||||
the "Yes to All" results.
|
||||
|
||||
We will show the necessary files to create rpc call that adds two integers on the target and returns back the result to the invoker.
|
||||
|
||||
- rpc client code (*/usr/bin/our\_test\_add\_client*)
|
||||
### Qubes RPC example ###
|
||||
|
||||
~~~
|
||||
#!/bin/sh
|
||||
echo $1 $2 # pass data to rpc server
|
||||
exec cat >&$SAVED_FD_1 # print result to the original stdout, not to the other rpc endpoint
|
||||
~~~
|
||||
We will show the necessary files to create rpc call that adds two integers
|
||||
on the target and returns back the result to the invoker.
|
||||
|
||||
- rpc server code (*/usr/bin/our\_test\_add\_server*)
|
||||
* rpc client code (`/usr/bin/our_test_add_client`):
|
||||
|
||||
~~~
|
||||
#!/bin/sh
|
||||
read arg1 arg2 # read from stdin, which is received from the rpc client
|
||||
echo $(($arg1+$arg2)) # print to stdout - so, pass to the rpc client
|
||||
~~~
|
||||
#!/bin/sh
|
||||
echo $1 $2 # pass data to rpc server
|
||||
exec cat >&$SAVED_FD_1 # print result to the original stdout, not to the other rpc endpoint
|
||||
|
||||
- policy file in dom0 (*/etc/qubes-rpc/policy/test.Add* )
|
||||
* rpc server code (*/usr/bin/our\_test\_add\_server*)
|
||||
|
||||
~~~
|
||||
$anyvm $anyvm ask
|
||||
~~~
|
||||
#!/bin/sh
|
||||
read arg1 arg2 # read from stdin, which is received from the rpc client
|
||||
echo $(($arg1+$arg2)) # print to stdout - so, pass to the rpc client
|
||||
|
||||
- server path definition ( */etc/qubes-rpc/test.Add*)
|
||||
* policy file in dom0 (*/etc/qubes-rpc/policy/test.Add* )
|
||||
|
||||
~~~
|
||||
/usr/bin/our_test_add_server
|
||||
~~~
|
||||
$anyvm $anyvm ask
|
||||
|
||||
- invoke rpc via
|
||||
* server path definition ( */etc/qubes-rpc/test.Add*)
|
||||
|
||||
~~~
|
||||
/usr/lib/qubes/qrexec-client-vm target_vm test.Add /usr/bin/our_test_add_client 1 2
|
||||
~~~
|
||||
/usr/bin/our_test_add_server
|
||||
|
||||
* invoke rpc via
|
||||
|
||||
/usr/lib/qubes/qrexec-client-vm target_vm test.Add /usr/bin/our_test_add_client 1 2
|
||||
|
||||
and we should get "3" as answer, after dom0 allows it.
|
||||
|
||||
Qubes RPC internals
|
||||
-------------------
|
||||
**Note:** For a real world example of writing a qrexec service, see this
|
||||
[blog post](http://theinvisiblethings.blogspot.com/2013/02/converting-untrusted-pdfs-into-trusted.html).
|
||||
|
||||
|
||||
## Qubes RPC internals ##
|
||||
|
||||
(*This is about the implementation of qrexec v3. For the implementation of
|
||||
qrexec v2, see [here](/doc/qrexec2/).*)
|
||||
|
||||
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`, then `qrexec-agent`).
|
||||
* 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
|
||||
to `qrexec-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 by `qrexec-policy`. Details described below in
|
||||
the service example.
|
||||
* `cmdline`: Command line to pass to `qrexec-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
|
||||
connected `qrexec-daemon` in dom0.
|
||||
* Executing associated `qrexec-daemon` execution/service requests.
|
||||
* Command line parameters: none.
|
||||
* `qrexec-client-vm`: Runs in an active domain. Used to pass service requests
|
||||
to `qrexec-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 literal `DEFAULT`. In that case,
|
||||
default Qubes user will be used to execute `some_command`.
|
||||
|
||||
- **dom0**: `qrexec-client` sets `QREXEC_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 with
|
||||
`qrexec-agent` later.
|
||||
- **dom0**: `qrexec-client` connects to **domX**'s `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-daemon` sends `MSG_HELLO` header followed by `peer_info`
|
||||
to `qrexec-client`.
|
||||
- **dom0**: `qrexec-client` replies with `MSG_HELLO` header followed by
|
||||
`peer_info` to `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-client` sends `MSG_EXEC_CMDLINE` header followed by
|
||||
`exec_params` to `qrexec-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 to `qrexec-client` with
|
||||
`MSG_EXEC_CMDLINE` header followed by `exec_params`, but with empty `cmdline`
|
||||
field. `connect_domain` is set to Qubes ID of **domX** and `connect_port`
|
||||
is set to a vchan port allocated by `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-daemon` sends `MSG_EXEC_CMDLINE` header followed
|
||||
by `exec_params` to the associated **domX** `qrexec-agent` over
|
||||
vchan. `connect_domain` is set to 0 (**dom0**), `connect_port` is the same
|
||||
as sent to `qrexec-client`. `cmdline` is unchanged except that the literal
|
||||
`DEFAULT` is replaced with actual user name, if present.
|
||||
- **dom0**: `qrexec-client` disconnects from `qrexec-daemon`.
|
||||
- **dom0**: `qrexec-client` starts a vchan server using the details received
|
||||
from `qrexec-daemon` and waits for connection from **domX**'s `qrexec-agent`.
|
||||
- **domX**: `qrexec-agent` receives `MSG_EXEC_CMDLINE` header followed by
|
||||
`exec_params` from `qrexec-daemon` over vchan.
|
||||
- **domX**: `qrexec-agent` connects to `qrexec-client` over vchan using the
|
||||
details from `exec_params`.
|
||||
- **domX**: `qrexec-agent` executes `some_command` in **domX** and connects
|
||||
the child's stdin/stdout to the data vchan. If the process creation fails,
|
||||
`qrexec-agent` sends `MSG_DATA_EXIT_CODE` to `qrexec-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 using
|
||||
`MSG_DATA_STDOUT` by `qrexec-agent`. `qrexec-client` passes data received as
|
||||
`MSG_DATA_STDOUT` to its own stdout (or to `local_program`'s stdin if used).
|
||||
- `qrexec-client` sends data read from local stdin (or `local_program`'s
|
||||
stdout if used) to `qrexec-agent` over the data vchan using
|
||||
`MSG_DATA_STDIN`. `qrexec-agent` passes data received as `MSG_DATA_STDIN`
|
||||
to `some_command`'s stdin.
|
||||
- `MSG_DATA_STDOUT` or `MSG_DATA_STDIN` with data `len` field set to 0 in
|
||||
`msg_header` is an EOF marker. Peer receiving such message should close the
|
||||
associated input/output pipe.
|
||||
- When `some_command` terminates, **domX**'s `qrexec-agent` sends
|
||||
`MSG_DATA_EXIT_CODE` header to `qrexec-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 to `qrexec-agent` (via local
|
||||
socket/named pipe).
|
||||
- **domY**: `qrexec-client-vm` sends `trigger_service_params` data to
|
||||
`qrexec-agent` (without filling the `request_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 say `13`) and fills it in the `trigger_service_params`
|
||||
struct received from `qrexec-client-vm`.
|
||||
- **domY**: `qrexec-agent` sends `MSG_TRIGGER_SERVICE` header followed by
|
||||
`trigger_service_params` to `qrexec-daemon` in **dom0** via vchan.
|
||||
- **dom0**: **domY**'s `qrexec-daemon` executes `qrexec-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 returns `0`, if the action is denied it
|
||||
returns `1`.
|
||||
- **dom0**: **domY**'s `qrexec-daemon` checks the exit code of `qrexec-policy`.
|
||||
- If `qrexec-policy` returned **not** `0`: **domY**'s `qrexec-daemon`
|
||||
sends `MSG_SERVICE_REFUSED` header followed by `service_params` to
|
||||
**domY**'s `qrexec-agent`. `service_params.ident` is identical to the one
|
||||
received. **domY**'s `qrexec-agent` disconnects its `qrexec-client-vm`
|
||||
and RPC processing is finished.
|
||||
- If `qrexec-policy` returned `0`, RPC processing continues.
|
||||
- **dom0**: if `qrexec-policy` allowed the RPC, it executed `qrexec-client
|
||||
-d domX -c 13,domY,domY_id user:QUBESRPC qubes.SomeRpc domY`.
|
||||
- **dom0**: `qrexec-client` sets `QREXEC_REMOTE_DOMAIN` environment variable
|
||||
to **domX**.
|
||||
- **dom0**: `qrexec-client` connects to **domX**'s `qrexec-daemon`.
|
||||
- **dom0**: **domX**'s `qrexec-daemon` sends `MSG_HELLO` header followed by
|
||||
`peer_info` to `qrexec-client`.
|
||||
- **dom0**: `qrexec-client` replies with `MSG_HELLO` header followed by
|
||||
`peer_info` to **domX**'s`qrexec-daemon`.
|
||||
- **dom0**: `qrexec-client` sends `MSG_EXEC_CMDLINE` header followed by
|
||||
`exec_params` to **domX**'s`qrexec-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 to `qrexec-client` with
|
||||
`MSG_EXEC_CMDLINE` header followed by `exec_params`, but with empty `cmdline`
|
||||
field. `connect_domain` is set to Qubes ID of **domX** and `connect_port`
|
||||
is set to a vchan port allocated by **domX**'s `qrexec-daemon`.
|
||||
- **dom0**: **domX**'s `qrexec-daemon` sends `MSG_EXEC_CMDLINE` header
|
||||
followed by `exec_params` to **domX**'s `qrexec-agent`. `connect_domain`
|
||||
and `connect_port` fields are the same as in the step above. `cmdline` is
|
||||
set to the one received from `qrexec-client`, in this case `user:QUBESRPC
|
||||
qubes.SomeRpc domY`.
|
||||
- **dom0**: `qrexec-client` disconnects from **domX**'s `qrexec-daemon`
|
||||
after receiving connection details.
|
||||
- **dom0**: `qrexec-client` connects to **domY**'s `qrexec-daemon` and
|
||||
exchanges `MSG_HELLO` as usual.
|
||||
- **dom0**: `qrexec-client` sends `MSG_SERVICE_CONNECT` header followed by
|
||||
`exec_params` to **domY**'s `qrexec-daemon`. `connect_domain` is set to ID
|
||||
of **domX** (received from **domX**'s `qrexec-daemon`) and `connect_port` is
|
||||
the one received as well. `cmdline` is set to request ID (`13` in this case).
|
||||
- **dom0**: **domY**'s `qrexec-daemon` sends `MSG_SERVICE_CONNECT` header
|
||||
followed by `exec_params` to **domY**'s `qrexec-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 a `qrexec-client` in this case because this is
|
||||
a VM-VM connection.
|
||||
- **domX**: `qrexec-agent` connects to the vchan server of **domY**'s
|
||||
`qrexec-agent` (connection details were received before from **domX**'s
|
||||
`qrexec-daemon`).
|
||||
- After that, connection follows the flow of the previous example (dom0-VM).
|
||||
|
||||
See [QrexecProtocol?](/doc/QrexecProtocol/).
|
||||
|
@ -14,11 +14,10 @@ System Documentation for Developers
|
||||
Fundamentals
|
||||
------------
|
||||
* [Qubes OS Architecture Overview](/doc/qubes-architecture/)
|
||||
* [Qubes OS Architecture Spec v0.3 [PDF]](/attachment/wiki/QubesArchitecture/arch-spec-0.3.pdf) (The original 2009 document that started this all...)
|
||||
* [Qubes OS Architecture Spec v0.3 [PDF]](/attachment/wiki/QubesArchitecture/arch-spec-0.3.pdf)
|
||||
(The original 2009 document that started this all...)
|
||||
* [Security-critical elements of Qubes OS](/doc/security-critical-code/)
|
||||
* Qubes RPC: [`qrexec` v2](/doc/qrexec/) ([R2 implementation](/doc/qrexec2-implementation/))
|
||||
* Qubes RPC: [`qrexec` v3](/doc/qrexec3/) ([R3 implementation](/doc/qrexec3-implementation/)) (Odyssey)
|
||||
* [Example for writing a `qrexec` service in Qubes OS (blog post)](http://theinvisiblethings.blogspot.com/2013/02/converting-untrusted-pdfs-into-trusted.html)
|
||||
* [Qrexec: command execution in VMs](/doc/qrexec3/)
|
||||
* [Qubes GUI virtualization protocol](/doc/gui/)
|
||||
* [Networking in Qubes](/doc/qubes-net/)
|
||||
* [Implementation of template sharing and updating](/doc/template-implementation/)
|
||||
|
Loading…
Reference in New Issue
Block a user