Qrexec changed

Major reorganization, split into user/API doc and implementation doc, make readable to human beings
This commit is contained in:
Joanna Rutkowska 2014-10-04 19:05:14 +00:00
parent 7bb29b5da0
commit ca5237d9e6

150
Qrexec.md
View File

@ -7,38 +7,50 @@ permalink: /wiki/Qrexec/
Command execution in VM (and Qubes RPC)
=======================================
In order to manage VMs easily, there is a need for a mechanism to allow dom0 to force command execution in a VM. For instance, when user selects from KDE menu an application, 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.
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
-------------
Basic Dom0-VM command execution
-------------------------------
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 on the the unix socket */var/run/qubes/qrexec.XID* 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-agent* to start a process (let's name it VMprocess) with a given command line. Since then, stdin/stdout/stderr from the VMprocess is passed via *qrexec-daemon* and *qrexec-agent* to the *qrexec-client* process.
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.
So, for example, executing in dom0
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. Since then, the stdin/stdout/stderr from this remote process is passed to the `qrexec-client` process.
*qrexec-client -d someVM user:bash//*
E.g. to start a primitive shell in a VM type the following in Dom0 console:
allows to work with the remote shell. 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 - with it, *qrexec-client* passes stdin/stdout of the remote program not to its stdin/stdout, but to the (spawned for this purpose) *local\_program*.
``` {.wiki}
[user@dom0 ~]$ /usr/lib/qubes/qrexec-client -d <vm name> user:bash
```
The *qvm-run* command is heavily based on *qrexec-client*. It also takes care for 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*.
The string before first semicolon specifies what user to run the command as.
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.
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.
Qubes RPC basics
----------------
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>`.
Some tasks (like intervm file copy) share the same rpc-like structure: a process in one VM (say, file sender) needs to invoke and pass/receive data to some process in other VM (say, file receiver). Thus, the Qubes RPC framework was written, facilitating such actions.
The `qvm-run` command is heavily based on `qrexec-client`. It also takes care for 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`.
Obviously, such interVM 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 communication via dom0, that can enforce proper authorization. Then, it is natural to reuse the already-existing qrexec framework. As basically it provides only dom0\<-\>VM channel, then we need to glue two qrexec connections in order to provide VM\<-\>VM channel.
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.
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.
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.
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 file descriptors 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.
Qubes Inter-VM Services (Qubes RPC)
-----------------------------------
Qubes RPC administration
------------------------
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 Disposable VM, and requesting it to do certain operations on a handed file(s).
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:
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 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, disposable VMs are tightly integrated - RPC to a DisposableVM 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 be then executed by the qrexec service, if the Dom0 policy allowed 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 takes care about connecting the stdin/stdout of the server provess 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
@ -52,25 +64,38 @@ In dom0, there is a bunch of files in */etc/qubes-rpc/policy* directory, whose n
These files contain lines with the following format:
srcvm destvm (allow|deny|ask)[,user=user\_to\_run\_as][,target=VM\_to\_redirect\_to]
``` {.wiki}
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 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.
On target VM, the */etc/qubes-rpc/RPC\_ACTION\_NAME* must exist, containing the file name of the program that will be invoked.
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 src VM, one should invoke the client via
On the target VM, the `/etc/qubes-rpc/XYZ` must exist, containing the file name of the program that will be invoked.
*/usr/lib/qubes/qrexec-client-vm target\_vm\_name RPC\_ACTION\_NAME rpc\_client\_path client arguments*
Requesting VM-VM (and VM-Dom0) services execution
-------------------------------------------------
Note that only stdin/stdout is passed between rpc server and client - notably, the no cmdline argument are passed. Source VM name is given by QREXEC\_REMOTE\_DOMAIN environment variable. By default, stderr of client and server is logged to respective /var/log/qubes/qrexec.XID files.
On src VM, one should invoke the qrexec client via the follwing command:
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.
``` {.wiki}
/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!
### 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.
Qubes RPC policy supports the "ask" action. This will prompt the user whether a given RPC call should be allowed. That prompt window has an 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 (for qubes.Filecopy service):
In order to remove such authorization, issue this command from a Dom0 terminal (example below for qubes.Filecopy service):
``` {.wiki}
sudo nano /etc/qubes-rpc/policy/qubes.Filecopy
@ -78,59 +103,50 @@ sudo nano /etc/qubes-rpc/policy/qubes.Filecopy
and then remove the first line/s (before the first \#\# comment) which are the "Yes to All" results.
### Qubes RPC example
### Qubes RPC "Hello World" service
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.
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.
- rpc client code (*/usr/bin/our\_test\_add\_client*)
- Client code on source VM (`/usr/bin/our_test_add_client`)
``` {.wiki}
#!/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
```
``` {.wiki}
#!/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
```
- rpc server code (*/usr/bin/our\_test\_add\_server*)
- Server code on target VM (`/usr/bin/our_test_add_server`)
``` {.wiki}
#!/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
```
``` {.wiki}
#!/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* )
- Policy file in dom0 (`/etc/qubes-rpc/policy/test.Add`)
``` {.wiki}
$anyvm $anyvm ask
```
``` {.wiki}
$anyvm $anyvm ask
```
- server path definition ( */etc/qubes-rpc/test.Add*)
- Server path definition on target VM (`/etc/qubes-rpc/test.Add`)
``` {.wiki}
/usr/bin/our_test_add_server
```
``` {.wiki}
/usr/bin/our_test_add_server
```
- invoke rpc via
- To test this service, run the following in the source VM:
``` {.wiki}
/usr/lib/qubes/qrexec-client-vm target_vm test.Add /usr/bin/our_test_add_client 1 2
```
``` {.wiki}
/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.
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?
---------------------
Qubes RPC internals
-------------------
When an user in VM executes the */usr/lib/qubes/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 CID* 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.
[![No image "qubes\_rpc.png" attached to Qrexec](/chrome/common/attachment.png "No image "qubes_rpc.png" attached to Qrexec")](/attachment/wiki/Qrexec/qubes_rpc.png)
The internal implementation of qrexec in Qubes R2 is described [here](/wiki/Qrexec2Implementation), and in Qubes R3 [here](/wiki/Qrexec3Implementation).