Application isolation

Goals

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

AppArmor also mediates access to some other types of resources and operations, such as mounts, signals, and user namespaces.

As of Debian 13 (Trixie) and Tails 7.x, other types of resources, such as Unix sockets and D-Bus, are not part of the isolation goals yet, which implies some of our AppArmor profiles have sandbox escape bugs. There are also sandbox escape bugs caused by dconf access.

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, for applications installed from the repositories of a general purpose distribution such as Debian, no good way exists to access resources outside the AppArmor sandbox. This is necessary, for example, to safely open a file chosen by the user outside of the sandbox, or to access the webcam.

So AppArmor profiles shipped by general purposes distributions often grant a very broad range of permissions, such as read or write access to most of $HOME, which makes them much less useful than they could be.

This problem has essentially been solved by Desktop Portals, thanks to the Flatpak project. Once applications use these portals instead of accessing resources directly, their AppArmor policy can be made much stricter and more effective… with some limitations, that are described below for Tor Browser.

Tor Browser

As of Tails 7.8, Tor Browser is confined with AppArmor in Tails.

We also run it as a Flatpak. For details, see design, which also discusses some security and isolation aspects.

Tor Browser's D-Bus access is mediated by xdg-dbus-proxy with rules set up by flatpak-run.

Thanks to the XDG Desktop Portal service the browser process itself does not need access to the filesystem for the purpose of downloading or opening files since it can be mediated through the FileChooser portal.

Unfortunately, various bugs in Firefox and fundamental limitations with the Desktop Portal technology creates a poor user experience for some scenarios involving directories that are inaccessible to the browser process. The FileChooser portal is not affected by these issues when handling files and directories that are accessible for the browser process, so for this reason we make the XDG user directories fully accessible, which includes the Tor Browser's default downloads folder, ~/Downloads.

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.

D-Bus

Many AppArmor profiles currently allow the confined process to escape its sandbox via D-Bus:

  • Unmediated access to the session bus ⇒ arbitrary code execution

    As Simon McVittie highlighted, some session services can be asked to execute arbitrary code via D-Bus (systemd, gnome-session, dbus-daemon, etc.).

    For example, according to Aaron Rainbolt: "unrestricted access to D-Bus can be used to modify the environment systemd starts new processes with, and can be used to restart services, allowing an application to escape AppArmor confinement by getting systemd to restart a service with an LD_PRELOAD environment variable pointed at a malicious library".

  • abstractions/dbus-session gives unmediated access to the D-Bus session bus

    Many AppArmor profiles include this abstraction.

    This can be fixed on Debian 14 (Forky) and thus Tails 8.x, by instead including abstractions/dbus-session-strict, and adding fine-grained dbus rules to allow only the required operations (assuming they are safe).

  • D-Bus access is not mediated at all in Tails 7.x

    On Debian 13 (Trixie) and thus Tails 7.x, all D-Bus is allowed even if access to the D-Bus socket (e.g. /run/user/1000/bus) is not explicitly granted, i.e. even if no abstractions/dbus-session* is included: even though the Linux kernel already supports Unix socket access mediation, actually enforcing it requires a parser that supports ABI 5.0, which is not supported by AppArmor 4.x userspace.

    This can be fixed in Debian 14 (Forky) and thus Tails 8.x, by upgrading AppArmor userspace to 5.x, and ensuring all relevant profiles use ABI 5.0 or greater.

Apps that run under Flatpak are generally not affected by these problems, thanks to xdg-dbus-proxy, as long as the Flatpak manifest for the app does not grant too broad D-Bus access.

dconf

Many AppArmor profiles currently allow the confined process to escape its sandbox via dconf: as Simon McVittie put it, write access to the user's dconf database "is a complete sandbox escape via any dconf/GSettings option that can be configured to run arbitrary commands, for example GNOME's desktop-wide custom keyboard shortcuts".

Such write access is available via any of these paths:

  • @{HOME}/.{cache,config}/dconf/user rw
  • @{run}/user/*/dconf/user rw
  • access to the dconf D-Bus service
    • Used as the fallback by apps when access via the aforementioned dconf/user paths is blocked.
    • So as long as D-Bus access is not mediated, dconf provides yet another way to leverage D-Bus access into arbitrary code execution.

Note that read access to dconf/GSettings is needed to react to session configuration changes, e.g. "Large text".

Apps that run under Flatpak don't need such access: they're using their own dconf database under their own data directory.

Per-app caveats

  • cupsd: includes abstractions/dbus, so presumably it can do anything on the system bus as root, even in Tails 8.0 (Forky); same on root's session bus in Tails 7.x (due to lack of Unix socket mediation). Either of those probably means arbitrary code execution as root.

    Upstream bug: Debian bug #1137638

  • haveged: can probably run arbitrary code as root via root's D-Bus session bus. We plan to remove it.

  • OnionCircuits:

    • Initially confined with AppArmor primarily for onion-grater, not needed anymore as long as we use network namespaces for that.
    • The AppArmor sandbox is currently ineffective due to D-Bus session access, which indirectly also grants dconf access; the app should automatically lose these in Tails 8.0 (Forky), after which we will reconsider this topic.
  • OnionShare:

    • Initially confined with AppArmor primarily for onion-grater, security sandboxing has never been a goal of these profiles.
    • The AppArmor sandbox is currently ineffective due to D-Bus session access, which indirectly also grants dconf access; it's OK, but we have plans to try to fix that if it's cheap (#21524, maybe #18123).
  • PDF reader (Evince, Papers): the AppArmor sandbox is currently ineffective due to D-Bus session and dconf access; we have plans to fix that (#21521).

  • Pidgin: the AppArmor sandbox is ineffective due to D-Bus session and dconf access; we plan to remove Pidgin so we won't fix that.

  • Thunderbird: the AppArmor sandbox is ineffective due to D-Bus session and dconf access, and also because the policy is wide open; we have plans to fix that (#21374, maybe #21522).

  • tor: runs as the debian-tor user, which has no D-Bus daemon.

  • Tor Browser: see application isolation.

  • Totem: the AppArmor sandbox is ineffective due to D-Bus session and dconf access; we have plans to fix that (#7929).

  • Unsafe Browser: see Unsafe Browser.

  • Everything else: other apps generally run unconfined, which is a problem as well. We have ideas to fix that (#21525).

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.