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

USB portal #1238

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft

Conversation

GeorgesStavracas
Copy link
Member

@GeorgesStavracas GeorgesStavracas commented Dec 8, 2023

(This is a re-proposal of #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:

  1. 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]

  2. 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.

@hadess
Copy link
Contributor

hadess commented Dec 9, 2023

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.

@JakobDev
Copy link
Contributor

JakobDev commented Dec 9, 2023

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.

@swick
Copy link
Contributor

swick commented Dec 9, 2023

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?

@swick
Copy link
Contributor

swick commented Dec 9, 2023

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.

@Mikenux
Copy link

Mikenux commented Dec 11, 2023

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.

@voidpointertonull
Copy link

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.

@swick
Copy link
Contributor

swick commented Dec 11, 2023

@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

@GeorgesStavracas
Copy link
Member Author

@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.)

@swick
Copy link
Contributor

swick commented Dec 11, 2023

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.

@Erick555
Copy link

(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

@smcv
Copy link
Collaborator

smcv commented Dec 11, 2023

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".

@smcv
Copy link
Collaborator

smcv commented Dec 11, 2023

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.

@Mikenux
Copy link

Mikenux commented Dec 12, 2023

@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).

@smcv
Copy link
Collaborator

smcv commented Dec 12, 2023

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.

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:

 1. 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]

 2. 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]

Co-Authored By: Georges Basile Stavracas Neto
<[email protected]>

---

[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.
@GeorgesStavracas
Copy link
Member Author

This latest push adjusts the portal code to match flatpak/flatpak#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.

@Be-ing
Copy link

Be-ing commented Feb 22, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Needs Triage
Development

Successfully merging this pull request may close these issues.

None yet

10 participants