Goals

For now, we are only aiming at filesystem resources isolation: that is, making sure that e.g. Pidgin cannot read the GnuPG keyring.

Other types of resources, such as signals, X, ptrace, sockets, D-Bus etc. are not part of the isolation goals yet.

Tools and basic configuration

For now, we have decided to use AppArmor to isolate applications, mostly because:

  • it is simple: AppArmor policy is relatively easy to understand, improve, and audit;

  • it is the best supported mandatory access control framework in Debian; it wasn't too hard to reach this point, and there is quite some room for improvement via collaboration with other distributions, most notably Ubuntu.

The apparmor package is installed, and AppArmor is enabled by default in Debian's Linux kernel since 4.13.10-1.

Confinement profiles

The AppArmor confinement profiles included in Tails come from:

To get the full and current list, run aa-status as root inside Tails.

Hacks to support the Live system usecase

Most Live systems use a union filesystem to provide the operating system with a read-write filesystem, based on a read-only branch (typically, SquashFS) and a read-write one (most often, tmpfs).

Unfortunately, AppArmor currently does not support union filesystems very well, because the LSM hooks do not allow it to distinguish between an access to the upper layer, and an access to the loop-backed underlying layer.

So, we have to adjust profiles a bit to make them support the paths that are actually seen by AppArmor in the context of Tails.

First, we are using a couple of aliases so that rules applying to "normal" paths (e.g. /home/amnesia/.gnupg/) also apply to Debian Live -specific paths, such as /lib/live/mount/overlay/rw/home/amnesia/.gnupg/. And, to avoid subsequent problems with overlapping rules, and to mitigate the increased policy compilation time (see details below), we also patch some some very broad rules to make them not apply to /lib/live/*. All these changes live in config/chroot local-patches/apparmor-aliases.diff, config/chroot local-patches/apparmor-alias-dot-d.diff and config/chroot local-includes/etc/apparmor.d/tunables/alias.d/tails.

Second, few more targeted adjustments are also applied:

Below, we discuss various leads that might avoid the need for coming up with such adjustments, and maintaining it.

User experience matters

Currently, no good way exists to let the user choose an arbitrary file each time they want to open or save one, without leaving the AppArmor profiles wide-open and then much less useful.

Solutions to this problem are work-in-progress in various upstream places, e.g.:

The idea is generally to introduce a privileged mediation layer between applications, the GTK file chooser and the filesystem. So, some day we can solve this problem in better ways, but we're not there yet.

Tor Browser

As of Tails 1.3, the Tor Browser is somewhat confined with AppArmor.

Given we cannot seriously allow the Tor Browser to read and write everywhere in the home and Persistent folder, we had to allow it to read/write files from/to one specific directory, and make it so the user experience is not hurt too much.

Now, let's say we have a downloads/uploads directory that's shared between the Tor Browser, the file browser, and all non-confined applications. That directory is called ~/Tor Browser/, and a GTK bookmark pointing to it is created at login time:

Then, we have a usability issue: the space available in that directory is limited by the free system memory (RAM). So large file downloads may fail. Note that Firefox doesn't mind letting users start downloading a file to a directory that hasn't enough room available to store it entirely, so this problem is not specific to downloading to an amnesiac directory.

Still, we thought it would be good to allow users to download large files from Tor Browser to their Persistent Storage, so we have introduced a second downloads/uploads directory: ~/Persistent/Tor Browser/, that is created whenever the "Personal data" (aka. ~/Persistent/) feature is turned on. In that case, if persistence was activated read-write, another GTK bookmark pointing to that directory is created at login time.

This seemed to be better than introducing yet another persistence feature (e.g. for ~/Tor Browser/): having downloads be either always amnesiac or always persistent (unless you restart or do stuff outside of the browser) would seem like a regression, since it breaks one of the core Tails properties. And forcing it to be a persistent folder by default actually has security issues since secure deletion does not work as expected on Flash memory. So, we decided that users need to have the option to download either to an amnesiac (~/Tor Browser/) or persistent (~/Persistent/Tor Browser/) place, like it was the case previously.

So, in a nutshell we give Tor Browser access to:

Note that we don't call these folders "Downloads", because e.g. if someone creates an ODT file and wants to upload it, having to move it to a folder called "Downloads" sounds really weird.

Implementation details and caveats, and future work

More confinement profiles

As part of the Debian AppArmor Team, we are working to get more well-tested and maintained profiles integrated into the distribution, and to improve cross-distribution collaboration in this area.

Isolating more types of resources

With AppArmor 2.9, once the corresponding kernel patches are merged into Linux mainline, we will get support for mediating many more types of resources: D-Bus calls, sockets, signals and so on.

Linux containers may also be a good way to isolate more types of resources.

Using alias rules to avoid modifying profiles

A number of problems prevent alias rules from just working in the context of Tails. The following discusses these complications, and a few possible solutions.

Overlapping rules

Update: this got implemented as described earlier in this document.

Alias rules can generate rules that overlap (and conflict) with existing ones, which can cause the policy to fail to compile.

E.g. the sanitized_helper profile (sourced by the Evince profile and many others) contains this rule:

/lib{,32,64}/**/ld{,32,64}-*.so     mrix,

which, once combined with this alias:

alias / -> /lib/live/mount/rootfs/filesystem.squashfs/,

will end up overlapping a lot of the rules generated for the alias. E.g.

/bin/* Pixr,

results in a rule of:

/lib/live/mount/rootfs/filesystem.squashfs/bin/* Pixr,

being generated, however since the alias command does not remove other rule sets, it only adds new rules. We end up with both:

/lib{,32,64}/**/ld{,32,64}-*.so     mrix,
/lib/live/mount/rootfs/filesystem.squashfs/bin/* Pixr,

which causes a conflict between ix and Pix.

To workaround this problem, we had to change the /lib{,32,64}/**/ld{,32,64}-*.so mrix, rule into essentially:

/lib{32,64}/**/ld{,32,64}-*.so     mrix,
/lib/{[^l],l[^i],li[^v],liv[^e],live[^/]}**/ld{,32,64}-*.so mrix,

which allows the profile to compile, as the x conflict has been removed.

Needless to say, this kind of regexp is painful to write, audit and maintain. Things could be nicer if AppArmor supported set operations; instead, we could do something like (syntax not finalized):

/lib/{^live**}/ld{,32,64}-*.so     mrix,

Other problematic overlaps include e.g. this rule from the sanitized_helper profile:

audit deny owner /**/* m,

... that will take away the executable mmap permission from all applications under /lib/live/ path, if the root user (who owns the file) tries to launch an application.

This can possibly be fixed using rewrite rules instead of aliases, or by an update to the AppArmor permission merging logic that would give us a way to define that the alias rules should have priority in the union.

Fixing such problems one after the other may be doable, but regardless: the way alias rules affect the policy as a whole, especially once combined with globing, makes our policy harder to understand, reason about, and audit.

Persistence

It may be that more aliases are needed to support the bind-mounts set up by live-boot when using the persistence feature. It may even be that these aliases need to be dynamically added, in function of the persistence configuration... that is, at login time. If that was the case, then the entire policy would need to be recompiled at login time, which could make the user experience very painful, especially considering that alias rules vastly increase policy compilation time.

Increased policy compilation time

Alias rules dramatically increase the policy compile time (e.g. 100 seconds for the Evince profile, that can be brought down to 8 seconds with the aforementioned rule change in the sanitized_helper profile).

To mitigate that problem, we ship a cached pre-compiled policy, that the parser will use as long as it considered it valid, and then no policy compilation happens at boot time; if the parser detects that the policy is out of date, then it will ignore the cache and compile the policy. For instance, this is what was done for the Ubuntu phone. To generate a valid binary policy cache at ISO build time:

  • we call the parser with a --features-file value that matches the pinned feature set used at Tails runtime; this comes for free since we run the parser from the build chroot, where this variable is set in the same /etc/apparmor/parser.conf file that will be used at runtime;

  • we use a similar enough version of the parser to the one installed in Tails; here again, this comes for free if we run the parser inside the build chroot;

  • the version of the kernel used during the ISO build process, that is the one the Vagrant box is running, should not matter: for example, since 2.13-1, the apparmor package maintains separate caches per feature set and a binary cache built on Linux 4.18 is still valid and used when booting on Linux 4.19 with the same features-file setting.

This is implemented in config/chroot local-hooks/99-cache-AppArmor-policy.

Resources: apparmor_parser(8), apparmor_parser --help, examples in parser/tst/features_files/*, and parser.conf.

Using rewrite rules to avoid modifying profiles

Other than alias rules, another option to avoid modifying profiles would be to use rewrite rules. They're basically the same as alias rules, except that it doesn't duplicate rules, so no conflicting rules are generated.

It remains to be researched if rewrite rules would work in our use case: e.g. it might be that some files are seen as read from the SquashFS initially, and written to the overlay. If that would be the case, then we would need to duplicate some rules in profiles to add back some paths that were rewritten.

overlayfs

Some ongoing work on AppArmor (labeling, extended conditionals) will help support overlayfs with less kludges. Time will tell whether the result meets our needs.

Linux containers

Using Linux containers for application isolation is being researched separately: Linux containers.

Credits

We owe a lot of thanks to John Johansen (john.johansen@canonical.com) for his patience and support. Substantial parts of this document were adapted from explanations he provided to us.