AppArmor FAQ

John Johansen
2007-04-16T21:33:26Z

[-- Attachment #1: Type: text/plain, Size: 10606 bytes --]

Here we present our direct responses to the most frequent questions from
the AppArmor from the 2006 post.

Use of Pathnames For Access Control
-----------------------------------

Some people in the security field believe that pathnames are an
inappropriate security mechanism. This depends on what you are
primarily trying to protect, and the rest follows from that.

Label-based security (exemplified by SELinux, and its predecessors in
MLS systems) attaches security policy to the data. As the data flows
through the system, the label sticks to the data, and so security
policy with respect to this data stays intact. This is a good approach
for ensuring secrecy, the kind of problem that intelligence agencies have.

Pathname-based security (exemplified in AppArmor, and its predecessor
Janus http://www.cs.berkeley.edu/~daw/janus/ and other systems like
Systrace http://www.citi.umich.edu/u/provos/systrace/ ) attach security
policy to the name of the data.

Controlling access to filenames is important because applications
primarily use those names to access the files behind them, and they
depend on getting to the right files. For example, login(1) expects
/etc/passwd to resolve to a valid list of user accounts. In the
traditional UNIX model, files do have names but not labels, and
applications only operate in terms of those names. Pathname-based
security puts more emphasis on the integrity of the system, making
secrecy the secondary goal that follows.

Caveat: Both label-based security and pathname-based security can
provide both secrecy and integrity protection, the above discussion is
only about which model makes it easier to provide which kind of security.

We acknowledge that not all objects on a UNIX system are paths, and we
agree that there is value in also protecting non-path resources.
Contrary to popular belief, AppArmor is *not* "Pathnames R Us", but
rather "Use native abstractions to mediate stuff": when you mediate
something, you should use the native syntax that users normally use to
access the object. This follows the UNIX philosophy of "least surprise"
so that users can understand the specification. Pathnames are the
natural notation for users to understand what file access rights are
being granted in the policy, and so AppArmor uses shell syntax for fully
qualified pathnames, including shell syntax wildcards.

Similarly, AppArmor grants access to POSIX.1e capabilities by name, the
name of the capability. In future work where AppArmor will add network
access control, the notation will resemble IPTables firewall rules. This
is an important part of what makes AppArmor usable: always using the
native abstraction for mediating access.

We also acknowledge that pathname based access control requires a way to
perform pathname matching in the kernel, and this comes at a cost higher
than comparing object labels -- which assumes that all objects in the
system already have the appropriate labels.

However, those concerned with performance should note that AppArmor
overhead is already quite low (single-digit percent slowdown). Security
is rarely performance-neutral, and AppArmor, and SELinux, are no
exception. However, that overhead is small, and can be selectively
avoided by not applying AppArmor to performance-sensitive programs.

It is also easy to overlook the fact that putting all those labels in
place is a pretty expensive operation as well, particularly considering
large file systems. So by providing string matching in the kernel,
AppArmor trades run-time performance to grant reduced administrative work.

It has been suggested that AppArmor's pathname-based syntax could be
compiled into SELinux policy, and this is in fact what the SEEdit
project http://seedit.sourceforge.net/ does. However, any change in
policy requires a complete re-labeling of the file system, and the
policy cannot apply to files that do not yet exist. AppArmor's in-kernel
string matching allows for policy specifying access to files that might
come to exist in the future.

Use Of d_path() For Computing Pathnames
---------------------------------------

We have been criticized for the use of d_path(), for various reasons:

- heuristic discovery of the vfsmount of a dentry,
- inability to reliably identify deleted files,
- inability to detect unreachable paths,
- ambiguity of paths for chroot processes,
- file lookup and the access check are not atomic.

Most of these issues are fixable (and fixed in the meantime), while the
non-atomicity is not really an issue.

Because struct vfsmount was not available to LSM hooks for computing
pathnames from (dentry, vfsmount) pairs, the version of AppArmor posted
last year used heuristics for rediscovering the vfsmounts associated
with dentries -- and possibly the wrong ones. We are now passing the
vfsmount objects through to all LSM hooks that compute pathnames, and so
this heuristic is gone, and now we always use the appropriate vfsmount.

The d_path patch already in the -mm tree allows reliably identifies
deleted files (at least when using the underlying __d_path()), as well
as unreachable paths.

One of the patches in the AppArmor series ensured that the result
returned by __d_path() is consistent even in face of remounts -- the
path returned will always be the name by which the file question was
reachable at the time when d_path was called.

One of the patches in the AppArmor series introduces d_namespace_path(),
based on __d_path(), which gets rid of the chroot ambiguity by computing
paths relative to the namespace root rather than the chroot.

The file lookup and the LSM access check are not atomic with each other
as far as the filesystem namespace is concerned: files may be renamed or
even removed between the lookup and the access check. It is important
that the lookup happens before the access check, and that the access
check happens before the access, but beyond that, all we care about
security wise is the pathname that the file in question had at the time
of the access check: the file could have been renamed shortly before the
lookup, or shortly thereafter -- this makes no difference because the
only thing we are interested about is the current name of the file.

In case a file is successfully looked up and then deleted before the
access check, it is also obvious how to proceed: the file might as well
have been deleted before the lookup, so we pretend that it was, and
fail the access with errno set to ENOENT. (This would be fatal for
accesses via open file descriptors -- temporary files are often accessed
after being deleted -- so we make this a special case, and allow
per-file-descriptor accesses to deleted files.)

The "race window" described above in fact is not only between the lookup
and the access check, but is much wider: when a process looks up a file
relative to its current working directory, the lookup is relative to
that directory, and the directory may long since have been renamed. The
same is true for absolute paths, which are only absolute relative to the
chroot directory. Still, what we care about is the pathname of the file
at the time of the access check, as above.

The previous version of AppArmor was basing access decisions on the
pathnames of files, even if those files were already deleted. While it
can be argued that the pathname that can still be recovered from a
deleted file sometimes holds an informational value, it is not obvious
that the name still has value from a security point of view. We no
longer do that -- instead, we differentiate between file descriptor and
name based accesses.

Working With Name Spaces
------------------------

AppArmor pathname expressions in profiles are relative to the root of
whatever filesystem namespace a process is in. This is secure because
processes can only create new namespaces if they have the CAP_SYS_ADMIN
capability, which confined processes are not supposed to be granted by
their profile because it would allow them to break out of the
confinement, and they can only manipulate their namespace using the
mount system calls, which confined processes are currently denied as well.

Unconfined processes can still set up different namespaces, and AppArmor
will apply the global policies to each of those namespaces
independently. This prevents AppArmor from confining ClearCase because
of ClearCase's heavy of namespaces, for example.

We are considering how to make AppArmor more flexible to allow some
controlled kinds of mounts, and how to make namespace support more
flexible. One way of improving the namespace support would be to allow
processes to switch between different sets of profiles. The process
setting up a new namespace could then switch to the set of profiles
appropriate for the new namespace as well. (Switching between different
sets of profiles automatically likely will not work -- namespaces do not
have an intrinsic name, and so if the same binary creates multiple
namespaces, there would be no way of telling one from the other.)

Alternate Implementation Strategies
-----------------------------------

It has been observed that computing the pathname after an object has
been looked up is counter-intuitive and racy, and that the pathname
should instead be constructed forwards, during the lookup. While
appealing, this approach breaks for lookups that are relative to the
current working directory (or relative to another open directory like
with openat(2)): in that case, the parent directories of the directory
are never visited (unless the relative path goes all the way up to the
root with ``..'').

All of these cases force the backwards construction of the pathname from
the middle. The pathname up to the root also cannot be cached because
directories along the path could get renamed at any time. Therefore,
since we must be prepared to do backward path construction anyway, we
might as well simplify the mechanism by computing the entire pathname
after looking up the object all of the time.

It has also been suggested that we use a shadow tree, where the module
maintains a shadow mapping from current dentries of interest to the
pathnames for those dentries. However, a very difficult problem with
this approach is that you have two large, complex data structures that
must be kept perfectly synchronized. This particularly becomes a problem
for renames, especially directory renames, which cause a change in the
fully qualified pathname of every file and directory below it,
necessitating large changes to the shadow tree. Our attempts to make
this approach work resulted in worse problems then we had with d_path.

[-- Attachment #2: Type: application/pgp-signature, Size: 189 bytes --]