Monday, July 4, 2016

The "Other" Linux MAC Software: AppArmor

Hey everyone - We have spent a lot of time talking about security measures that are somewhat similar in what they do at a high level.  The implementations differ, but AppLocker, SELinux, and AppArmor (which we will talk about today) implement varying levels of mandatory access control.  The reason I am harping on this topic so much is because I do not feel that it is well understood.  While I have mentioned in every article I have written on these defense measures, none is a foolproof way to prevent all attacks all of the time.  When they are configured correctly, they make it more difficult for an attack to be successful.

AppArmor is similar to SELinux, but the implementation details between each differ.  They both sit on top of the default discretionary access control that exists in Linux systems.  Which one you would want to implement will probably depend on which Linux distribution you are using.  AppArmor is turned on by default on Ubuntu and OpenSUSE whereas SELinux is turned on by default for Red Hat based distributions like CentOS and Fedora.  Both are available for other distributions as well.

AppArmor works by confining processes to a pre-defined set of resources (like which files it can access, how it can access the network, et cetera).   It does this through profiles.  Profiles are defined on a application located at a particular path.  If you have the same application installed to two different places on your system, you would need to write two separate AppArmor profiles to protect them.  This means you have to be diligent about what you have on your system and what needs protecting.  Applications that are not defined by a profile (called unconfined in AppArmor speak) are subject to the normal system access controls.  When the action is allowed by the system, AppArmor is consulted.  If there is no profile defined for that application, the action is allowed.

Fortunately, AppArmor has a learning mode (similar to SELinux's Audit mode) called Complain where the system logs and alerts infractions but does not actually prevent anything.  This makes it easier to develop profiles and test them before you deploy them.

As with any security measure, it is important to understand how the program you want to restrict works so that you can strike the correct balance between security and usability.  If you define a profile for an application, anything not in that profile will be denied.  Like with anything, we need to start with definitions and an understanding of how the tool works.  That way, we can use it more effectively.

Let's take a look at a small sample AppArmor profile and talk through it:
# Almost any line starting with '#' is a comment
# The next line allows us to include other files
# Including another file allows you define things that you might want to use in other profiles

# We will talk about the three types of includes: abstractions, tunables, and program chunks.
#include <tunables/global>

# @ denotes a variable
@{HOME} = /home/*/ /root/

# The next line defines the application we are writing the profile for
# The curly brace starts the profile definition
/usr/bin/sampleapp {
    #include <abstractions/base>
    /lib/** r,
    @{HOME}/** rw,
    /usr/bin/otherapp ix,

Parts of an AppArmor Profile

AppArmor profiles feature various sections, much like a program or script.  We will talk about the sections of our sample in order.  First up is includes.


Includes allow you to reuse pieces of profiles you have already written.  Includes have the following benefits:
  • Individual profiles are smaller since you can simply include some previously written pieces instead of having everything in one file
  • You can more easily propagate changes through multiple profiles.  If you change something in your include, all files depending on that include will use the updated code.  If you were not using includes, you would have to update profiles manually.
There are three types of includes:
  • Abstractions:  Abstractions are common things that an application might need like access to printers, authentication applications, and others.  This is similar to including a library in a programming language to allow you to use a certain set of functionality.
  • Program Chunks:  Program chunks are specific things that a specific program needs due to some facet of local security policy.  These are not too common.
  • Tunables: Tunables are global variables that you can define once and use in multiple places.  For example, if your application is installed in /usr/bin/myapp on one set of systems but in /opt/myapp on another set of systems, you can have the install directory defined in a tunable.  When you deploy your profile to the first set of systems, you can have the variable be one value, and when you deploy the same profile to the other set of systems, you can change the variable accordingly without changing the whole profile.
Includes start with #include (no space) and use pointed brackets '<' and '>' to let AppArmor know where to look.  Profiles live in /etc/apparmor.d, and relative directory declarations will be relative to that path.  So, in our example, tunables/global will be the file under /etc/apparmor.d/tunables/global.
When you write statements outside of the profile definition, it will apply to all applications in the profile.  Anything in the curly braces will apply to the application you noted before the curly braces.  For example, our HOME variable will apply to any application profile definitions we create in this profile, but the line where we included the base abstractions will only apply to /usr/bin/sampleapp and not any other application we define in this profile.

Local Variables

The @{HOME} variable we declared is an example of a local variable.  These are used to define paths that you want to control access to.  These are usually at the top of the file.  They are local in the sense that they are only defined in the context of the particular profile they are in.  If you want to use a variable somewhere else, a tunable would be a better option.

They are declared this way:
@{<name of variable>} = /some/path


Aliases are similar to global variables and are used to rewrite paths.  For example, if you have a profile that uses /home for everything and you have a different directory for home (say /faraway), you can create an alias which will save you from changing the entire profile.  That alias would look like this:

/home -> /faraway

Aliases should be defined after you define variables.


Rules are the access control definitions you create for each application in the profile.  Rules are a number of statements that start with an application then have a number of statements in between curly braces.  There are various types of statements you can use to write your rules.


Capabilities are certain privileges defined in the Linux kernel for functions typically given to the root user or superuser.  An example of a capability is to change ownership of a file (chown).

Network Access Control

Unfortunately, network access control in AppArmor is not very granular.  You cannot restrict an application to certain ports or IPs.  You can say that an application can only use TCP or UDP for communication or is allowed to open raw sockets.  That is about it.  For finer grained access control, a firewall would be better.

File and Directory Access Control

File and directory access control in AppArmor roughly translates from the traditional *nix style discretionary access controls with a few additions.   The permissions you would expect, such as read, write, and execute are there.  We will talk a bit more about execution in the next subsection.  Before we get into the controls themselves, we have to talk about how files and paths are specified in AppArmor.  Globs are used to specify groups of files or directoriesGlobs in AppArmor look a little like regular expressions, but they are not as powerful.  You can think of globs as wildcarding what you want to specify.  In upcoming releases, AppArmor may allow the specification of globs based on Perl Regular Expressions, but for now, we will talk about what we can do today.

The most common globs you will work with are the following:
  • *: A single asterisk is a wildcard that matches any number of characters except the forward slash (/), current directory (.), and parent directory (..).  It will match hidden files (those starting with a dot, like .bashrc).  A single asterisk only works at the directory level specified.  It is like running a command without the recursive switch.  For example, /home/* will only match files and directories in /home, but not those in subdirectories.  So it would match the directory /home/user, but nothing inside of /home/user.
  • **: Two asterisks allows you to match files and folders recursively.  Other than that, it is the same as a single asterisk.  So, if you specify /home/**, it will match both the directory /home/user and the files and folders inside of /home/user.
  • ?: A question mark wildcards a single character except forward slash (/).  So for example, if you had a specification of /home/use?, it would match any directories or files in /home starting with use and one other character (like user, used), but not files or folders starting with use and more than one other character like /home/users, /home/usering
  • []: You can use square brackets ([]) to specify a class of characters to match a single character.  For example, [abc] or [a-c] will match a single a, b, or c.
  • [^...]: A caret (^) can be used to say that you do not want to match a class of characters.  For example, [^a] will say do not match the single character a.
  • {}: Curly braces inside of a file or directory specification allow you to match any string in a comma separated list.  For example, {abc, def} will match the string 'abc' or the string 'def'.
  • If you need to match a special character like an asterisk, you can escape it with a backslash (\).  \* will match the asterisk character.
Let's work through a few globs so that we can cement the idea.

  • /usr/bin/ will only match the directory /usr/bin/ and nothing underneath it.  The slash at the end is necessary to specify that you want to match the directory and not a file called bin under /usr/.
  • /usr/bin/apt will just match the file /usr/bin/apt.  If there is a directory called apt in /usr/bin/, it will not be matched because we did not put a slash to tell AppArmor we want to specify the directory.
  • /usr/bin/* will match any files under /usr/bin/.  This glob will not match directories in /usr/bin/.
  • /usr/bin/**/ will match any directories under /usr/bin/.  This glob will not match files in /usr/bin/ because we said that the name must end with a slash which would only happen with a directory.
  • /usr/bin/** will match any files and directories under /usr/bin/.
  • /usr/bin/**[^/] will match all files under /usr/bin/ but explicitly deny any directories because we are saying do not match where the last character is a /.  A slash as the last character would only happen with directories.
Now that we understand how to specify the files and folders we want to apply permissions to, let's talk about the actual permissions we specify for files and folders:

  • r (Read):  Files and folders can be read
  • w (Write): Files and folders can be written to
  • a (Append): This allows files to be written to and added to.  It is a bit limited in that the file has to be open a certain way (O_APPEND) by the program to fall under the append that AppArmor is looking for.  If the program does not append files that way, it will be denied even with the a permission set.
  • l (lower case L, link): This grants the program permission to link to a file.
  • k (lock): This gives the program permission to lock a file.  When combined with w, the program can make an exclusive lock on a file.
  • m (Memory map): This allows a program to perform actions on the memory of another program (memory for a program is typically mapped in /proc/<pid>/maps).
  • x (Execute): This allows a program to execute another file (if it is executable).  This must be combined with additional qualifiers which we talk about in the next subsection.
You can combine these permissions to allow multiple operations on a file or folder.  rw for example would allow reading and writing on a file or folder.

Here are a few examples:
  • /usr/bin/** r : A program can read any file or folder under /usr/bin/.
  • /home/user/* rw: A program can read or write any file in /home/user.

Execution Control

I mentioned in the last subsection that when you want to control execution with the x permission, you have to qualify it with additional permissions specific to execution.  We will run through those now.

  • i (would be used as ix): The spawned executable would inherit the profile that the parent is using.  For example, if program1 protected by the program1 profile launches program2, program2 will inherit the permissions that program1 has based on its profile.
  • p (would be used as px): The spawned executable would need its own profile in order to execute.  For example, if program1 launches program2, program2 would need to have a defined profile to be able to execute.
  • c (would be used as cx): The spawned executable would need a child profile defined in the profile for the parent executable.  This is similar to p, but the profile would be a child profile and not a separate profile.
  • u (would be used as ux): The spawned executable would be launched unconfined.  That means that AppArmor protections would not used for the spawned process.  You should use this only under very controlled and specific instances because if something happens in the execution of the spawned process (like it gets exploited), you will not be protected.
In all of the examples above, environment variables would be scrubbed for the spawned process.  Scrubbing means that certain environment variables are removed from the environment of the spawned process.  The environment variables that are scrubbed have to do with the linking and loading of libraries (see the man page for for more detail on the specific environment variables).  One environment variable you want scrubbed is LD_PRELOAD.  LD_PRELOAD allows other libraries to be loaded before libraries in the standard locations.  So if there was some library loaded via LD_PRELOAD, it would also get loaded for the spawned process which you might not want.  If you want to disable environment variable scrubbing, use a capital letter (I, P, C, U) instead. For example, Ix would mean inherit the parent's profile but do not scrub environment variables.   You can also combine execute permissions to provide a fallback.  So, you could say Iux which means try inherit without environment variable scrubbing but fallback to unconfined if that does not work.  I suggest always using environment variable scrubbing and only using unconfined if absolutely necessary.


Hats allow a program or subprocess to assume a different security context when necessary.  We talked about security contexts in our SELinux discussion.  The idea is similar here.  A program can wear any number of hats (or subprofiles), but it ends there.  A hat cannot have its own hats. Hats are not used too often, but you might see them, so I wanted to mention them.  They look like profiles but start with a caret (^).  So for example, ^myhat {...} would define the hat myhat.  Inside of the curly braces, you would define your rules as normal.  A program has to be aware of hats so that it can change between them.  The best example that I know of with hats is apache using mod_apparmor.

Audit (Complain) Mode

We have talked about an audit mode in both SELinux and AppLocker.  A similar mode called complain exists in AppArmor.  There are a few ways of invoking it, but I like to run aa-complain against the profile I want to put in complain mode.  The command would look like this:

sudo aa-complain /path/to/profile

aa-complain must be run as root.  To turn off complain mode (and turn on enforcing mode), simply run aa-enforce:

sudo aa-enforce /path/to/profile

aa-enforce must also be run as root.

You can also add the phrase flags=(complain) to the profile name.  For example, a profile called /usr/bin/app {...} would become /usr/bin/app flags=(complain) {..}

What Should I Make AppArmor Profiles For?

AppArmor profiles (or SELinux Security Contexts) are good for applications that are at a higher risk for coming into contact with untrusted and potentially malicious input such as web servers and web browsers.

After you make a new profile, it should be placed in /etc/apparmor.d/.  Typically, the name of the profile is the path to the executable with dots substituted for the forward slashes.  For example, the profile for /usr/bin/firefox would like in /etc/apparmor.d/usr.bin.firefox.

You can also take a look at the various includes since they live in their respective folders in /etc/apparmor.d/ (/etc/apparmor.d/tunables/, /etc/apparmor.d/abstractions/, and /etc/apparmor.d/program-chunks).

A Small Example

The last thing I wanted to discuss in this post is a small example that should summarize everything we have talked about so far.  I wrote a very small script that reads two files: /etc/passwd and ~/.bashrc.  I want to make it so that the script is confined to the home directory of the user running it.   Here is the script:

#!/usr/bin/env python

import os

    with open('/etc/passwd', 'r'):
         print('I can open /etc/passwd.')
    print('I tried to open /etc/passwd but failed.')

    # os.expanduser('~') gets the current user's home directory in Linux
    with open(os.expanduser('~') + '/.bashrc'):
        print("I can open user's bashrc.")
    print("I tried to open user's bashrc and failed.")

Essentially, the script tries to open /etc/passwd, and if it fails, it will print a message saying it could not open the file.  The script then does the same with the user's .bashrc.  Without any protections, both tries should work.  Let's look at the standard permissions on both files:

So, /etc/passwd is world readable, so our user should be able to open it.  As we expected, our .bashrc is owned by our user, and we have permissions to read it.  With that, let's try running our script (I put it in /usr/bin/badscript):

This is not what we want, so let's write a profile:

#include <tunables/global>

/usr/bin/badscript {
 #include <abstractions/base>

 @{HOME}/** r,
 # Since this is a python script, python will be
 # executing it, so we need to allow the
 # executables and libraries that python needs
 /usr/lib/python*/** r,
 /usr/local/lib/python*/** r,
 /etc/python*/* r,
 /usr/bin/python* ix,

Let's go through the profile. tunables/global includes tunables/home which gives us the @{HOME} variable.  We want the script to be able to read files in /home, so we will let the script access files and folders (**) under @{HOME} (/home/ and /root/).  Since the script is a Python script, we need to let the things Python needs to run.  Those are the next four lines after the comments.  You might be wondering how I knew to write permissions for those folders and files.  In order to write the profile, I put it in complain mode and looked at what the executable tried to access.  We will go through this in a minute.

When you want to use the profile, place it in /etc/apparmor.d/.  I called it usr.bin.badscript.   You can call it whatever you want.  We will restart apparmor to let it read our new profile:
sudo systemctl restart apparmor

Now, let's try running badscript:

Oh no!  We wanted to confine the script, not to stop it from running.  Let's put the profile in complain mode and see if we can figure out what happened:

sudo aa-complain /etc/apparmor.d/usr.bin.badscript

The script ran, so that is good.  To figure out what we need to allow to make the script work, we need to look at the kernel log in /var/log/kern.log.  You will want to look for lines that say ALLOWED, like this one:
localhost kernel: [425293.157251] audit: type=1400 apparmor="ALLOWED"
operation="open" profile="/usr/bin/badscript" name="/usr/bin/badscript" pid=9787
comm="python" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0

I put some line breaks in to make it easier to read.  The parts that help us understand what should have been denied are on the last two lines (everything starting with name=).  We can see that a read was attempted (requested_mask = "r") and was denied (denied_mask = "r") on the file /usr/bin/badscript (name="/usr/bin/badscript").  So it looks like we to add /usr/bin/badscript to the profile with an r permission.  This makes sense because the Python executable is executing the script, so it needs to be able to read it.  Let's add that.  The profile now looks like this:

#include <tunables/global>

/usr/bin/badscript {
 #include <abstractions/base>

 @{HOME}/** r,
 # Since this is a python script, python will be
 # executing it, so we need to allow the
 # executables and libraries that python needs
 /usr/lib/python*/** r,
 /usr/local/lib/python*/** r,
 /etc/python*/* r,
 /usr/bin/python* ix,
 /usr/bin/badscript r,

Now, let's put the profile back in enforcing mode, and see what happens:

sudo aa-enforce /etc/apparmor.d/usr.bin.badscript

Success!  Let's look at /var/log/kern.log to make sure the profile did what want:

Mar 24 18:41:16 localhost kernel: [426015.506783] audit: type=1400: apparmor="DENIED"
operation="open" profile="/usr/bin/badscript" name="/etc/passwd"
pid=9853 comm="python" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0

This was the only new log that was created, and as we can see, the /usr/bin/badscript profile tried to read (requested_mask = "r") /etc/passwd (name="/etc/passwd") and was denied (apparmor="DENIED").

When debugging your own profile, it is important to think about whether you want to accept the risk of writing permissions to let the program do whatever it is it wants to do.  This depends on your risk tolerance and how much you need the program to what it wants.

Conclusions and Final Thoughts

Over the past few posts, we have talked about a few defense measures that implement various access controls and protections.  I hope this series has been useful for you.  None of the measures we have talked about are going to protect you from everything.  As I have said a few times before, when correctly configured, these tools can help you build a layered network defense.  Thanks for reading, and if you have any questions, please let me know!


No comments:

Post a Comment