qubes-doc/developer/services/qrexec2.rst
Marek Marczykowski-Górecki b93b3c571e
Convert to RST
2024-05-21 20:59:46 +02:00

417 lines
15 KiB
ReStructuredText
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

======================
Qrexec v2 (deprecated)
======================
(*This page is about qrexec v2. For qrexec v3, see*
:doc:`here </developer/services/qrexec>` *.*)
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:
.. code:: bash
[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 available 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:
.. code:: bash
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:
.. code:: bash
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 exist, 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:
.. code:: bash
/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 variablethe 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):
.. code:: bash
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``)
.. code:: bash
#!/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``)
.. code:: bash
#!/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``)
.. code:: bash
$anyvm $anyvm ask
- Server path definition on target VM (``/etc/qubes-rpc/test.Add``)
.. code:: bash
/usr/bin/our_test_add_server
- To test this service, run the following in the source VM:
.. code:: bash
/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 <https://blog.invisiblethings.org/2013/02/21/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 doesnt 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* :doc:`here </developer/services/qrexec-internals>` *. 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 VMs ``/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 implementation
--------------------------
``%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 :doc:`qrexec framework implemention in Qubes R3 </developer/services/qrexec>` 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:
.. figure:: /attachment/doc/qrexec2-internals.png
:alt: qrexec2-internals.png
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-connected.
- 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)*