-
-
Notifications
You must be signed in to change notification settings - Fork 183
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
[WIP] Add new portal for USB device access #559
Conversation
(CI failures seem to just be that libudev is missing, will update tomorrow) |
Quick drive-by comments:
To see if this API is viable, I'd really like to see an attempt at porting a libusb application to this. The API should be usable in single-threaded mode, so we could probably have a "Flatpak" backend where enumeration, hotplug, and opening device would go through the portal automatically. We're still missing revoke support in the kernel, as I mentionedi in the original issue, so that the portal can't yank access to an application that's opened a device, which is a potential security issue, and we don't have a way to request access to USB devices for which we don't already have access. For example, accessing an Arduino in programming mode still would require running a bunch of commands as root/install udev rules. |
I had looked into it, but at a glance for this usage, it mostly provided stuff like ootb autoptr support and slightly cleaner attribute parsing, which is nice but would really shave off at most 10 lines or so and didn't really seem worth adding a dependency for. That being said, apparently a lot of stuff already requires gudev:
so it's probably more prevalent than I initially realized. This probably wouldn't be too hard to change out.
I'm not really sure what this is referring to here, is it in the Flatpak PR? I haven't updated any of the docs there yet.
All the mentioned device nodes are in /dev/bus/usb, though (the code already determines if it's a USB device by checking ID_BUS). Did you mean it shouldn't access anything where the subsystem isn't USB?
I don't think trying to exclude certain subsystems would help much, though. The only real way (from what I can tell) to identity USB devices outside of the bus is the subsystem like I mentioned above. However, there are already some miscellaneous devices attached to this subsystem, this is what I find in /dev/bus/usb without actually having anything plugged in:
None of the udev attributes available on it would indicate what types of devices these are; I only found out by reading the vendor strings. Thus, regardless, we're going to end up with devices that have alternative APIs also exposed over USB without a clear way of blocking them. It's also worth noting that Android intentionally leaves HID devices available through UsbManager. If an application wants to communicate with the device in a more low level way that's USB-specific, that functionality is still available. For instance, the Stadia app uses UsbManager to communicate with the controller and set its current charging acceptance state. This is a strictly USB-only operation, so it makes sense that it's through a USB portal vs an HID portal, but it's still being performed on an HID device.
I will admit, this is more just so that it's easier to open the device nodes and not related to permissions. Since they may be ro, we can't just unconditionally open them as rw when the application calls OpenDevice, and we can't just always open them as ro since the application may need to write to it (i.e. to send force feedback data). By making the application request ro vs rw, we can easily fail out early on if write access was in fact needed, while not having to worry about it if the application only needs read access. I'm open to ideas on how to clean it up!
My personal concern with using udev properties is that they're a bit of a wild west (over 130 Stuff like
One reason for this distinction is partly what I mentioned above:
If this indeed ends up being guarded behind having permissions, the application would need to call OpenDevice if it needs the serial, which would be a bit bizarre. Now, we could have OpenDevice simply always show a permission dialog in addition to RequestPermission, but depending on how USB management requests get handled (see below), we'd end up with other calls that all also can show permission dialogs in addition to RequestPermission, which seemed a bit odd API-wise? Now that I think about it, though, it's a bit odd that requesting permission to read/write to a device would be required to request access to the serial. Maybe
This is definitely also in the plans, along with the demos I mentioned before!
This is probably terrible, but...could this, in theory, be handled by holding onto the fd in the portal, and calling shutdown on it if permission is revoked? This would have the issue that the portal dying would result in losing control over the fd, however.
This is something I actually forgot to mention above. It's definitely a bit of an issue; I guess in theory, we could of course just use polkit to have some system service hand access to the device, but the portal of course doesn't have any communication set up for that right now. This also comes back to the other thing that I completely forgot to mention previously, which is that the usbdevfs ioctls aren't supported yet, and those are definitely something that's needed for full-fledged device support (e.g. the HID use case I mentioned above). Some of them needed some careful treading, which is why I wanted to work on and propose the core permissions system first. (Side note: this is certainly a lot more written out than I anticipated, I should probably have gone into some of these design ideas in the initial post first.) |
I meant here. I was under the impression that it just added more items that would ultimately end up in the
No. I meant that it should be allowed to accessing the device node for the special driver for the device, just the raw USB device. Eg. I don't think that it should be able to access the dev input node for a USB mouse or keyboard, or the v4l device node for a webcam, etc.
Which is why I would restrict exporting to devices under
I really think that should be something separate. There's a separate discussion about HID devices in this same repo. Note that in the Stadia controller's case, I'm pretty sure it talks to I think this particular use case would be better served through:
This comes back to the original problem. The API seems to have been designed to access any device node that's associated with a USB device. I think it should only be a proxy for
I certainly think that blocklists and allowlists of, well, blocked and allowed properties would be better than using single item properties. Use an The serial of the device will also be accessible to the app once it opens the device, and could be shown/used in some way in the portal permission dialogue. I could very well imagine a Flatpak announcing in its manifest that it supports all the devices from Apple in a certain PID range, and leaving it to the end-user to select the device to open in a portal dialogue. Something closer to a "File open" dialogue where you filter a type of file, rather than getting access to a full file list ahead of time.
See above, I don't think that this should be here.
I meant that there shouldn't be a separate RequestPermission API. For each of "enumerating devices" and "opening devices", I think that the portal should be the one asking the user for permission (through a shell dialogue, for GNOME at least).
I'm pretty certain that won't work just as it doesn't work for input devices, which would be why there's an evdev revoke API. Happy to be proven wrong.
My problem here is that we need to figure out how this could work in a way that doesn't make a popup show up to escape the sandbox, and another popup show up to access the device node. I think we need to figure this out before implementing this feature. Or only show user-accessible devices (
Those ioctl() aren't supported by what?
I'm guessing you read #227 (comment) ? In short, I think it would be good to:
Does that all make sense? I'd be happy to help out with implementing a backend for libusb and integration tests (using umockdev). (PS: I'm scared of that regex) |
Any progress on this? |
@hadess Hey sorry for the long radio silence here, there's been a lot going on. I'm almost done with some changes to fix most of the issues raised, though there were a few design issues that came up that are a bit interesting:
EDIT: yes I accidentally hit Shift+Enter before I finished, this may have appeared incomplete in any email notifications. Sorry about that! |
But seeing the serial number would be something of interest when enumerating devices. So do we need a separate method to request permission to access things like the name, or serial number when it's information that seems pretty vital when enumerating. My idea as far as permissions were concerned was to avoid explicit permission requests, and roll those into API calls that were going to get made.
This gives plenty of opportunity to tighten or loosen the permissions without changes to the API. Were your concerns about leaking the serial number (and other metadata) about fingerprinting/information leakage, or were there other concerns?
USB HID devices, yes :)
I think this would need to be checked on a case-by-case basis. For sysattr data (currently active USB configuration), we could export that through a udev property directly if it's useful. I have commit rights for systemd, and exporting a sysattr isn't too complicated. What would the parent be used for?
Yes, I think that it might be a little bit overkill, if combined with the 3 above permission entry points. |
Pretty much this. AFAIK some devices can have fully unique serials, meaning that you could basically pretty easily fingerprint devices, even trying to link them together based on any sometimes-plugged-in shared hardware, whereas just the name would be far more limited in that goal. I guess we could maybe add a separate method just for getting restricted properties? Then, future calls to get properties would include the restricted ones since permission was already granted.
From what I can tell, the primary candidate I found was However, wouldn't this mean that any systems with an older udev wouldn't get the new properties, unless we can assign the properties in custom udev rules by reading the sysattr file?
I'm admittedly not 100% clear on this, but from what I can tell it's primarily used since different USB interfaces are exposed as different nodes, so code sometimes has to walk them up to the closest matching parent in order to find the one it wants to manipulate. In theory, we could abstract over this in the portal...but I'm hesitant to go that far past what udev itself provides. |
Sorry about not getting back to you earlier, I really thought I did (I even remember thinking about the answers).
I don't think that we need to protect those properties by using additional calls. The application can only list the set of devices it declared interest in in the Flatpak manifest. Then the application needs to ask for enumeration. Maybe in that part of the API the application could say which fields it's interested in, with the default being to pass the names, and serial if there are duplicate names. Eventually, we could have host-side lockdown settings for whether that data should be passed at all (similarly to the privacy settings in Android forks that allow further lockdown compared to the stock OS). The serial and other metadata could also be provided as one of the return values to opening the device if that information is only useful once the device is opened.
I can't think of a single case where a newer xdg-desktop-portal would be deployed and the rest of the host couldn't be updated, or the support in question backported. I should be able to work on that soon.
Could we avoid adding API/metadata until we have a user for it? I think that it would be a very good idea to look at what it would take to port a couple of applications/utilities to using this portal API, at least one that would use a yet to be written libusb shim, and one that can be modified to fully take advantage of a UI-based workflow. Maybe something like The most important isn't to get the API right first time, but not to dig ourselves into an API hole where we couldn't get out. So minimal but extensible API, and API that's close to the existing usage patterns. Talking of, it might be a good time to start looking at the UI design of the USB portal. Do you have prior art available for, say, Android, iOS, macOS, and maybe Windows UWP? |
Flatpak PPA does xdg-desktop-portal updates on ubuntu including LTS. |
That probably isn't sufficient to support all the new features exported through portals. For example, I can't imagine the Flatpak screen sharing and other newer features working too well if there's no (new enough) pipewire on the host, anymore than if the user uses a desktop which doesn't have the right features. New portals requiring host updates isn't much to ask for. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
No worries, I barely had time to work on this before (and have made far too many replies "in mind only" regardless!).
So to be clear:
This could be troublesome if a duplicate device is added afterwards though, right? For instance:
To be clear, if all an application needs is "a roughly unique name for each attached USB device", the portal generates random IDs to attach to each device on add. This would serve most purposes, but afaik the purpose of the serial leans more towards being unique across runtime adds & removes. In addition, as I was typing this, there's one other issue: not having a way of getting the serial itself directly means we couldn't support WebUSB in Chromium or Firefox, since it requires the ability to get a device serial.
Hmm, I think in most cases thus far though, an entire portal ends up missing, right? It seems weird to make the device portal just "not work" if a specific udev property that is desired is not available, even if no app actually needs it. On the other hand, if the portal appears to work, but a particular property is just silently missing, it could result in some bizarre behavior. As I was thinking about this, I realized we could technically read the value ourselves and "synthesize" a udev property if it's not available on the host version. However, I'm not sure if udev change events would get emitted if said synthesized property changes... I guess technically the portal could also just ship a udev rule to set it? (Not sure if rules can actually read sysattr values and track changes though.)
There is a user for it: that example was pulled from Chromium. I just haven't stepped through the code far back enough to get the full reasoning as to why (but I can if needed).
As-is, the current dialog was loosely based (but reworded to match the other portal dialogs) on Android, which looks like this (admittedly the first screenshot I found on the web): In essence, it just asks the user, showing them the app and device name. The offer to open the application on connecting the USB device was not added, since it would open up a sizable rabbit hole of implementation details. |
I'm waiting on this for one of my projects. If I can help out in any way, let me know. |
Sorry for the noise, just wanted to add that I'd also love to see progress here in order to lock down Firefox and Chromium/Electron based apps. I'm currently working on making WebRTC use the xdp-camera portal, but as browsers also support Yubikeys, gaming controllers etc. we're stuck on If you're busy with other things, I wonder if it would make sense to have a Edit: opened flatpak/flatpak#4405 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some random drive-by comments.
</para></listitem> | ||
</varlistentry> | ||
<varlistentry> | ||
<term>has_joystick s</term> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be a boolean?
<varlistentry> | ||
<term>has_joystick s</term> | ||
<listitem><para> | ||
Whether this device has / is a joystick.If not present, then it should be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
Whether this device has / is a joystick.If not present, then it should be | |
Whether this device has / is a joystick. If not present, then it should be |
<term>writable b</term> | ||
<listitem><para> | ||
Whether the device can be opened for writing with | ||
org.freedesktop.portal.Usb.OpenDevice().If not present, then it should be |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit:
org.freedesktop.portal.Usb.OpenDevice().If not present, then it should be | |
org.freedesktop.portal.Usb.OpenDevice(). If not present, then it should be |
</para></listitem> | ||
</varlistentry> | ||
<varlistentry> | ||
<term>devnode s</term> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps device_node
? (Probably not a good idea though, since devnode
matches udev terminology)
filter = get_filter_from_options (&options_dict); | ||
|
||
permissions = app_usb_permissions_for_app_info (app_info); | ||
usb_enumerate_all_to_variant (usb, &builder, permissions, filter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please clarify how USB devices are enumerated the first time an app is run? It looks to me that this code would run into a chicken-and-egg problem, where apps can't enumerate devices because they don't have permissions stored, and they don't have permissions because they can't enumerate devices.
I've started working on the USB revoke support, motivated by whot's work on HID revoke support: systemd/systemd#23140 |
Related: flatpak/flatpak#4083
Core design notes
UsbManager
API.--device=all
don't need dynamic permissions, because they already have full access to everything.Remaining questions / issues
I have a test script that will be cleaned up and posted at some point. This could in theory also be auto tested using something like https://github.com/martinpitt/umockdev but setting all of that up is probably also a bit out of scope!