- contribute
- design
- Application isolation
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:
- individual Debian packages that ship confinement profiles, e.g. Tor;
- the apparmor-profiles package;
- the apparmor-profiles-extra package;
- our tails/torbrowser-launcher repository:
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:
- config/chroot local-includes/etc/apparmor.d/tunables/home.d/tails
- config/chroot local-patches/apparmor-adjust-pidgin-profile.diff
- config/chroot local-patches/apparmor-adjust-tor-abstraction.diff
- config/chroot local-patches/apparmor-adjust-tor-profile.diff
- config/chroot local-patches/apparmor-adjust-totem-profile.diff
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.:
- for AppArmor
- for GNOME sandboxed applications:
- Initial ideas on portals for file access thread on gnome-os-list
- https://wiki.gnome.org/Design/OS/Sharing
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:
- config/chroot local-includes/usr/lib/systemd/user/tails-create-tor-browser-directories.service
- config/chroot local-includes/usr/local/lib/create-tor-browser-directories
- config/chroot local-includes/usr/lib/systemd/user/tails-add-GNOME-bookmarks.service
- config/chroot local-includes/usr/local/lib/add-GNOME-bookmarks
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:
~/Tor Browser/
, which is amnesiac, as everything else in Tails by default; this is set to be the default download directory (config/chroot local-includes/usr/share/tails/tor-browser-prefs.js);~/Persistent/Tor Browser/
, that is persistent, and only created when~/Persistent/
is itself persistent and read-write.
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.