Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new device API #579

Merged
merged 49 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
14e431f
q-dev: keep one DeviceInfo API
piotrbartman Nov 27, 2023
69adb22
q-dev: simplify parent_device
piotrbartman Dec 29, 2023
471e6a5
q-dev: DeviceInterface
piotrbartman Dec 30, 2023
7632208
q-dev: pci devices draft
piotrbartman Dec 30, 2023
7708add
q-dev: split persistent
piotrbartman Jan 8, 2024
d1f333f
q-dev: return DeviceAssignment in get_attached_devices
piotrbartman Jan 21, 2024
a113b12
q-dev: readable serialization
piotrbartman Jan 23, 2024
81c8c3e
q-dev: device-{added,removed}:block
piotrbartman Jan 25, 2024
7febeff
q-dev: DeviceAssignment serialization
piotrbartman Jan 29, 2024
2b033d4
q-dev: full_identity of device
piotrbartman Jan 30, 2024
33bb3f2
q-dev: development of auto-attach and required flags
piotrbartman Jan 31, 2024
4a426e6
q-dev: implementation of DeviceInfo.attachment
piotrbartman Feb 1, 2024
cb8837b
q-dev: move preventing of non-pci device as required to client
piotrbartman Feb 12, 2024
ab9b205
q-dev: rename full_identity -> self_identity
piotrbartman Feb 17, 2024
6735ea3
q-dev: block devices
piotrbartman Feb 19, 2024
d430f7f
q-dev: update docs
piotrbartman Feb 21, 2024
eae86a6
q-dev: update tests
piotrbartman Feb 21, 2024
39e9408
q-dev: typo in doc/qubes-devices.rst
piotrbartman Mar 15, 2024
6e2faf0
q-dev: fix path
piotrbartman Mar 17, 2024
28f1789
q-dev: fix block device events
piotrbartman Feb 21, 2024
9796c57
q-dev: extract ext/utils
piotrbartman Mar 19, 2024
ad35d13
q-dev: device protocol
piotrbartman Mar 19, 2024
cd2515f
q-dev: @marmarek comments
piotrbartman Mar 20, 2024
76f5538
q-dev: device protocol refactor + tests
piotrbartman Mar 20, 2024
da079f2
q-dev: update docs
piotrbartman Mar 24, 2024
b89a89c
q-dev: broder allowance of names
piotrbartman Mar 25, 2024
0193c0a
q-dev: attaching block dev refactor
piotrbartman Mar 25, 2024
af8996f
q-dev: set.assignment -> set.required
piotrbartman Mar 26, 2024
9dec436
q-dev: fix auto-attachment of block devices
piotrbartman Apr 9, 2024
00256e4
q-dev: typos
piotrbartman Apr 19, 2024
8da3246
q-dev: fix memory balancing
piotrbartman Apr 22, 2024
54b9f9b
q-dev: update device_protocol
piotrbartman Apr 23, 2024
42e062a
q-dev: update integration tests
piotrbartman Apr 23, 2024
4a13df1
q-dev: update integration tests fo block devices
piotrbartman Apr 23, 2024
fe36156
q-dev: fix serialization
piotrbartman Apr 28, 2024
3527f7b
q-dev: simplify pack properties
piotrbartman Apr 30, 2024
320f246
q-dev: more restrictive key allowance
piotrbartman Apr 30, 2024
c347bfe
q-dev: do not load block devices in offline mode
piotrbartman May 8, 2024
2969817
q-dev: update tests and minor fixes
piotrbartman May 8, 2024
8fba2be
q-dev: allow an unassignment of required device + typos
piotrbartman May 8, 2024
cd2cc2f
q-dev: update test
piotrbartman May 8, 2024
ba66cba
q-dev: remove leftover debug print
piotrbartman May 9, 2024
93f60d5
q-dev: add block and pci tests to improve testcov + typos
piotrbartman May 13, 2024
fd84f65
q-dev: add more block tests + fixes
piotrbartman May 13, 2024
f483e60
q-dev: make pylint happy
piotrbartman May 14, 2024
3313291
q-dev: restore attach_and_notify
piotrbartman May 14, 2024
9e52556
q-dev: get parent device directly from vm
piotrbartman May 16, 2024
5d0ea72
q-dev: make pylint happy
piotrbartman May 16, 2024
58b7872
q-dev: keep description as in `lspci/lsusb`
piotrbartman May 18, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -60,26 +60,38 @@ ADMIN_API_METHODS_SIMPLE = \
admin.vm.Shutdown \
admin.vm.Start \
admin.vm.Unpause \
admin.vm.device.pci.Assign \
admin.vm.device.pci.Assigned \
admin.vm.device.pci.Attach \
admin.vm.device.pci.Attached \
admin.vm.device.pci.Available \
admin.vm.device.pci.Detach \
admin.vm.device.pci.List \
admin.vm.device.pci.Set.persistent \
admin.vm.device.pci.Set.required \
admin.vm.device.pci.Unassign \
admin.vm.device.block.Assign \
admin.vm.device.block.Assigned \
admin.vm.device.block.Attach \
admin.vm.device.block.Attached \
admin.vm.device.block.Available \
admin.vm.device.block.Detach \
admin.vm.device.block.List \
admin.vm.device.block.Set.persistent \
admin.vm.device.block.Set.required \
admin.vm.device.block.Unassign \
admin.vm.device.usb.Assign \
admin.vm.device.usb.Assigned \
admin.vm.device.usb.Attach \
admin.vm.device.usb.Attached \
admin.vm.device.usb.Available \
admin.vm.device.usb.Detach \
admin.vm.device.usb.List \
admin.vm.device.usb.Set.persistent \
admin.vm.device.usb.Set.required \
admin.vm.device.usb.Unassign \
admin.vm.device.mic.Assign \
admin.vm.device.mic.Assigned \
admin.vm.device.mic.Attach \
admin.vm.device.mic.Attached \
admin.vm.device.mic.Available \
admin.vm.device.mic.Detach \
admin.vm.device.mic.List \
admin.vm.device.mic.Set.persistent \
admin.vm.device.mic.Set.required \
admin.vm.device.mic.Unassign \
admin.vm.feature.CheckWithNetvm \
admin.vm.feature.CheckWithTemplate \
admin.vm.feature.CheckWithAdminVM \
Expand Down Expand Up @@ -211,8 +223,11 @@ endif
admin.vm.CreateInPool.AdminVM \
admin.vm.device.testclass.Attach \
admin.vm.device.testclass.Detach \
admin.vm.device.testclass.List \
admin.vm.device.testclass.Set.persistent \
admin.vm.device.testclass.Assign \
admin.vm.device.testclass.Unassign \
admin.vm.device.testclass.Attached \
admin.vm.device.testclass.Assigned \
admin.vm.device.testclass.Set.required \
admin.vm.device.testclass.Available
install -d $(DESTDIR)/etc/qubes/policy.d/include
install -m 0644 qubes-rpc-policy/admin-local-ro \
Expand Down
125 changes: 125 additions & 0 deletions doc/qubes-devices.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,131 @@
:py:mod:`qubes.devices` -- Devices
===================================

Main concept is that some domain (backend) may expose (potentially multiple)
devices, which can be attached to other domains (frontend). Devices can be of
different buses (like 'pci', 'usb', etc.). Each device bus is implemented by
an extension (see :py:mod:`qubes.ext`).

Devices are identified by pair of (backend domain, `ident`), where `ident` is
:py:class:`str` and can contain only characters from `[a-zA-Z0-9._-]` set.


Device Assignment vs Attachment
-------------------------------

:py:class:`qubes.device_protocol.DeviceAssignment` describes the assignment of a device
to a frontend VM. For clarity let's us introduce two types of assignments:
*potential* and *real* (attachment). Attachment indicates that the device
has been attached by the Qubes backend to its frontend VM and is visible
from its perspective. Potential assignment, on the other hand,
has two additional options: `automatically_attach` and `required`.
For detailed descriptions, refer to the `DeviceAssignment` documentation.
In general we refer to potential assignment as assignment
and real assignment as attachment. To check whether the device is currently
attached, we check :py:meth:`qubes.device_protocol.DeviceAssignment.attached`,
while to check whether an (potential) assignment exists,
we check :py:meth:`qubes.device_protocol.DeviceAssignment.attach_automatically`.
Potential and real connections may coexist at the same time,
in which case both values will be true.


Actions
-------

The `assign` action signifies that a device will be assigned to the frontend VM
in a potential form (this does not change the current system state).
This will result in an attempt to automatically attach the device
upon the next VM startup. If `required=True`, and the device cannot be attached,
the VM startup will fail. Additionally, upon device detection (`device-added`),
an attempt will be made to attach the device. However, at any time
(unless `required=True`), the user can manually modify this state by performing
`attach` or `detach` on the device, changing the current system state.
This will not alter the assignment, and automatic attachment attempts
will still be made in the future. To remove the assignment the user
need to perform `unassign` (see next section).

Assignment Management
---------------------

Assignments can be edited at any time: regardless of whether the VM is running
or the device is currently attached. An exception is `required=True`,
in which case the VM must be shut down. Removing the assignment does not change the real system state, so if the device is currently attached
and the user remove the assignment, it will not be detached,
but it will not be automatically attached in the future.
Similarly, it works the other way around with `assign`.

Proper Assignment States
------------------------

In short, we can think of device assignment in terms of three flags:
#. `attached` - indicating whether the device is currently assigned,
#. `attach_automatically` - indicating whether the device will be
automatically attached by the system daemon,
#. `required` - determining whether the failure of automatic attachment should
result in the domain startup being interrupted.

Then the proper states of assignment
(`attached`, `automatically_attached`, `required`) are as follow:
#. `(True, False, False)` -> domain is running, device is manually attached
and could be manually detach any time.
#. `(True, True, False)` -> domain is running, device is attached
and could be manually detach any time (see 4.),
but in the future will be auto-attached again.
#. `(True, True, True)` -> domain is running, device is attached
and couldn't be detached.
#. `(False, True, False)` -> device is assigned to domain, but not attached
because either (i) domain is halted, device (ii) manually detached or
(iii) attach to different domain.
#. `(False, True, True)` -> domain is halted, device assigned to domain
and required to start domain.


PCI Devices
-----------

PCI devices cannot be manually attached to a VM at any time.
We must first create an assignment (`assign`) as required
(in client we can use `--required` flag) while the VM is turned off.
Then, it will be automatically attached upon each VM startup.
However, if a PCI device is currently in use by another VM,
the startup of the second VM will fail.
PCI devices can only be assigned with the `required=True`, which does not
allow for manual modification of the state during VM operation (attach/detach).

Microphone
----------

The microphone cannot be assigned (potentially) to any VM (attempting to attach the microphone during VM startup fails).

Understanding Device Self Identity
----------------------------------

It is important to understand that :py:class:`qubes.device_protocol.Device` does not
correspond to the device itself, but rather to the *port* to which the device
is connected. Therefore, when assigning a device to a VM, such as
`sys-usb:1-1.1`, the port `1-1.1` is actually assigned, and thus
*every* devices connected to it will be automatically attached.
Similarly, when assigning `vm:sda`, every block device with the name `sda`
will be automatically attached. We can limit this using :py:meth:`qubes.device_protocol.DeviceInfo.self_identity`, which returns a string containing information
presented by the device, such as, `vendor_id`, `product_id`, `serial_number`,
and encoded interfaces. In the case of block devices, `self_identity`
consists of the parent port to which the device is connected (if any),
the parent's `self_identity`, and the interface/partition number.
In practice, this means that, a partition on a USB drive will only be
automatically attached to a frontend domain if the parent presents
the correct serial number etc., and is connected to a specific port.

Port Assignment
---------------

It is possible to not assign a specific device but rather a port,
(e.g., we can use the `--port` flag in the client). In this case,
the value `any` will appear in the `identity` field of the `qubes.xml` file.
This indicates that the identity presented by the devices will be ignored,
and all connected devices will be automatically attached. Note that to create
an assignment, *any* device must currently be connected to the port.


.. automodule:: qubes.devices
:members:
:show-inheritance:
Expand Down
14 changes: 10 additions & 4 deletions qubes-rpc-policy/90-admin-default.policy.header
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,20 @@
!include-service admin.vm.volume.Import * include/admin-local-rwx
!include-service admin.vm.volume.ImportWithSize * include/admin-local-rwx

!include-service admin.vm.device.mic.Assign * include/admin-local-rwx
!include-service admin.vm.device.mic.Assigned * include/admin-local-ro
!include-service admin.vm.device.mic.Attach * include/admin-local-rwx
!include-service admin.vm.device.mic.Attached * include/admin-local-ro
!include-service admin.vm.device.mic.Available * include/admin-local-ro
!include-service admin.vm.device.mic.Detach * include/admin-local-rwx
!include-service admin.vm.device.mic.List * include/admin-local-ro
!include-service admin.vm.device.mic.Set.persistent * include/admin-local-rwx
!include-service admin.vm.device.mic.Set.required * include/admin-local-rwx
!include-service admin.vm.device.mic.Unassign * include/admin-local-rwx
!include-service admin.vm.device.usb.Assign * include/admin-local-rwx
!include-service admin.vm.device.usb.Assigned * include/admin-local-ro
!include-service admin.vm.device.usb.Attach * include/admin-local-rwx
!include-service admin.vm.device.usb.Attached * include/admin-local-ro
!include-service admin.vm.device.usb.Available * include/admin-local-ro
!include-service admin.vm.device.usb.Detach * include/admin-local-rwx
!include-service admin.vm.device.usb.List * include/admin-local-ro
!include-service admin.vm.device.usb.Set.persistent * include/admin-local-rwx
!include-service admin.vm.device.usb.Set.required * include/admin-local-rwx
!include-service admin.vm.device.usb.Unassign * include/admin-local-rwx

18 changes: 6 additions & 12 deletions qubes/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,7 @@
import traceback

import qubes.exc

class ProtocolError(AssertionError):
'''Raised when something is wrong with data received'''


class PermissionDenied(Exception):
'''Raised deliberately by handlers when we decide not to cooperate'''
from qubes.exc import ProtocolError, PermissionDenied


def method(name, *, no_payload=False, endpoints=None, **classifiers):
Expand Down Expand Up @@ -127,8 +121,8 @@ def __init__(self, app, src, method_name, dest, arg, send_event=None):
#: destination qube
self.dest = self.app.domains[vm]
except KeyError:
# normally this should filtered out by qrexec policy, but there are
# two cases it might not be:
# normally this should be filtered out by qrexec policy, but there
# are two cases it might not be:
# 1. The call comes from dom0, which bypasses qrexec policy
# 2. Domain was removed between checking the policy and here
# we inform the client accordingly
Expand Down Expand Up @@ -213,11 +207,11 @@ def enforce(predicate):
def validate_size(self, untrusted_size: bytes) -> int:
self.enforce(isinstance(untrusted_size, bytes))
if not untrusted_size.isdigit():
raise qubes.api.ProtocolError('Size must be ASCII digits (only)')
raise qubes.exc.ProtocolError('Size must be ASCII digits (only)')
if len(untrusted_size) >= 20:
raise qubes.api.ProtocolError('Sizes limited to 19 decimal digits')
raise qubes.exc.ProtocolError('Sizes limited to 19 decimal digits')
if untrusted_size[0] == 48 and untrusted_size != b'0':
raise qubes.api.ProtocolError('Spurious leading zeros not allowed')
raise qubes.exc.ProtocolError('Spurious leading zeros not allowed')
return int(untrusted_size)

class QubesDaemonProtocol(asyncio.Protocol):
Expand Down
Loading