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

device identity #614

Open
wants to merge 69 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
458c1c1
q-dev: port
piotrbartman Jul 4, 2024
58ae845
q-dev: attachment confirmation PoC
piotrbartman Aug 27, 2024
0005d08
q-dev: port
piotrbartman Jul 31, 2024
167ec8d
q-dev: comparison
piotrbartman Aug 1, 2024
1d0b2ad
q-dev: assignment
piotrbartman Aug 2, 2024
bfbe0b9
q-dev: ask-to-attach is attach_automatically
piotrbartman Aug 27, 2024
a5a7fdb
q-dev: add device_identity to device assignment
piotrbartman Aug 5, 2024
d7302f3
q-dev: check identity
piotrbartman Aug 5, 2024
e481e22
q-dev: implementation of attachment confirmation
piotrbartman Aug 27, 2024
9d82600
q-dev: auto-attach only required block devices before vm start
piotrbartman Aug 7, 2024
5ea5fc6
q-dev: fix attribute name
piotrbartman Aug 7, 2024
6b031a0
q-dev: backward compatible device_protocol
piotrbartman Aug 7, 2024
9053c70
q-dev: add self_identity do device identity
piotrbartman Aug 8, 2024
ba2100e
q-dev: refactor device_protocol.py
piotrbartman Aug 12, 2024
fd6e4a8
q-dev: fix events
piotrbartman Aug 15, 2024
6dd8f56
q-dev: unify protocol
piotrbartman Aug 15, 2024
1302bf9
q-dev: fix test
piotrbartman Aug 15, 2024
1b2934c
q-dev: virtual device
piotrbartman Aug 15, 2024
c93f8c3
q-dev: device -> devices
piotrbartman Aug 15, 2024
efb572e
q-dev: matches
piotrbartman Aug 16, 2024
e09010e
q-dev: backend_name
piotrbartman Aug 16, 2024
93713ed
q-dev: device_protocol
piotrbartman Aug 17, 2024
af77f91
q-dev: cleanup
piotrbartman Aug 19, 2024
1d26a0e
q-dev: fixes
piotrbartman Aug 19, 2024
c2aac2d
q-dev: deny list
piotrbartman Aug 19, 2024
2514885
q-dev: assignment.device
piotrbartman Aug 20, 2024
9f19108
q-dev: error handling
piotrbartman Aug 20, 2024
56559c0
q-dev: add tests
piotrbartman Aug 20, 2024
64e7669
q-dev: fix block auto-attach
piotrbartman Aug 20, 2024
4aac101
q-dev: add block devices tests
piotrbartman Aug 26, 2024
c7d244a
q-dev: update device_protocol.py
piotrbartman Aug 26, 2024
e73d56d
q-dev: fix tests and make linter happy
piotrbartman Aug 27, 2024
b8bee03
q-dev: update pci tests and cleanup
piotrbartman Aug 27, 2024
d62624f
q-dev: update qubes.rng and fix tests
piotrbartman Aug 28, 2024
63489c1
q-dev: Set.required -> Set.assignment
piotrbartman Aug 28, 2024
97084d6
q-dev: deny list drop ins and comments
piotrbartman Sep 24, 2024
4dbf597
q-dev: do not include port id in device identity
piotrbartman Sep 25, 2024
c91295e
q-dev: add error message
piotrbartman Sep 25, 2024
1999c2f
q-dev: do not load device if device_id does not match
piotrbartman Sep 25, 2024
d76b318
q-dev: do not attach unknown device
piotrbartman Sep 30, 2024
daaf839
q-dev: better error message
piotrbartman Sep 30, 2024
1db8f8f
q-dev: remove redundant list
piotrbartman Oct 1, 2024
1267f3f
q-dev: async confirmation
piotrbartman Oct 7, 2024
89925b0
q-dev: sanitize confirmation output
piotrbartman Oct 7, 2024
a851333
q-dev: fire pre-event for assignment
piotrbartman Oct 8, 2024
53b6bd9
q-dev: fix detaching required devices
piotrbartman Oct 8, 2024
cc65d08
q-dev: remove unused import
piotrbartman Oct 8, 2024
be25600
q-dev: update device_protocol.py
piotrbartman Oct 9, 2024
4539f63
q-dev: update device tests
piotrbartman Oct 9, 2024
2b6dfb8
q-dev: update admin api device tests
piotrbartman Oct 11, 2024
4ff7ae3
q-dev: add encoding type
piotrbartman Oct 14, 2024
fd6bd27
q-dev: minor device_protocol fixes
piotrbartman Oct 14, 2024
e6b70ef
q-dev: introduce AnyPort
piotrbartman Oct 14, 2024
1109708
q-dev: devices improvements
piotrbartman Oct 14, 2024
a3b781c
q-dev: add short way to create DeviceAssignment
piotrbartman Oct 14, 2024
1cd1c88
q-dev: rename attach-confirm -> qubes-device-attach-confirm
piotrbartman Oct 14, 2024
9bdeb52
q-dev: keep consistency in fire_event_for_permission
piotrbartman Oct 15, 2024
bee1797
q-dev: fix conflicted attachments
piotrbartman Oct 16, 2024
6dec58d
q-dev: wait for attaching devices during startup and update tests
piotrbartman Oct 17, 2024
5d49926
q-dev: pylint + black
piotrbartman Oct 18, 2024
79001ff
q-dev: fix deny list
piotrbartman Oct 18, 2024
5b38d7f
q-dev: call attach-confirm socket directly
piotrbartman Oct 18, 2024
4a16de5
q-dev: fix block device removing
piotrbartman Oct 21, 2024
ca949bb
q-dev: fix type hint
piotrbartman Oct 25, 2024
600209d
q-dev: update docs
piotrbartman Oct 25, 2024
73ea327
q-dev: less scary device category names
piotrbartman Oct 29, 2024
9a77764
q-dev: fix assignment.devices
piotrbartman Oct 29, 2024
08b899c
q-dev: pylint
piotrbartman Oct 29, 2024
727133f
q-dev: update tests and make pylint happy
piotrbartman Oct 30, 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
10 changes: 5 additions & 5 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -66,31 +66,31 @@ ADMIN_API_METHODS_SIMPLE = \
admin.vm.device.pci.Attached \
admin.vm.device.pci.Available \
admin.vm.device.pci.Detach \
admin.vm.device.pci.Set.required \
admin.vm.device.pci.Set.assignment \
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.Set.required \
admin.vm.device.block.Set.assignment \
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.Set.required \
admin.vm.device.usb.Set.assignment \
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.Set.required \
admin.vm.device.mic.Set.assignment \
admin.vm.device.mic.Unassign \
admin.vm.feature.CheckWithNetvm \
admin.vm.feature.CheckWithTemplate \
Expand Down Expand Up @@ -227,7 +227,7 @@ endif
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.Set.assignment \
admin.vm.device.testclass.Available
install -d $(DESTDIR)/etc/qubes/policy.d/include
install -m 0644 qubes-rpc-policy/admin-local-ro \
Expand Down
182 changes: 128 additions & 54 deletions doc/qubes-devices.rst
Original file line number Diff line number Diff line change
@@ -1,25 +1,80 @@
: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`).
The main concept is that a domain (backend) can expose (potentially multiple)
devices, each through a port, where only one device can be in one port at
any given time. Such devices can be connected to other domains (frontends).
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.
Devices are identified by a pair (`port`, `device_id`), where `port` is a pair
(backend domain, `port_id`). Both `port_id` and `device_id` are :py:class:`str`,
and in addition, port_id is unique per backend. More about the requirements
for `port_id` and `device_id` can be found in the sections below.

Classes
-------

:py:class:`qubes.device_protocol.Port`: a pair `<backend_domain>:<port_id>`
with `devclass` (e.g., `pci`, `usb`). In the previous version (before
QubesOS 4.3), this was referred to as `Device`, and `port_id` was named `ident`.

:py:class:`qubes.device_protocol.AnyPort`: A class used to handle cases where
any port is accepted.

:py:class:`qubes.device_protocol.VirtualDevice`: A pair `<port>:<device_id>`.
This class links a device identified by `device_id` to a specific port.
If both values are specified, the instance represents a device connected to
that particular port. If the port is of type `AnyPort`, it represents a device
identified by `device_id` that can be connected to any port. This is used by
:py:class:`qubes.device_protocol.DeviceInfo`, which describes what to do with
a device identified by `device_id` when connected anywhere. Similarly,
when `device_id` is `*`, the instance represents any potential device
connected to the given port. As a result, the device is considered "virtual"
meaning it may or may not represent an actual device in the system.
A device with `*:*` (any port and any device) is not permitted.

:py:class:`qubes.device_protocol.DeviceInfo`: Derived from `VirtualDevice`.
Extensions should assume that `Port` is provided, and based on that,
`device_id` should return the same string for the same device, regardless of
which port it is connected to. The `device_id` acts as a device hash
and *should* be "human-readable". It must contain only digits, ASCII letters,
spaces, and the following characters: `!#$%&()*+,-./:;<>?@[\]^_{|}~`.
It cannot be empty or equal to `*`.

:py:class:`qubes.device_protocol.DeviceAssignment`: Represents the relationship
between a `VirtualDevice` and a `frontend_domain`. There are four modes:
#. `manual` (attachment): The device is manually attached to `frontend_domain`.
This type of assignment does not persist across domain restarts.
#. `auto-attach`: Any device that matches a `VirtualDevice` will be
automatically attached to the `frontend_domain` when discovered
or during domain startup.
#. `ask-to-attach`: Functions like `auto-attach`, but prompts the user for
confirmation before attaching. If no GUI is available, the prompt is ignored.
#. `required`: The device must be available during `frontend_domain` startup and
will be attached before the domain is started.

:py:class:`qubes.device_protocol.DeviceInterface`: Represents device interfaces
as a 7-character code in the format `BCCSSII`, where `B` indicates the devclass
(e.g., `p` for PCI, `u` for USB, `?` for unknown), `CC` is the class code,
`SS` is the subclass code, and `II` represents the interface code.

:py:class:`qubes.device_protocol.DeviceCategory`: Provides an easy-to-use,
arbitrary subset of interfaces with names assigned to categories considered as
most relevant to users. When needed, the class should be extended with new
categories. This structure allows for quick identification of the device type
and can be useful when displaying devices to the end user.

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:
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.
has tree modes: `auto-attach`, `ask-to-attach` and `required`.
For detailed descriptions, take a look at
:py:class:`qubes.device_protocol.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`,
Expand All @@ -28,17 +83,35 @@ 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.

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

It is important to understand that :py:class:`qubes.device_protocol.Port` 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.device_id`, which returns a string
containing information presented by the device, such as for example
`vendor_id`, `product_id`, `serial_number`, and encoded interfaces.
In the case of block devices, `device_id` consists of the parent's `device_id`
to which the device is connected (if any) 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.

Actions
-------

The `assign` action signifies that a device will be assigned to the frontend VM
The `assign` action means 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,
upon the next VM startup. If `mode=required`, 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
(unless `mode=required`), 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
Expand All @@ -48,23 +121,23 @@ 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`.
or the device is currently attached. 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
#. `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
Then the possible 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.
Expand All @@ -79,51 +152,52 @@ because either (i) domain is halted, device (ii) manually detached or
#. `(False, True, True)` -> domain is halted, device assigned to domain
and required to start domain.

Note that if `required=True` then `automatically_attached=True`.

Conflicted Assignments
----------------------

If a connected device has multiple assignments to different `frontend_domain`
instances, the user will be asked to choose which domain connect the device to.
If no GUI client is available, the device will not be connected to any domain.
If multiple assignments exist for a connected device with different options but
to the same `frontend_domain`, the most specific assignment will take
precedence, according to the following order (from highest to lowest priority):
#. Assignment specifies both `port` and `device_id`.
#. Assignment specifies only the `port`.
#. Assignment specifies only the `device_id`.

It is important to note that only one matching assignment can exist within
each of the categories listed above.

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 `*` 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.


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).
(in client we can use `--required` flag). 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.

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.
The microphone cannot be assigned with the `mode=required` to any VM.

Port Assignment
---------------
USB Devices
-----------

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.
The USB devices cannot be assigned with the `mode=required` to any VM.


.. automodule:: qubes.devices
Expand Down
4 changes: 2 additions & 2 deletions qubes-rpc-policy/90-admin-default.policy.header
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
!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.Set.required * include/admin-local-rwx
!include-service admin.vm.device.mic.Set.assignment * 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.Set.required * include/admin-local-rwx
!include-service admin.vm.device.usb.Set.assignment * include/admin-local-rwx
!include-service admin.vm.device.usb.Unassign * include/admin-local-rwx

Loading