Home Wiki > SDB:AppArmor geeks
Sign up | Login

SDB:AppArmor geeks

tagline: From openSUSE

A Geeks Introduction to AppArmor

Introduction

This guide is intended to walk you through trying AppArmor to better understand AppArmor internals. Users who simply wish to use distribution-packaged AppArmor should refer to the distribution-supplied documents.


AppArmor

AppArmor is a Linux Security Module implementation of name-based access controls. AppArmor confines individual programs to a set of listed files and posix 1003.1e draft capabilities.

One of AppArmor's main goals is to be simple to understand and simple to use. One of the features of AppArmor introduced to help users write and maintain policy is a per-profile learning mode. Profiles may be in one of two states: learning mode (also known as "complain mode") and enforcement mode (also known as "confined").

Learning mode is configured per-profile; processes running in a profile can easily be toggled between learning mode and enforcement mode. Only unconfined root processes are allowed to toggle learning mode.

Kernel patches

The AppArmor kernel patches are provided in a format convenient for use with quilt.[1] The patches apply against recent kernel.org git kernels; git snapshots or released kernels may work with minor fuzz. If there is major fuzz or patches do not apply, consider trying older or newer kernels.

After downloading and unpacking the kernel sources, apply all the AppArmor patches; an example of using quilt to apply AppArmor patches:

 tar -zxvf apparmor.tar.gz
 ln -sf apparmor ~/path/to/kernel/sources/patches
 cd ~/path/to/kernel/sources
 quilt push -a


Kernel configuration

Configure your kernel; CONFIG_SECURITY_APPARMOR must be set to 'y' or 'm'. 'm' receives more regular testing. Disable (or set to Module) CONFIG_SECURITY_CAPABILITIES and CONFIG_SECURITY_SELINUX.

(If you wish to compile SELinux or Capabilities into your kernel, then you must add selinux=0 capability.disable=1 to your kernel command line (grub, lilo, yaboot, etc.).)

It is not sufficient to put SELinux into permissive mode. AppArmor must be the only LSM loaded.


Securityfs

AppArmor uses the kernel standard securityfs mechanism to load policy and report information to system administrators. The usual mountpoint for securityfs is /sys/kernel/security.

Mount it with:

 mount securityfs -t securityfs /sys/kernel/security

Once securityfs has been mounted and AppArmor loaded, /sys/kernel/security/apparmor/profile will list the profiles loaded into the kernel, as well as mark if the profiles are in enforcement mode or in learning mode:

 $ sudo cat /sys/kernel/security/apparmor/profiles
 /usr/bin/opera (complain)
 /usr/lib/firefox/firefox.sh (complain)
 /sbin/lspci (enforce)
 ...

/sys/kernel/security/apparmor/control/ control seldom-used features of AppArmor:

 audit    if '1', audit all actions by confined processes
 complain if '1', allow all actions by confined processes, report
          accesses not granted by policy
 debug    if '1', emit copius debugging
 logsyscall  if '1', use audit framework's syscall debugging, if audit
          has been instructed to create per-task contexts.[2]


Policy parser

You will need the apparmor_parser [3] to load AppArmor policy into the kernel. Simply run 'make' in the top-level directory to build apparmor_parser.

Once the parser is built and the AppArmor module loaded, you may use the parser to compile and install policy:

 echo "/tmp/ls { /tmp/ls rm, }" | ./apparmor_parser

Once a profile for a program has been loaded into the kernel, you must use the --replace option:

 echo "/tmp/ls { /tmp/ls rm, }" | ./apparmor_parser --replace

--replace may be used even if no profile by that name exists.


Anatomy of a profile

AppArmor profiles are a simple declaritive language; fully described in the apparmor.d(5) manpage. Profiles are stored by convention in /etc/apparmor.d/. The AppArmor parser supports a simple cpp-style

  1. include mechanism to allow sharing pieces of policy.

A simple profile looks like this:

/tmp/ls flags=(complain) {
 # executable needs 'r' and mmap PROT_EXEC 'm'
 /tmp/ls rm,
 /lib/ld-2.5.so rmix,
 /etc/ld.so.cache rm,
 /lib/lib*.so* rm,

 /dev/pts/*    w,

 /proc/meminfo r,
 /var/run/nscd/socket w,
 /var/run/nscd/passwd r,
 /var/run/nscd/group  r,

 /tmp/ r,
}

The first "/tmp/ls" is the name of the profile. This profile will be automatically used whenever an unconfined process executes /tmp/ls.

The "flags=(complain)" instruct the AppArmor module to allow all operations and log any events that would have been denied, had the profile been loaded in enforcement.

Individual profiles are set into complain or enforcement mode; this helps users to incrementally deploy AppArmor in production environments. To place a profile in complain mode, add 'flags=(complain)' as shown above, and reload the profile with apparmor_parser --replace (or the initscript's restart option). To place all profiles into complain mode, you may also use the /sys/kernel/security/apparmor/control/complain file as described above.

The AppArmor distribution includes two small tools, aa-enforce and aa-complain, that will set profiles into enforce or complain mode:

 # enforce firefox
 Setting /usr/lib/firefox/firefox.sh to enforce mode.

Inside the body of the profile are any number of rules; the rules use a shell-inspired globbing syntax:

 ?         - any single character, except /
 *         - any number of characters excepting /
 **        - any number of characters including /
 [abc]     - one of a, b, or c
 [a-c]     - one of a, b, or c
 {ab,cd}   - alternation -- 'ab' or 'cd'

Rules ending with / will only match directories. Rules ending with ? or * won't match directories. Rules ending with ** will match directories. Some examples:

 /tmp/*   - all files directly in /tmp
 /tmp/*/  - all directories directly in /tmp
 /tmp/**  - all files and directories underneath /tmp
 /tmp/**/ - all directories underneath /tmp

None of these will match /tmp/.

Each rule also specifies permissions:

      r    - read
      w    - write
      ux   - unconstrained execute
      Ux   - unconstrained execute -- scrub the environment
      px   - discrete profile execute
      Px   - discrete profile execute -- scrub the environment
      ix   - inherit execute
      m    - allow PROT_EXEC with mmap(2) calls
      l    - link

AppArmor does not require execute access to allow directory traversal.

AppArmor does not require 'w' access on a directory to create or rename files inside the directory. Instead, 'w' permission is required on the specific files, directories, pipes, etc., that a confined process attempts to create or rename.

AppArmor requires 'r' access on a directory for a confined process to call getdents().

Execute access requires a modifier: 'P' or 'p' instructs AppArmor to transition to a different profile when a process performs on exec() with the named program. 'U' or 'u' instructs AppArmor to unconfine the process when it performs an exec() with the named program. 'i' instructs AppArmor to keep the current profile, even if a profile exists for the named program.

The 'Px' and 'Ux' modes will use the kernel's unsafe exec facility to instruct glibc to clean the environment. ('Px', 'px', 'Ux', 'ux', change the privileges available to the process, much like setuid or setgid. The privileges may be different from the calling process, whether more or less restrictive. Cleaning the environment helps protect against e.g. LD_PRELOAD abuse.)

'm' is required for libraries and executables that must be mapped PROT_EXEC. This also helps prevent e.g. LD_PRELOAD abuse when 'Px' cannot be used.

To create a hardlink, the profile needs 'l' access on the name of the new link. Also, the rights on the new link must be a subset of the rights on the target (excepting 'l' itself). Some examples:

 /foo lr,
 /bar r,    link("bar", "foo") will succeed
 
 /foo l,
 /bar r,    link("bar", "foo") will succeed, empty rights are subset of 'r'
 
 /foo r,
 /foo w,
 /foo l,
 /bar rw,   link("bar", "foo") will succeed, 'rw' on both

 /fo* r,
 /f*o w,
 /foo l,
 /bar r,   link("bar", "foo") will fail -- /foo has rw, /bar only r

Any link that includes execute modifiers (PpUui) on the new link name must exactly match the privileges on the target of the link. (Granting link permission to programs isn't a common need.)

AppArmor also mediates the use of POSIX 1003.1e draft capabilities; capabilities that a process is allowed to use are simply listed in the profile with "capability <lowercase name from capabilities(7) with CAP_ stripped off" -- e.g., "capability sys_admin," or "capability audit_control,":

# vim:syntax=apparmor
# Last Modified: Wed Mar 21 13:27:16 2007
#include <tunables/global>

/sbin/lspci {
 #include <abstractions/base>
 #include <abstractions/consoles>
 
 capability sys_admin,
 
 /sbin/lspci mr, 
 /sys/bus/pci/ r,
 /sys/bus/pci/devices/ r,
 /sys/devices/** r,
 /usr/share/pci.ids r,
} 

This profile uses predefined include files which are part of the apparmor-profiles package.

Logging

AppArmor uses the kernel standard audit facility to report policy violations. When a profile is in complain mode, the log messages look like this:

 type=APPARMOR msg=audit(1174506429.573:1789): PERMITTING r access to
 /home/sarnold/ (ls(16504) profile /tmp/ls active /tmp/ls)
      

When a profile is in enforcement mode, the log messages look like this:

 type=APPARMOR msg=audit(1174508205.298:1791): REJECTING r access to
 /bin/ (ls(16552) profile /tmp/ls active /tmp/ls)
      

These log messages are sent to the kernel auditing facility; if auditd is not running, the kernel will forward these messages to printk for collection by klogd. Auditd must be configured with --with-apparmor to enable the #defines to translate AppArmor's message integers to strings.

AppArmor also logs some important events in the process lifecycle, such as when processes in learning mode fork and change domain via exec. These other events, while not strictly related to permissions requested by the process, help the 'genprof' profile generation tool reconstruct when specific accesses are required by processes -- this allows the tool to make more relevant and meaningful policy suggestions.


Generating profiles by hand

While the majority of our users are expected to generate profiles with the help of our profile tools, it is possible to write policy by hand. This final section gives a very quick walkthrough generating a simple profile for firefox.

Since the kernel resolves symlinks to their 'final destinations' before presenting AppArmor with policy questions, we first must see if /usr/bin/firefox is a symlink or the shell script that starts firefox; on our system, it is a symlink:

 # ls -l /usr/bin/firefox
 lrwxrwxrwx 1 root root 25 Mar 21 13:36 /usr/bin/firefox -> ../lib/firefox/firefox.sh

So we will start a profile for /usr/lib/firefox/firefox.sh. This shell script will execute firefox-bin, as we will see later; when it does so, we will tell AppArmor to inherit this profile. Thus, firefox-bin will be executing under the profile for /usr/lib/firefox/firefox.sh.

To get started, we can make some assumptions about the privileges that firefox will need (both as a shell script and as a fairly complex GUI application):

# cat /etc/apparmor.d/usr.lib.firefox.firefox.sh
/usr/lib/firefox/firefox.sh flags=(complain) {
 /usr/lib/firefox/firefox.sh r,
 /bin/bash rmix,
 /lib/ld-2.5.so rmix,
 /etc/ld.so.cache rm,
 /lib/lib*.so* rm,
 /usr/lib/lib*.so* rm,
}
# apparmor_parser --reload < /etc/apparmor.d/usr.lib.firefox.firefox.sh
Replacement succeeded for "/usr/lib/firefox/firefox.sh".

Before starting firefox, start a tail -F /var/log/audit/audit.log (or /var/log/messages, or wherever your kernel messages are being sent).

In another terminal, start firefox. tail will show a few hundred PERMITTING rules:

 type=APPARMOR msg=audit(1174512269.026:1804): PERMITTING rw access to
   /dev/tty (firefox(16950) profile /usr/lib/firefox/firefox.sh active
   /usr/lib/firefox/firefox.sh)

 type=APPARMOR msg=audit(1174512269.026:1805): PERMITTING r access
   to /usr/share/locale/locale.alias (firefox(16950) profile
   /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh)

 type=APPARMOR msg=audit(1174512269.026:1806): PERMITTING r access to
   /usr/lib/locale/en_US.utf8/LC_IDENTIFICATION (firefox(16950) profile
   /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh)

and so forth.

Because we want this profile to be fairly simple, we'll be fairly permissive; add a few more rules to the profile and reload:

 /dev/tty rw,
 /usr/share/locale/** r,
 /usr/lib/locale/** r,

Now re-run firefox. (There is no need to handle _all_ log entries at once. In complain mode, AppArmor will only report accesses that aren't in the profile. This makes it fairly easy to add a few rules and re-run the application to determine what privileges are still necessary.)

Re-running we get a few more messages:

 type=APPARMOR msg=audit(1174512791.236:5356): PERMITTING r access
   to /usr/lib/gconv/gconv-modules.cache (firefox(17031) profile
   /usr/lib/firefox/firefox.sh active /usr/lib/firefox/firefox.sh)

 type=APPARMOR msg=audit(1174512791.236:5357): PERMITTING r access to
   /proc/meminfo (firefox(17031) profile /usr/lib/firefox/firefox.sh
   active /usr/lib/firefox/firefox.sh)

 type=APPARMOR msg=audit(1174512791.240:5358): PERMITTING x access to
   /bin/basename (firefox(17032) profile /usr/lib/firefox/firefox.sh
   active /usr/lib/firefox/firefox.sh)

 type=APPARMOR msg=audit(1174512791.240:5359): LOGPROF-HINT
   changing_profile pid=17032

 type=APPARMOR msg=audit(1174512791.240:5360): PERMITTING r access to
   /bin/basename (firefox(17032) profile null-complain-profile active
   null-complain-profile)

 ...

  type=APPARMOR msg=audit(1174512791.240:5364): PERMITTING mr access to
    /bin/basename (basename(17032) profile null-complain-profile active
    null-complain-profile)


So now, we add a few more rules:

 /usr/lib/gconv/** r,
 /proc/meminfo r, 
 /bin/basename rmix,

We selected 'rmix' for /bin/basename -- like most small shell utilities, basename should not have a profile for itself. There's nothing wrong with giving basename a profile, but the value of such a profile would be very limited -- the profile would need read access to the whole filesystem for shell scripts to function reliably. When basename simply inherits from another profile, then it has no more and no fewer privileges than the calling program -- which is often a fine tradeoff.

The loader will need 'r' and 'm' access to execute basename, and we use 'ix' to specify no domain transition. The kernel logs only reported 'r', 'm' and 'x' access; we have to make a decision among 'p', 'i', and 'u' qualifiers for 'x' ourselves. (Again, the standard user tools would prompt users for this decision and give consequences of decisions -- see your distribution documentation for details on the user tools.)

We continue in this fashion, iteratively adding / changing rules as needed by the logs; some of the logs report attribute modifications, such as:

 type=APPARMOR msg=audit(1174519157.851:10357): PERMITTING
   attribute (mode,ctime,) change to /home/sarnold/.gnome2_private/
   (firefox-bin(17338) profile /usr/lib/firefox/firefox.sh active
   /usr/lib/firefox/firefox.sh)
 

These need to be represented in the profile with simple 'w' access.

 /home/*/.gnome2_private/ w,

After nine iterations, the profile looks like this -- we've inserted blank lines between each iteration:


 /usr/lib/firefox/firefox.sh flags=(complain) {
   /usr/lib/firefox/firefox.sh r,
   /bin/bash rmix,
   /lib/ld-2.5.so rmix,
   /etc/ld.so.cache rm,
   /lib/lib*.so* rm,
   /usr/lib/lib*.so* rm,
 
   /dev/tty rw,
   /usr/share/locale/** r,
   /usr/lib/locale/** r,
 
   /usr/lib/gconv/** r,
   /proc/meminfo r,
   /bin/basename rmix,
 
   /usr/bin/file rmix,
   /etc/magic r,
   /usr/share/misc/magic.mgc r, 
   /bin/gawk rmix,
   /usr/lib/firefox/firefox-bin rmix,
 
   /usr/lib/firefox/lib*so rm,
   /opt/gnome/lib/lib*so* rm,
   /usr/share/X11/locale/* r,
   /var/run/nscd/socket w,
   /var/run/nscd/passwd r,
 
   /usr/share/X11/locale/** r,
   /home/*/.Xauthority r,
   /usr/lib/gconv/*so m,
   /home/*/.mozilla/** rw,
   /etc/resolv.conf r,
   /usr/lib/firefox/**.so rm,
   /usr/lib/firefox/** r,
 
   /etc/opt/gnome/** r,
   /var/run/dbus/system_bus_socket w,
   /etc/localtime r,
   /opt/gnome/lib/**.so rm,
   /var/cache/libx11/compose/* r,
   /tmp/orbit-*/ w,
   /dev/urandom r,
   /tmp/ r,
   /dev/null rw,
   /opt/gnome/lib/GConf/2/gconfd-2 rmix,
 
   /dev/log w,
   /tmp/orbit-*/* w,
   /tmp/gconfd-*/ r,
   /tmp/gconfd-*/** rwl,
   /home/*/.gconf/ r,
   /home/*/.gconf/* rw,
   /etc/fonts/** r,
   /var/cache/fontconfig/* r,
   /home/*/.fontconfig/** r,
   /usr/share/ghostscript/fonts/** r,
   /etc/passwd r,
   /var/tmp/ r,
   /bin/netstat rmix,

   /home/*/.gnome2_private/ w,
   /home/*/.gconfd/* rw,
   /proc/net/ r,
   /proc/net/* r,
   /usr/share/fonts/** r,
   /usr/lib/browser-plugins/ r,
   /usr/lib/browser-plugins/** rm,
 }

Sorting the entries in the profile can help show areas that can be collapsed with even more generic rules:

 /usr/lib/firefox/firefox.sh flags=(complain) {
   /bin/basename rmix,
   /bin/bash rmix,
   /bin/gawk rmix,
   /bin/netstat rmix,
   /dev/log w,
   /dev/null rw,
   /dev/tty rw,
   /dev/urandom r,
   /etc/fonts/** r,
   /etc/ld.so.cache rm,
   /etc/localtime r,
   /etc/magic r,
   /etc/opt/gnome/** r,
   /etc/passwd r,
   /etc/resolv.conf r,
   /home/*/.fontconfig/** r,
   /home/*/.gconfd/* rw,
   /home/*/.gconf/ r,
   /home/*/.gconf/* rw,
   /home/*/.gnome2_private/ w,
   /home/*/.mozilla/** rw,
   /home/*/.Xauthority r,
   /lib/ld-2.5.so rmix,
   /lib/lib*.so* rm,
   /opt/gnome/lib/GConf/2/gconfd-2 rmix,
   /opt/gnome/lib/lib*so* rm,
   /opt/gnome/lib/**.so rm,
   /proc/meminfo r,
   /proc/net/ r,
   /proc/net/* r,
   /tmp/gconfd-*/ r,
   /tmp/gconfd-*/** rwl,
   /tmp/orbit-*/ w,
   /tmp/orbit-*/* w,
   /tmp/ r,
   /usr/bin/file rmix,
   /usr/lib/browser-plugins/ r,
   /usr/lib/browser-plugins/** rm,
   /usr/lib/firefox/firefox-bin rmix,
   /usr/lib/firefox/firefox.sh r,
   /usr/lib/firefox/lib*so rm,
   /usr/lib/firefox/** r,
   /usr/lib/firefox/**.so rm,
   /usr/lib/gconv/** r,
   /usr/lib/gconv/*so m,
   /usr/lib/lib*.so* rm,
   /usr/lib/locale/** r,
   /usr/share/fonts/** r,
   /usr/share/ghostscript/fonts/** r,
   /usr/share/locale/** r,
   /usr/share/misc/magic.mgc r,
   /usr/share/X11/locale/* r,
   /usr/share/X11/locale/** r,
   /var/cache/fontconfig/* r,
   /var/cache/libx11/compose/* r,
   /var/run/dbus/system_bus_socket w,
   /var/run/nscd/passwd r,
   /var/run/nscd/socket w,
   /var/tmp/ r,
 }

After collapsing a few rules into more generic and open:

 /usr/lib/firefox/firefox.sh flags=(complain) {
   /bin/basename rmix,
   /bin/bash rmix,
   /bin/gawk rmix,
   /bin/netstat rmix,
   /dev/log w,
   /dev/null rw,
   /dev/tty rw,
   /dev/urandom r,
   /etc/fonts/** r,
   /etc/ld.so.cache rm,
   /etc/localtime r,
   /etc/magic r,
   /etc/opt/gnome/** r,
   /etc/passwd r,
   /etc/resolv.conf r,
   /home/*/.fontconfig/** r,
   /home/*/.gconfd/* rw,
   /home/*/.gconf/ r,
   /home/*/.gconf/* rw,
   /home/*/.gnome2_private/ w,
   /home/*/.mozilla/** rw,
   /home/*/.Xauthority r,
   /lib/ld-2.5.so rmix,
   /lib/lib*.so* rm,
   /opt/gnome/lib/GConf/2/gconfd-2 rmix,
   /opt/gnome/lib/**.so* rm,
   /proc/meminfo r,
   /proc/net/ r,
   /proc/net/* r,
   /tmp/gconfd-*/ r,
   /tmp/gconfd-*/** rwl,
   /tmp/orbit-*/ w,
   /tmp/orbit-*/* w,
   /tmp/ r,
   /usr/bin/file rmix,
   /usr/lib/browser-plugins/ r,
   /usr/lib/browser-plugins/** rm,
   /usr/lib/firefox/firefox-bin rmix,
   /usr/lib/firefox/firefox.sh r,
   /usr/lib/firefox/** r,
   /usr/lib/firefox/**.so rm,
   /usr/lib/gconv/** r,
   /usr/lib/gconv/*so m,
   /usr/lib/lib*.so* rm,
   /usr/lib/locale/** r,
   /usr/share/** r, 
   /var/cache/fontconfig/* r,
   /var/cache/libx11/compose/* r,
   /var/run/dbus/system_bus_socket w,
   /var/run/nscd/passwd r,
   /var/run/nscd/socket w,
   /var/tmp/ r,
 } 
   
   
   

[1] http://savannah.nongnu.org/projects/quilt [2] auditctl(8) option -e; perhaps your distro also supports

   /etc/sysconfig/auditd or similar for persistent configuration

[3]