Can interfaces define per-user bind mounts?

There some ongoing work to implement per-session snapd that could perhaps just implement the portal logic.

That is what I would prefer too. See https://github.com/snapcore/snapd/pull/3260 and Integrate snapd-xdg-open into snapd repository for a few more details.

1 Like

While re-implementing the URL opener portion of the portal inside snapd is not too difficult, I don’t think that is a viable option for things like the document portal and file picker. Getting the security control and other semantics correct is likely to be a lot more difficult than you might first think.

Also, a partial reimplementation of the portal is probably going to cause more problems than it solves. If you tell GTK to use the portal (either by patching GTK, or just setting the GTK_USE_PORTAL environment variable), it won’t just change how it tries to open URLs: it will also change the file choosers, network monitoring code, printing support, etc.

I think the most sensible way forward is to teach xdg-desktop-portal how to tell when it is talking to snap confined apps and just let it do its thing.

I am leaving that for the relevant people to comment but I think the crucial point here will be to marry those two different security systems.

I’m pretty sure we want user session snapd to assist in this. GTK itself should just do what it does internally but the provide of that thing, which must be a stable protocol, might be either snapd or the adapted portal code. I’m sure we can figure out a way to make it work nicely and reliably over time.

Just as a quick note, currently XDG_RUNTIME_DIR is set to /run/user/<uid>/snap.$SNAP_NAME. This is going to have to change to accommodate wayland (see Wayland, dconf and XDG_RUNTIME_DIR).

I had shot at updating the snap-confine AppArmor policy, adding the following rules:

# support for xdg-desktop-portal documents portal
/run/user/[0-9]/** r,
mount options=(rw bind) /run/user/[0-9]*/doc/by-app/* -> /run/user/[0-9]*/doc,

But I’m still having trouble getting this to work. The weird thing is that I’m not seeing any denial error in syslog. Looking at the strace output, I just get a permission denied error from the syscall:

mount("/run/user/1000/doc/by-app/snap.pkg.file-roller", "/run/user/1000/doc", NULL, MS_NOSUID|MS_NODEV|MS_BIND, NULL) = -1 EACCES (Permission denied)

Is there anything obvious that I’m doing wrong here?

Are you really not seeing any apparmor denials? Other than that I don’t know what could be blocking this.

I’ll give it a try on a second machine just to make sure there isn’t something weird in my build environment.

So I’m seeing the same behaviour on the second system. They’re both running Zesty, if that makes a difference. Putting the policy into complain mode doesn’t make a difference either.

I wonder if the EACCES failure is being triggered before the AppArmor checks get a chance to look at the syscall?

FYI, DAC is checked before LSM, so certainly possible. The fact that it doesn’t work in complain mode is a strong indicator a DAC check (uid/gid, capability, traditional permissions, acls, etc) is the issue.

Okay, I’ve tracked down what is going on here: the path I’m trying to bind mount is within a FUSE file system. By default, FUSE restricts access to a file system to the user that mounted it, unless either of the allow_other or allow_root options are used. Neither of those options are specified by xdg-document-portal, so I get the permission error when snap-confine tries to set up the mount name space as root.

This is working with Flatpak, so I guess I’ll check what they’re doing differently.

Okay, so I think I know what is going on now: bubblewrap (flatpak’s equivalent to snap-confine) switches back to the user’s uid while preserving some root capabilities:

So this allows them to call mount() as a normal user, which in turn allows that mount call to reference paths within the FUSE file system. For added security, it implements privilege separation by forking and dropping the capabilities in one of the processes.

This is looking a lot more complicated than a simple afternoon hack :frowning:

I’ve not thought it all through, but allow_root may be acceptable here. For snaps, we could have ‘owner’ match for these paths if we wanted (we already have owner /run/user/[0-9]*/snap.@{SNAP_NAME}/...). For non-snaps, not having ‘allow_root’ as a security mechanism on the surface seems specious since if you are root you can replace libfuse and make it not care about allow_root. From the link you gave:

libfuse-specific mount options:
       These following options are not actually passed to the kernel but
       interpreted by libfuse. They can be specified for all filesystems
       that use libfuse:

       allow_root
              This option is similar to allow_other but file access is
              limited to the filesystem owner and root.  This option and
              allow_other are mutually exclusive.

(note, ‘allow_root’ is not passed to the kernel and is only enforced by libfuse, therefore it can easily be subverted).

Just to be clear, the FUSE file system in question here is outside of the sandbox (it is part of a trusted helper, to use the Ubuntu Touch terminology). The problem is that I want to bind mount from a subdirectory inside that file system, which fails because root is prevented access.

Now we’ll need to modify the xdg-document-portal source code to allow it to detect when it is talking to a snap over D-Bus, so we could certainly turn on allow_root. But there are legitimate reasons why this isn’t on by default, so I think it is reasonable to ask whether we should handle this case if/when snap-confine gains the ability to set up per-user mounts.

Yes.

That is what I was speaking to. I was questioning whether there were legitimate reasons why it wasn’t on by default. Like I said, I didn’t think it all through, so I’m only questioning it. :slight_smile: Do you have details on what this is trying to protect?

There’s this comment in the kernel side code:

So I doubt the xdg-document-portal developers consciously decided to block root access: they just used the defaults, which allowed all the behaviours they needed.

@jdstrand: do you have any more thoughts about this? I have put together a demo PPA that uses the allow_root FUSE flag here:

… but it requires that I edit the /etc/fuse.conf file to enable this feature. If we want to provide good integration for confined desktop apps, then we’ll need a real fix for this.

I started digging into this again last week, and put together the following PR (which @zyga-snapd has kindly given some feedback on already):

This doesn’t yet perform the mounts as a regular user, so doesn’t yet handle xdg-document-portal’s FUSE file system. But I thought it would be good to get some early feedback on the general design.

There is also the question of whether it makes sense to move the responsibility for creating user mounts from snap-confine to snap-update-ns. On the plus side, it centralises all the mount code. On the minus side, it’d be an extra fork+exec each time you run an app that has user mounts. Maybe that isn’t too bad though.

2 Likes

Since this would be only the cost paid when we run a snap for the first time (as a given user) I think it is well worth the price. I have some upcoming patches that will greatly complicate the mount namespace setup (the profiles will grow lots of features) so I think this is the right way to proceed.