I have been using Qubes as my primary OS1 for a while now, and I am extremely happy with the level of control it provides me over my apps. Recently someone raised an interesting question on the Qubes OS forum:

Hello,

Is there a way to attach a particular USB device (by Vendor ID/Product ID) to a Qube automatically on connect? I have a sys-usb to manage USB devices.

The USB device I am using frequently becomes disconnected (and reconnected) during use and it only needs to be connected to one particular Qube.

Thanks,
N

https://forum.qubes-os.org/t/usb-device-auto-attach-to-qube/5977

But what exactly is $\sysusb$?

Typically, $\sysusb$ is a minimal VM to which all USB controllers are assigned. So all USB devices are attached to $\sysusb$ when plugged in, and not to $\DomZ$ that has direct access to rest of the hardware. Attaching untrusted USB devices to $\DomZ$ is a potentially fatal security risk. Many ready-to-use implementations of common attacks already exist, e.g. the USB Rubber Ducky. Therefore, it’s strongly recommended to use a dedicated USB VM, and only passthrough trusted devices to VMs that need to access them.

Below are some excellent resources on understanding the rationale behind using $\sysusb$:

Also note that running applications, such as your password manager etc. directly in $\sysusb$ defeats the whole point of using Qubes OS — compartmentalization. A malicious USB device, if connected, could potentially extract all of your application data. So, $\sysusb$ should only be used as a handler, and only trusted devices should be connected to application VMs (called AppVMs in Qubes).

I have wondered about this sort of automation before. I have a bunch of USB devices: external disks, crypto wallets, password managers, etc. which I attach to specific VMs immediately after plugging in. It would be nice to be able to attach trusted devices to certain VMs automatically.

On a “regular” OS, such as Debian or Fedora, triggering actions on connecting / disconnecting USBs devices is typically achieved using udev rules. We could match on the Vendor ID / Model ID etc. to detect when a particular USB device is added or removed, and may RUN a desired script on that event. However, this isn’t as straightforward with $\sysusb$ on Qubes OS, since it typically would have no access to other running VMs and cannot attach devices to them directly.

# POTENTIAL SECURITY RISK

Before trying out the proposed changes on your system, please remember that we are trading off security for convenience. If implemented incorrectly, or without proper constraints, you might end up unintentionally attaching potentially harmful devices to your VMs automatically!

I proposed an initial idea on how this could be done, and this weekend I finally had some time to try it out 🙂 The high-level plan is outlined below:

1. Since $\sysusb$ cannot escape virtualization and directly attach devices to other VMs, we must somehow communicate via the AdminVM (i.e., $\DomZ$), which has access to all VMs.
2. Communication in Qubes typically happens using an elegant remote procedure call (RPC) framework, called Qrexec, so we must write some new Qrexec service that receives a device attachment request and fulfills it.
3. Our Qrexec service will be listening in $\DomZ$, and a $\sysusb$ client must pass the target VM name and device name to this service from a udev trigger.
4. Finally, we must also setup an appropriate Qrexec policy for this service in $\DomZ$.

If you are unsure of what RPC / Qrexec / udev are, then I would strongly suggest familiarizing yourself with those tools first before making any changes to your system.

# ADVANCED QUBES / LINUX STUFF

DO NOT copy-paste any code or shell commands from the internet, unless you understand exactly what they do. You may compromise the security of your system, or damage it otherwise.

#### Changes in $\DomZ$

There are two main changes necessary in $\DomZ$: (a) a new Qrexec service to listen to device attachment requests from $\sysusb$, (b) a Qrexec policy to restrict the source and destination VMs for calls to this service.

#### The Qrexec Service

First, let’s start with the new Qrexec service that would handle requests from $\sysusb$. I am only going to describe how to automatically attach block devices, but attaching full USB devices is very similar (see comment at the end of this section).

There are a few different ways of implementing this service. One important choice to make is regarding the mode of communication with $\sysusb$. Only stdin/stdout is passed between the Qrexec server and client — in particular, there is no support for command-line arguments in Qrexec. However, instead of communicating over stdin/stdout, I chose to use a service argument, which is actually passed as a single command-line argument to the service. A service argument, unlike stdin/stdout communication, is visible on the $\DomZ$ prompt, so the communication is a bit more transparent. Moreover the service argument is also sanitized by Qrexec, before being supplied to the script.

However, since Qrexec only allows a single service argument currently, we must pack our target VM name and device name together. An example prompt from my $\sysusb$ should be displayed below. custom.USBDeviceAttach is the service that we are going to create, and work+__+sda is the service argument. In this case, I pack the target VM name (work) and block device name (sda within $\sysusb$) together by concatenating both using +__+.

The service script itself is pretty simple, as I show below.

#!/usr/bin/env bash

# Exit immediately if any of the following commands fail
set -e

# Expect exactly one argument -- the service argument
[ "$#" -eq 1 ] # Unpack service argument and expect exactly 2 strings ARG=($(sed 's;+__+; ;g' <<< $1)) [ "${#ARG[@]}" -eq 2 ]

# Make sure we have a remote VM name from Qrexec
[ -n "$QREXEC_REMOTE_DOMAIN" ] # Start the target VM, if needed qvm-start --skip-if-running "${ARG[0]}"

# Attach the target device from remote VM to target VM