xdg-desktop-portal
xdg-desktop-portal copied to clipboard
USB portal
(This is a re-proposal of https://github.com/flatpak/xdg-desktop-portal/pull/559, but essentially rewritten)
The USB portal is the middleman between sandboxed apps, and the devices connected and available to the host system. This is the first version of the portal.
Device filtering
Sandboxed apps must declare which USB devices they support ahead of time. This information is read by the XDG Desktop Portal and used to determine which USB devices will be exposed to requesting apps. On Flatpak, these allowed and blocked devices are set by the "--usb" and "--no-usb" arguments against "flatpak build-finish" and "flatpak run". "--devices=all" does not influence the portal.
Blocking a device always take precedence over allowing them, even when a blanket permission ("--devices=all") is set.
Individual devices are assigned a unique identifier by the portal, which is used for all further interactions. This unique identifier is completely random and independent of the device. Permission checks are in place to not allow apps to try and guess device ids without having permission to access then.
Permissions
There are 2 dynamic permissions managed by the USB portal in the permission store:
-
Blanket USB permission: per-app permission to use any methods of the USB portal. Without this permission, apps must not be able to do anything - enumerate, monitor, or acquire - with the USB portal. [1]
-
Specific device permission: per-app permission to acquire a specific USB device, down to the serial number.
Enumerating devices
There are 2 ways for apps to learn about devices:
-
Apps can call the EnumerateDevices() method, which gives a snapshot of the current devices to the app.
-
Apps can create a device monitoring session with CreateSession() which sends the list of available devices on creation, and also notifies the app about connected and disconnected devices.
Only devices that the app is allowed to see are reported in both cases.
The udev properties exposed by device enumeration is limited to a well known subset of properties. [2]
Device acquisition & release
Once an app has determined which devices it wants to access, the app can call the AcquireDevices() method. This method may prompt a dialog for the user to allow or deny the app from accessing specific devices.
If permission is granted, XDG Desktop Portal tries to open the device file on the behalf of the requesting app, and pass down the file descriptor to that file. [3]
Open questions / TODOs
- How should it deal with unnaccessible devices? Escalate privileges with polkit?
- Is the backend D-Bus interface really necessary? Could we get away with the access portal for now?
- Per-device permission is not fully implemented yet
- This code is probably very inefficient
[1] Exceptionally, apps can release previously acquired devices, even when this permission is disabled. This is so because we don't yet have kernel-sided USB revoking. With USB revoking in place, it would be possible to hard-cut app access right when the app permission changes.
[2] This patch uses a hardcoded list. There is no mechanism for apps to influence which other udev properties are fetched. This approach is open to suggestions - it may be necessary to expose more information more liberally through the portal.
[3] This is clearly not ideal. The ideal approach is to go through logind's TakeDevice() method. However, that will add significant complexity to the portal, since this logind method can only be called by the session controller (i.e. the only executable capable of calling TakeControl() in the session - usually the compositor). This can and probably should be implemented in a subsequent round of improvements to the USB portal.
Additionally, "--devices=all" gives a blank permission to the Flatpak app to list all devices.
If you use that in flatpak
, why is the portal needed? The app will have access to all the USB devices that the user has permission for through /dev/bus/usb/XX
.
* How should it deal with unnaccessible devices? Escalate privileges with polkit?
This is an important question, and an even more important one is how do you remove the authorisation for an app to talk to a device that it's already talking to.
A particularly important test of the API would be to adapt libgusb or libusb to access USB devices through the portal rather than talking directly to the Linux kernel.
Sandboxed apps must declare which USB devices they support ahead of time.
How will this work in practice? If some Game has Controller support, it is very unlikely that this Game ships a list of all serial numbers of all Game Controllers in existence.
Additionally, "--devices=all" gives a blank permission to the Flatpak app to list all devices.
If you use that in flatpak, why is the portal needed? The app will have access to all the USB devices that the user has permission for through /dev/bus/usb/XX
Then the portal isn't needed but the portal exists so apps can drop --device=all
eventually. That being said, I don't think --devices=all
should influence how the portal works at all. Is that the point you're trying to make?
If some Game has Controller support, it is very unlikely that this Game ships a list of all serial numbers of all Game Controllers in existence.
First of all, this portal is not about input, joysticks and gamepads. Let's not get sidetracked here. Even then, yes, games or middleware ship with a list of all joysticks they support because you have to understand a specific joystick to actually use it.
What is the scope of this portal, its targets? This could be clearer.
Perhaps certain classes of devices could not be allowed in order to force, where possible, the use of the correct portal, such as for cameras (since this example was mentioned), and to offer additional protection. An exception, for many devices, is to access the firmware interface for upgrade purposes, which should ideally be separate from standard device use. This, if I'm not wrong, obviously.
What is the scope of this portal, its targets? This could be clearer.
Seems like most questions are about this to begin with. The joystick question was quite on-topic already, but I'd raise a worse case, browsers and WebUSB. If --devices=all is the answer for that too, then it's surely rather confusing.
The whole device declaration logic seems to be odd for me, seems like it's counterintuitive:
- Benevolent programs won't keep on getting updates just to keep on adding all kinds of devices they support, so legitimate usage will suffer from filtering.
- Malevolent programs will gladly abuse the idea of adding a ton of device identifiers as apparently that would mean that all the listed devices can be enumerated without the user's authorization which is useful for tracking.
Doesn't the model established for virtual machines already make sense? It's essentially the same as plugging in a device just in software (when it actually works as there are some limitations). I thought that would be the initial goal, then additional conform functions could be added like a suggested list of what the program would be interested in.
The enumeration permission just really doesn't seem to make sense, and overriding would be also tedious. Currently if I get let's say a gaming related program, then by default it will want to access ~/Games , but I can override it fine and chances are really good the program won't be upset. Compare that to the logic of overriding the USB list (if even possible) simply leading to the enumeration logic failing because there would be just no way of getting to the point of adding a device.
An exception, for many devices, is to access the firmware interface for upgrade purposes, which should ideally be separate from standard device use.
This is actually a really good mention of one of the worst cases I've seen without direct access. Firmware upgrading tends to exercise reconnect logic with a short timeout. For example a typical process can start with rebooting the device into an update mode, waiting for the device to reappear in a second or so, and it's not even necessarily the "same" device, it's not uncommon to present a different USB descriptor in update mode.
@GeorgesStavracas maybe make it clear what this portal is not trying to achieve, particularly:
- Joystick and gamepad input/output is out of scope and should be handled by wayland
- firmware upgrades are out of scope and should be handled by fwupd
- video (cameras, capture, etc) and audio is out of scope and should be handled by pipewire
@hadess
If you use that in flatpak, why is the portal needed? The app will have access to all the USB devices that the user has permission for through /dev/bus/usb/XX.
Flatpak apps with --device=all
still don't have monitoring - the devices you get when the sandbox is mounted is what you get for the lifetime of the application.
I don't know if it's a good idea to look at this the devices flag from the portal. It may not be. It may be better to increase the expressiveness of --usb
and --no-usb
and ignore --devices
completely
This is an important question, and an even more important one is how do you remove the authorisation for an app to talk to a device that it's already talking to.
My honest opinion is that for v1 of the USB portal, it shouldn't try to escalate permissions, and e.g. GNOME Settings should say something like "You need to restart the app for this permission change to take effect". Otherwise it will be blocked on too many year-long changesets. But I guess you'll disagree with that :)
A particularly important test of the API would be to adapt libgusb or libusb to access USB devices through the portal rather than talking directly to the Linux kernel.
Yup it's on my list next.
@swick
Sounds good, will update the commit and PR description soon.
firmware upgrades are out of scope and should be handled by fwupd
I 100% agree that all firmware updates should go through fwupd, however, I think the portal, as it is right now, would allow for that, no? Apps can get raw USB access through the portal, and can monitor for devices unplugs and replugs, so technically it's possible? Is there anything else that apps need to do to manually update a firmware?
(I wish we could distribute fwupd through Flatpak. It's probably not possible right now, and probably won't be possible with this portal either.)
I think the portal, as it is right now, would allow for that, no?
Probably for a lot of cases, yes. The point of saying we don't want to support firmware updates is that when some device comes up with a way to update the firmware which doesn't work with the portal we can say it's not an issue and they should use fwupd instead.
(I wish we could distribute fwupd through Flatpak. It's probably not possible right now, and probably won't be possible with this portal either.)
https://flathub.org/apps/org.freedesktop.fwupd
Flatpak apps with --device=all still don't have monitoring - the devices you get when the sandbox is mounted is what you get for the lifetime of the application.
That's not the case. If all of /dev
is bind-mounted as a single directory (like --device=all
does), then the app can open any device node that exists, even if the device node didn't exist during app startup.
Proper support for hotplug in that situation requires jumping through some hoops, because libudev change-notification via netlink doesn't work across a container boundary, but you can get "good enough" change notification by using inotify on /dev
. This is how SDL and Proton do it, and com.valvesoftware.Steam
relies on that to be able to have game controller hotplug. It's far from perfect, but it works.
Similarly, if a whole subdirectory of /dev
is bind-mounted, then that subdirectory is "live": this is how flatpak/flatpak#5481 works.
What definitely doesn't work for monitoring/hotplug is if we bind-mount individual device nodes (like if we bind-mount /dev/bus/usb/001/001
but not the rest of /dev/bus/usb/
, or if we bind-mount /dev/hidraw0
but not the rest of /dev/
). If we do that, the list of devices is statically part of the mount table, and cannot change during the app's lifetime, so hotplug cannot work. That's why I accepted /dev/input/
in flatpak/flatpak#5481, but rejected /dev/hidraw*
.
In principle there would be nothing to stop Flatpak from bind-mounting all of /dev/bus/usb
, although I agree that a portal is necessary if we want any middle ground between "app can access all USB devices directly" and "app cannot access any USB devices directly".
or if we bind-mount
/dev/hidraw0
but not the rest of/dev/
... or, perhaps more relevant to this particular PR, since input devices are explicitly out-of-scope for this PR: if we bind-mount /dev/ttyUSB0
but not the rest of /dev/
, for devices that present to the system as a USB serial port.
@smcv:
What does this mean? That the application can know all the devices we have and access them freely (without permission)?
Or individual devices can be mounted, but then we forget about hotplugging? So the portal would just emit connected/disconnected signals so the app can tell the user to restart it?
Still in relation to per-device access, I think mass storage is not a candidate either (if that is relevant for this portal).
What does this mean? That the application can know all the devices we have and access them freely (without permission)?
If it has --device=all
, yes. If it has --device=input
(new in 1.15.x), only the /dev/input/
subtree. Otherwise, no.
This latest push adjusts the portal code to match https://github.com/flatpak/flatpak/pull/5620. It also addresses some comments.
@hadess
Additionally, "--devices=all" gives a blank permission to the Flatpak app to list all devices.
If you use that in
flatpak
, why is the portal needed? The app will have access to all the USB devices that the user has permission for through/dev/bus/usb/XX
.
I have removed this check. --devices=all
does not influence the USB portal behaviour anymore.
Since input devices are out of scope for this, are hidraw devices in scope for this? In particular, I'm interested in HID DJ controllers for Mixxx which are accessed through the hidapi library. In the past, Mixxx used the libusb backend of hidapi, but switched to the hidraw backend due to a bug with certain devices using the libusb backend.