Making snapd's generated desktop files usable from within a snap

As part of the effort to support a fully confined desktop session, I’ve been experimenting with the experimental desktop-launch interface implemented by snapd PR #8699. This gives a snap application access to a D-Bus interface that allows it to launch other snap packaged applications via the .desktop files they install. This was intended as a middle ground between complete isolation and providing back-door access to arbitrary command execution outside the sandbox.

The interface boils down to making D-Bus calls like:

gdbus call \
     --session \
     --dest io.snapcraft.Launcher \
     --object-path /io/snapcraft/PrivilegedDesktopLauncher \
     --method io.snapcraft.PrivilegedDesktopLauncher.OpenDesktopEntry \
     chromium_chromium.desktop

To make use of this in the prototype, I created new launcher .desktop files for each application I wanted to be able to launch, using a gdbus call invocation like above in the Exec= line.

My thought was that this would eventually be replaced with some code in glib of gnome-shell that would recognise when it was running under confinement and being asked to launch a snap .desktop file, and call this D-Bus API rather than trying to launch directly. However in discussions yesterday @laney queried whether it would be possible to do this without patching GLib.

The main concerns here were that we don’t want to maintain a patch against GLib (possibly indefinitely), and that it would be nice to have a solution that worked for all implementations of the XDG Desktop File specification rather than just a single one. So that got me thinking about what would be necessary to make that work.

At present, snapd writes out desktop files with Exec lines that look like:

Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/chromium_chromium.desktop /snap/bin/chromium %U

The BAMF_DESKTOP_FILE_HINT part was intended for use by Unity 7’s old bamf-daemon to match processes to the desktop file that launched them (by parsing /proc/$pid/environ). But it also means that command knows which .desktop file it was run from.

The actual /snap/bin/chromium here is a symbolic link to /usr/bin/snap, which does the equivalent to running snap run chromium when run through the symlink. If the confined application had access to the /snap/bin/* symlinks and could execute /usr/bin/snap, then presumably the snap command could detect that it was running under confinement and instead make the OpenDesktopEntry D-Bus call. That would effectively make the same desktop file usable both inside and outside of the confinement sandbox.

There are a few variations to this setup that I can think of:

  1. rather than calling the regular /usr/bin/snap, use layouts to replace that path with our own script (which the /snap/bin/* symlinks will now point to).

  2. Rather than complicating the code path used for regular command line invocation of snap commands and services, we could rewrite the desktop files to use a desktop file specific snap sub-command. We’d still have the same “should I run this command directly or delegate via D-Bus?” code, but it would be limited to the case of application launches via desktop files.

What do you think?

1 Like

Heya, I got asked to please reply to this to bump the topic back up. :slightly_smiling_face:

Yeah, this is pretty much what I had in mind. Without knowing too many of the details: if we’re talking about GLib inside the session snap being able to call OpenDesktopEntry when it detects we’re under confinement then it should be possible for /usr/bin/snap to do this too. That feels better to me, as it’s centralised the snap specific implementation under snapd’s control, and is a solution that would work for all implementors of the desktop entry spec.

1 Like

One thing that is somewhat unfortunate about adding this code to snap run or some other snap foo subcommand is that we haven’t typically allowed access to the /usr/bin/snap command at all for strict snaps, because we don’t want to make it easy for folks to manage snaps via snap commands inside the same snap. Allowing access to execute /usr/bin/snap would now mean that folks can at least somewhat use the snap command inside their snap, which is probably okay from a security perspective since privileged operations like snap install actually are executed via /usr/bin/snap making REST calls to the snapd socket which would still be denied anyways, but it would probably lead to more forum posts about “I called snap foo in my snap and it doesn’t work”. At least today that situation just fails because they can’t execute snap at all.

A counter-point to the above though is that it could be added to just a specific interface that is used by this “session snap” if I’m understanding correctly. Another point is that we do actually have some snaps (ubuntu-image and make-system-user come to mind) which have legitimate uses for calling unprivileged snap commands (though note both of those are classic confined), so if we do decide to support some snaps calling the snap command we could follow through and make that a supported thing by setting up the bind mounts of /usr/bin/snap inside the snap mount namespace to either like /snap/snapd/current/usr/bin/snap or /snap/core/current/usr/bin/snap, etc.

I’m not super concerned with the D-Bus call/BAMF_DESKTOP_FILE_HINT check adding latency to snap run, we already know that snap run is rather slow from some other benchmarking we did for a commercial project recently and have discussed potentially splitting out specifically snap run into it’s own binary, which would alleviate some of the concerns I mentioned above

1 Like

I’ve made a proof of concept that allows automatically uses the io.snapcraft.Launcher service when running .desktop files from within snaps.

1 Like