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 (cont.) #1354

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open

USB portal (cont.) #1354

wants to merge 2 commits into from

Conversation

hfiguiere
Copy link
Collaborator

@hfiguiere hfiguiere commented Apr 30, 2024

This supercede #1238

Close #1238

@hfiguiere
Copy link
Collaborator Author

After a painful rebasing.

@hfiguiere hfiguiere self-assigned this Sep 5, 2024
@hfiguiere hfiguiere marked this pull request as ready for review September 14, 2024 03:05
@hfiguiere
Copy link
Collaborator Author

Feature is linked to flatpak/flatpak#5620

I think this is ready for review.

@hfiguiere hfiguiere force-pushed the usb-portal branch 2 times, most recently from 69843ce to 4dcfc94 Compare September 14, 2024 18:35
Copy link
Contributor

@swick swick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Checked the bits outside of usb.c so far.

src/xdp-app-info-flatpak.c Outdated Show resolved Hide resolved
src/xdp-utils.h Outdated Show resolved Hide resolved
src/xdp-utils.h Outdated Show resolved Hide resolved

enumerable_devices = g_key_file_get_string_list (app_info_flatpak->flatpak_info,
"USB Devices",
"allowed-devices",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enumerable-devices/hidden-devices

Might want to use defines for those constants.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed the literal.

src/xdg-desktop-portal.c Outdated Show resolved Hide resolved
src/xdp-utils.c Outdated Show resolved Hide resolved
src/xdp-utils.c Outdated Show resolved Hide resolved
src/xdp-utils.c Outdated Show resolved Hide resolved
src/xdp-utils.c Outdated Show resolved Hide resolved
src/xdp-utils.c Outdated Show resolved Hide resolved
Copy link
Contributor

@swick swick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bunch of comments on the interface definition. Mostly about documentation but I'm a bit worried about what device removal means and how we should deal with udev properties.

data/org.freedesktop.portal.Usb.xml Outdated Show resolved Hide resolved
data/org.freedesktop.portal.Usb.xml Outdated Show resolved Hide resolved

* ``properties`` (``a{sv}``)

A list of udev properties that this device has. These properties
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm really worried about this. It essentially makes udev a stable API even though it really is not.

Comment on lines +146 to +157
This method can only be called once, and only after calling
org.freedesktop.portal.Usb.AcquireDevices().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should mention the #org.freedesktop.portal.Request::Response signal

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the response signal should carry an id that should be passed to FinishAcquireDevices. That way there is no problem with calling AcquireDevices multiple times before calling FinishAcquireDevices.


There are no supported keys in the @options vardict.
-->
<method name="ReleaseDevices">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably say something about the relation to the session and connection. I assume closing the session or connection implicitly also releases all devices?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eh, there is no session involved with acquiring and releasing devices, only a connection. The question still remains: what happens when the connection is closed. Same as release? Nothing?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the dbus connection is closed all the devices are removed (ie released)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my suggestion on FinishAcquireDevices would resolve this thread.


* ``action`` (``s``)

Type of event that occurred. One of "add", "change", or "remove".
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens when one has acquired a device and it got removed? Is the fd still usable? Only sometimes?

We don't have a mechanism for revoking the fd so a real device unplug will result in a remove with the fd becoming unusable but when the user wants to revoke a device, we can't do anything with the existing fd on the client and so it won't be.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think my suggestion on FinishAcquireDevices would resolve this thread as well.

@devices: Array of device identifiers
@options: Vardict with optional further information

Releases previously acquired devices.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does it mean to release a device? Is the fd still usable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will close the file descriptor when we release the "owned device" object. I haven't checked how libusb, for example, reacts to this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it closes the fd on the portal side which is good because we need to do that to not leak things, but it doesn't close the fd on the client side. In fact, you cannot close the fd on the client side from the portal side. The only thing we can do is revoke file descriptors but that currently only works for evdev and hidraw and not for usb. So as long as we don't get kernel support for that, clients will be able to continue to use the fd that they acquired even if the connection is closed. We should however say something in the interface description here which will allow us to revoke those USB devices if the connection gets closed, or if the user wants to revoke access manually, when we get USB revoke from the kernel.

Copy link
Contributor

@swick swick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looked through the usb.c file now. Mostly nickpicks but also a few actual issues we have to work out.

src/usb.c Outdated Show resolved Hide resolved
src/usb.c Outdated Show resolved Hide resolved
src/usb.c Outdated Show resolved Hide resolved
src/usb.c Outdated Show resolved Hide resolved
src/usb.c Show resolved Hide resolved
g_dbus_method_invocation_return_error (invocation,
XDG_DESKTOP_PORTAL_ERROR,
XDG_DESKTOP_PORTAL_ERROR_FAILED,
"Cannot call AcquireDevices() with an unfinished "
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not? The interface description doesn't mention this restriction at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The changes I proposed on AcquireDevices would resolve this thread.

src/usb.c Outdated Show resolved Hide resolved
Comment on lines +146 to +157
This method can only be called once, and only after calling
org.freedesktop.portal.Usb.AcquireDevices().
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the response signal should carry an id that should be passed to FinishAcquireDevices. That way there is no problem with calling AcquireDevices multiple times before calling FinishAcquireDevices.

owned_device = g_hash_table_lookup (sender_info->owned_devices, access_data->device_id);
if (!owned_device)
{
fd = open (device_file, access_data->writable ? O_RDWR : O_RDONLY);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why would opening the file fail? Is udev already telling us that we should be able to open the device node (i.e. we have permission to do it) or is do we discover about bad permissions just here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

many reason. It's IO nothing is guaranteed. The device could have disappear, could be unitialized. If it can go wrong it will go wrong.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, but that means we'll show USB devices to the portal clients, letting users go through the UI to approve access, just to then eventually notice that we actually can't make the device available. Would be much nicer if we didn't advertise those devices in the first place.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is not a lot we can do. We can call access on the device is_gudev_device_suitable() and return FALSE if the call fail. That will check for existance and read permission. At that leve we don't know if we want the write permissions.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And we already check in handle_acquire_devices() as well.

Still a lot of race condition can occur it acquiring the device.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, obviously there can always be cases where opening will fail because this is inherently racy, but why would we want to advertise devices which we cannot open because we have no read permission? The code right now figures out that it won't be able to open it, advertises it anyway and then fails when a client tries to acquire it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we no longer do. the change just hadn't been pushed yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe I'm blind but I can't seem to find it? Are you sure you pushed the latest version now?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/flatpak/xdg-desktop-portal/pull/1354/files#diff-4ce7d34e66e5ca9b52f8e5666f0d4efe11e48278f34a7cdba6df390c14012a27R163

Which mean if it can't access it it won't list it. So open won't be attempted. In case of race condition, ie the device is no longer accessible between the list and this, then this will error normally.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I missed that one. Perfect!

src/usb.c Outdated Show resolved Hide resolved
@hfiguiere hfiguiere force-pushed the usb-portal branch 2 times, most recently from a258338 to b40ccee Compare September 21, 2024 00:12
@hfiguiere
Copy link
Collaborator Author

(I haven't addressed everything yet)

@hfiguiere hfiguiere force-pushed the usb-portal branch 7 times, most recently from bdaa78e to 27f10e2 Compare September 26, 2024 03:40
@hfiguiere hfiguiere force-pushed the usb-portal branch 3 times, most recently from a8ce8e9 to a3123a1 Compare October 9, 2024 01:47
@swick swick mentioned this pull request Oct 9, 2024
src/xdp-app-info-host.c Outdated Show resolved Hide resolved

Device ID of the parent device.

* ``readable`` (``b``)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What use is a device which can not be read from? AcquireDevices also doesn't have a readable only a writable. Pretty sure this should just be removed.

Copy link
Collaborator Author

@hfiguiere hfiguiere Oct 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Until very recently it would have listed the devices even if it couldn't access them.

See #1354 (comment)

It is devices that can be enumerated after all.

@hfiguiere hfiguiere force-pushed the usb-portal branch 3 times, most recently from 5dfe11a to 3cb7179 Compare October 13, 2024 03:11
@swick
Copy link
Contributor

swick commented Oct 15, 2024

Pushed a branch with a bunch of cleanups and changes I'd like to see https://github.com/swick/xdg-desktop-portal/commits/wip/usb-portal-fixes1/

But in general this LGTM. The biggest issue is the lack of tests IMO. Some basic python tests using umockdev would be great but I'd also like to see this merged rather sooner than later. Do you have time to do that work?

@hfiguiere hfiguiere force-pushed the usb-portal branch 2 times, most recently from adf2665 to 75e2525 Compare October 16, 2024 03:07
@swick
Copy link
Contributor

swick commented Oct 16, 2024

I started working on python tests for this portal here: https://github.com/swick/xdg-desktop-portal/commits/wip/usb-portal-tests/

It's based on the PR which tests the location portal with the python tests because it already has a bunch of improvements.

Found a few bugs in the impl already and the branch contains fixes.

It's using umockdev to create the testbed and it succeeds in hiding the host devices but no success so far with making devices visible from the test itself.

@swick
Copy link
Contributor

swick commented Oct 17, 2024

Some prepwork for the testing is up in a PR: #1477

Also update the branch https://github.com/swick/xdg-desktop-portal/commits/wip/usb-portal-tests/ which now has passing tests with some mocked devices. It could use some more tests and it still contains fixes that this PR doesn't have.

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 enumerable and hidden devices are set by the
"--usb" and "--nousb" arguments against "flatpak build-finish"
and "flatpak run". Neither "--devices=all" nor "--device=usb" do
influence the portal.

Hidding a device always take precedence over making them enumerable,
even when a blanket permission ("--usb=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]

---

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

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

Signed-off-by: Hubert Figuière <[email protected]>
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.

2 participants