I’ve been taking a look at what would be involved in writing SELinux policy for covering the existing interfaces. My current impression is that this is going to be (a) difficult, (b) fragile and © difficult, but let’s start with a quick introduction to SELinux and some fundamental differences between it and Apparmor.
SELinux is based around processes running in contexts and interacting with resources that have labels. The SELinux policy describes how these interact - for example, a process running in the user_t context could be given permission to read files labelled user_home_t by providing
allow user_t user_home_t:file read;
Any files that are labelled as user_home_t would then be readable by any process in the user_t context (assuming that it also satisfied traditional Unix permissions). All access is mediated by labels - it’s not possible to grant access to a specific path.
Like Apparmor, policy isn’t restricted to the kernel - dbus and systemd (for example) are SELinux aware and can interpret policy to determine whether a process running in a specific context should have access to a resource they control. That side of things seems pretty straightforward.
So, conceptually, a lot of the this works identically on SELinux and Apparmor - a policy module is synthesised for each Snap and loaded into the system-wide policy. However, SELinux policy can’t exist per-process - instead, we need to generate a unique context for each app in a Snap and grant permissions only to that specific context. Something along the lines of SVirt (http://danwalsh.livejournal.com/30565.html) would work here, using a generic context with an MLS extension that’s unique to the app.
This is where things get more difficult. As previously mentioned, SELinux is label-based rather than path-based. So, where an existing interface might do:
/dev/tpm0 rw
an SELinux policy would have to do:
allow snap_t:s4:c123 tpm_dev_t:chr_file rw_chr_file_pmerissions;
The problem is that there’s no inherent way to know in advance what hte label of /dev/tpm0 is - this is something that may differ between distributions. Some distributions may provide a specific label for /dev/tpm*, others may have it labelled with a generic device type. In the latter case, granting access to that will also grant access to any other device nodes with the same label.
Dbus has the same problem. The policy for permitting a task to speak to dnsmasq over DBus would look something like:
allow snap_t:s4:c123 dnsmasq_t:dbus send_msg;
Where, again, dnsmasq_t may be entirely different between distributions.
In the majority of cases this probably doesn’t provide an impediment to writing a policy that works for a specific distribution - Fedora has sufficiently fine-grained labels that each existing interface can probably be ported to that. My concern is that doing so in a way that’s entirely consistent with the Apparmor implementation may not be possible, and that it may be differently inconsistent across different distributions. This may result in the same interface giving access to different functionality on different distributions.
The other concern I have here is that there’s no good way to keep all of this in sync. Distributions may provide policy updates during a release, and Snapd would have to be updated in lockstep with that. Worse, if any changes are made to an interface, the same change will need to be made to every implementation (Apparmor, Fedora SELinux, Debian SELinux, Suse SELinux and so on) and it’ll need to be validated against every distribution that has support. Realistically, this isn’t possible - mistakes will be made, and things will end up broken as a result. The test matrix is just going to be too large.
I don’t think per-distribution SELinux policy is going to be viable, which I think means there’s two real choices here:
- Use SELinux to confine Snaps from each other, but for no other purpose. All other resource access would then be via Portals, as in Flatpak.
- Add kernel support for stacking monolithic LSMs and have Snaps running in an environment where Apparmor policy is validated before SELinux. Per-Snap access control would remain in the form of Apparmor policy, while snapd itself would be confined by SELinux. This would also require modification to dbus and systemd to support validating requests against multiple policies.
To be fair, I’m leaning to the pessimistic here - I’ve maintained SELinux policy at two separate companies, and I can’t claim that the experience has been a bag of fun in either case. Does anyone see an easier way of doing this that doesn’t run into these problems?