AppArmorStacking

From AppArmor
Jump to: navigation, search

WARNING: Draft WIP

Required Versions

  • AppArmor Kernel module - 3.5-beta1 (Ubuntu 16.04)
    • Limitations
      • Stacking is limited to crossing a single policy namespace boundry
      • The secruity/apparmor/policy interface is not virtualized
      • The /sys/module/apparmor/parameters/* interfaces are not virtualized
      • policy namespaces can not be transitioned to automatically when other system namespaces are entered
      • system namespacing may still affect policy in ways not desired in a stack, such as the disconnected path applying to all parts of the stack
      • The 3.5 kernel has a bug that breaks ix /foo -> &bar, use px /foo -> @{profile_name}//&bar, instead
  • AppArmor Userspace - 2.11-beta (2.10.95 shipped in Ubuntu 16.04)

Related Documentation

Introduction

AppArmor now supports (see #Required_Versions) the stacking of two or more profiles to create a confinement that is the intersection of all profiles in the stack. Stacking is done dynamically (at run time) and provides several abilities to the policy author: it can be used to ensure that confinement never becomes more permissive, to reduce the permissions of a generic profile on a specific task, and when combined with policy namespaces to provide both system level and container and user level policy.

How a stack is expressed

In AppArmor a stack is expressed by concatenating two or more profile names together with the character sequence //& between each profile.

That is if profile A and profile B are stacked together it is expressed as A//&B. For the larger stack of profile A, profile B and profile C the stack is expressed by A//&B//&C.

A stack can also be express with the current confinement as an implicit component. This is done by specifying the stack starting with an & character. That is if a process is confined by profile A then &B expresses the stack A//&B, and &B//&C expresses the stack A//&B//&C when applied to that process.

The order of the profiles in the stack is unimportant A//&B is equivalent to B//&A. AppArmor will always create a unique ordering and will always report in that given ordering but that ordering may be different between versions, and user applications and policy can specify the stack in any order that is convenient.

While the stacking syntax may not show up in policy for many use cases it is important to know the syntax because it will show up under introspection and in audit logs.

Application behavior when under stacked confinement

The majority of AppArmor rules can be evaluated as a simple intersection of profile permissions. That is to say all profiles in the stack must allow the requested permission(s) or the request will be denied. However when a permission request is allowed some rules have effects beyond granting the permisson.

Resource rules

Resource rules like rlimits will grant permissions based on the lowest resource bounds allowed, and when setting the tasks rlimits will set the limits to the smallest resource bound.

Exec rules

Rules that control exec transitions have effects on confinement beyond allowing an exec. They also control which profile(s) are confining the process after the exec is allowed. When a process is confined by a stack exec transitions are evaluated and applied on a per profile basis and then the result is evaluated into the new confinement. If any duplicate confinement is encountered then the duplication is eliminated, that is the intersect of profile A with another profile A (A//&A) reduces to just profile A.

Examples

Given a process that is confined by profile A and profile B (A//&B) defined by the transitions in the stub profiles for an exec to /bin/example the transitions and resulting confinement are as follows

Eg. 1

  profile A {
    /bin/example ix,
    ...
  }
  profile B {
    /bin/example px -> C,
    ...
  } 

profile A makes no transition and stays in profile A, and profile B transitions to profile C, resulting in a final confinement of profile A and profile C (A//&C).

Eg. 2

  profile A {
    /bin/example px -> C,
    ...
  }
  profile B {
    /bin/example px -> D,
    ...
  }

profile A transitions to profile C, and profile B transitions to profile D, resulting in a final confinement of profile C and profile D (C//&D).

Eg. 3

  profile A {
    /bin/example px -> B,
    …
  }
  profile B {
    /bin/example px -> C,
    …
  }

profile A transitions to profile B, and the original profile B transitions to profile C, resulting in a final confinement of profile B and profile C (B//&C).

Eg. 4

profile A {
   /bin/example px -> C,
   …
}
profile B {
   /bin/example px -> C,
   …
}

profile A transitions to profile C, and profile B transitions to profile C, resulting in a confinement of profile C and profile C which can be reduced to a final confinement of profile C.


Environment variable scrubbing

Environment variable scrubbing for a stacked exec transition is performed if any of the transitions in the stack specify a transition. This can result in a tighter confinement than some of the profiles in the stack specify but is required to meet the tighter confinement specified by at least one of the transitions.

Expressing stacking in Policy

AppArmor policy has been extended so that in policy profile transitions can express profile stacks.

A stack can be expressed through a named transition using the //& stack separator as described above in How a stack is expressed.

 Eg. specifying an exec transition to a stack
  /bin/** px -> one//&two,

or using the leading permission syntax

  px /bin/** -> one//&two,

The above example fully transitions to the stack one//&two without regard to the current profile.

To specify a stack relative to the current profile the @{profile_name} variable can be used

  Eg. specifying a stack including the current profile
    px /bin/** -> @{profile_name}//&one//&two,
 

In addition to the above change named transitions have been extended to support a stack relative to the profile transition.

 
    /bin/** px -> &two,
 
Will compute the profile transition as per
 
    /bin/** px,
 
and then stack the profile two on top of that transition.

This allows for stacks to be based on the executable and profile transitions instead of just the current profile.


The relative stack syntax also supports the profile transition with fall back, that is pix and pux, where if a target profile transition is not found either the current profile or unconfined is used as a fallback.

 Eg.
 
    /bin/** pix -> &two,
 


In addition to the above change the ix' transition can be used to express stacking relative to the current profile*.

 
    /bin/** ix -> &two,
 
is equivalent to
 
    /bin/** px -> @{profile_name}//&two,
 
  • Note: The 3.5 kernel has a bug that breaks ix /foo -> &bar, use px /foo -> @{profile_name}//&bar, instead.

Evaluating stacking transitions when a stack is applied

Stacking transitions can at first glance appear to be fairly complicated when a task is already confined by a stack, however finding the final transition is fairly straight forward as long as the stack is evaluated per profile.

Eg. A task is confined by the stack A//&B. With the profiles having the transitions

profile A {
  px /bin/** -> C//&D,
}
profile B {
  px /bin/** -> &C,
}

when /bin/foo is executed, the permission for both A and B are checked.

A allows the exec and transitions to C//&D
B allows the exec and transitions to /bin/foo//&C.

The two stacks are then combined to final the final stack of

/bin/foo//&C//&D

Application directed Stacking

AppArmor provides an API for processes to change their own confinement. This API is controlled in policy by change_profile rules (see below), but is unrestricted for tasks that are unconfined.

Stacking API


int aa_stack_profile(const char *profile);
int aa_stack_onexec(const char *profile);

The aa_stack_profile() is analogous to the aa_change_profile() function, if successful it provides an immediate change of confinement by stacking the requested profile(s) on top of the tasks current confinement.

 Eg. Given a task confined by the profile one
     if (aa_stack_profile("two") < 0)
        exit(1);  /* fail */
     /* now confined by one//&two */
  

The aa_stack_profile() function will accept a stacked profile label as an argument resulting in an even deeper stack.

 Eg. Given a task confined by the profile one
     if (aa_stack_profile("two//&three") < 0)
        exit(1);  /* fail */
     /* now confined by one//&two//&three */
  

The profiles specified in the stack must exist and be loaded into the kernel or the stack function will fail. See aa_stack_profile(3) for other failures.


The aa_stack_onexec() api function is analogous to the aa_change_onexec() function in that it is applied at exec time. The specified profile if allowed will override rule based profile transitions and like aa_stack_profile() will accept stacked profile labels as an argument. Like aa_stack_profile() aa_stack_profile() can fail both at the api call and at exec time depending on the change_profile rules in the profile and the executable called.

Extended change_profile API


int aa_change_profile(const char *profile);
int aa_change_onexec(const char *profile);

The aa_change_profile() and aa_change_onexec() functions have been extended to support stacking. The argument can now specify a stack of profiles to switch to.

 Eg. Give a task confined by the profile one
       if (aa_change_profile("two//&three") < 0)
          exit(1);  /* fail */
       /* now confined by two//&three */
     Notice that profile one has been dropped because aa_change_profile() changes the current confinement it does not add to it.
  

Introspection of confinement


int aa_getcon(char **label, char **mode);
int aa_getpeercon_raw(int fd, char *buf, int *len, char **mode);

The standard introspection APIs have been extended to return a stacked label string when present. If the mode will be mixed if the profiles in the stack have different modes.

aa_stack_profile vs. aa_change_profile

The aa_stack_profile() function can be emulated by doing

 
 aa_get_con(old);
 strcat(new, "//&");
 strcat(new, old);
 aa_change_profile(new);
 

The same thing can be done to recreate aa_stack_onexec() with aa_change_onexec(). However the aa_stack_ versions are more efficient as they do not need to introspect the kernel nor do they have to build the stacked transition label in userspace.

change_profile rules

Like change_profile() and change_onexec() stacking uses change_profile rules to determine if a stack is allowed. To do this the change_profile rule has been extended to allow specifying stacking permissions.

 # Allow stacking profile A
 change_profile -> &A,

 # Allow stacking the stack A//&B
 change_profile -> &A//&B,


To limit a stack to exec time the rule becomes

 # Allow stacking the profile A at exec of /bin/**
 change_profile /bin/** -> &A,

 # Allow stacking the stack A//&B at exec of /bin/**
 change_profile /bin/** -> &A//&B,

It is important to note that the stack request of stack_onexec() is applied against the current profile confinement and at this time their is no way to specify the onexec stack should take place against a computed profile transition as the policy rule using px -> & allows.


In addition to the relative stack specification, the change_profile rule also supports specifying the absolute stack

 change_profile -> A//&B,

 change_profile /bin/foo -> A//&B,


Stacking does not require that a change_profile with a stack specifier (leading &) be used. Just as

 A {
   change_profile -> A//&B,
 }

allows aa_change_profile("A//&B") to transition to the stack A//&B, it also allows aa_stack_profile("B") because the end result of the stack A//&B is the same as if aa_change_profile("A//&B") was called.

secure exec

change_onexec and stack_onexec by default use safe exec which scrubs several dangerous variables from the environment. It is possible to specify that an unsafe exec needs to be done by using the unsafe keyword in change_profile rules.

 change_profile unsafe /bin/foo -> bar,

Note: that the unsafe keyword requires the exec portion (/bin/foo in the example above) of the change_profile rule to be present.

stacks and sets of change_profile rules

The above rules allow fully specifying a stack however stacking is also allowed if the set of profiles within a stack are individually allowed. That is if profile contains.

 change_profile -> A,
 change_profile -> B,

then

 aa_change_profile("A");
 aa_change_profile("B");
 aa_change_profile("A//&B");

are all allowed, because A//&B is a subset of profile A and of profile B.

At this time the transition is only allowed if the full set of profiles in the stack is allowed. That is to change to the stack A//&B rules allowing the change to both profile A, and profile B are required. Even though the stack A//&B is strictly a subset of the permissions in profile A, and is also a subset of the permissions in profile B.

At this time it is recommended to use tight change_profile rules to control stacking, in the future the controls may be loosened to allow more flexibility around stacking.

Application Directed transitions and Stacks

When a task is confined by a stack of profiles and it requests to change its confinement via any of the API functions (aa_change_profile, aa_change_onexec, aa_stack_profile, aa_stack_onexec) then transition must be allowed by all the profiles in the stack in view and with replace all profiles in the stack in view.


Eg. If a task is confined by A//&B, and the task requests to aa_change_profile("C")

 Given
   A {
      change_profile -> C,
   }
B { change_profile -> F, } then the request will fail because B does not allow changing to profile C.
 Given
   A {
      change_profile -> C,
   }
B { change_profile -> C, } then the request will succeed and the stack of A//&B will be replaced by profile C.


Eg. If a task is confined by A//&B, and the task requests to aa_change_profile("C//&D")

 Given
   A {
      change_profile -> C//&D,
   }
B { change_profile -> F, } then the request will fail because B does not allow changing to neither C nor D.


 Given
   A {
      change_profile -> C,
   }
B { change_profile -> C, change_profile -> D, } then the request will fail because A does not allow changing to profile D.
 Given
   A {
      change_profile -> C//&D,
   }
B { change_profile -> C, change_profile -> D, } then the request will succeed and the stack A//&B will be replaced by C//&D

Low level interfaces

If access to libapparmor is not available then the low level interfaces may be accessed directly. This will require direct access to the proc file system or in the case of peer creds the getsockopt function. Please note that correct policy rules allowing the stack will still be required, you will have to be careful to handle any errors and separate the mode from the label. Use of libapparmor is the only way to ensure compatibility between versions.


To stack against current confinement

   echo -n "stack profile_a" > /proc/<tid>/attr/current

To stack against confinement at exec time

   echo -n "stack profile_a" > /proc/<tid>/attr/exec

To change current confinement

   echo -n “changeprofile profile_a” >/proc/<tid>/attr/current

To specify confinement to change to at exec

   echo -n “changeprofile profile_a” >/proc/<tid>/attr/exec

To find the current confinement

   cat /proc/<tid>/attr/current

To find the confinement specified to be used at exec

   cat /proc/<tid>/attr/exec

To find a peer’s confinement

   getsockopt(fd, SOL_SOCKET, SO_PEERSEC, buf, &optlen);

Threading and Self Directed Transitions

The above examples used <tid> to indicate that the kernel thread id should be used. Under most circumstances using the /proc/self/attr/... symlink is sufficient. However in multi-threaded processes this can result in a failure. In a multi-threaded application the thread must write the proc file that matches its thread id, not the pid of the process.

Writing to the wrong proc file is not the only source of failure that can occur when a multi-threaded application is using self directed transition. Self directed transitions only apply to the thread that set them. So if a thread successfully sets a transition, it only applies to that thread, other threads with-in the process will remaine confined by the old confinement. This can lead to weird transitions failures as transitions become dependent on which thread does the exec.

Due to these issues AppArmor may reject self directed transitions of multi-threaded processes unless policy allows them.


AppArmor provides a special change_instance api for dealing applications that need to transition multiple threads or processes.

Using Stacking in combination with Policy Namespaces

Stacking is not limited to use with profiles within a policy namespace as was done in the examples above. It can be used to set up confinement that crosses policy namespace boundaries. This allows AppArmor to enforce a system policy on a container/chroot, while allowing the container to specify its own set of policy. It can also be used to enforce system policy while allowing a user to specify their own policy.

Expressing a stack that crosses a policy namespace boundry

Like a regular profile transitions that changes namespace boundaries

 px /bin/foo -> :ns1://profile,
 change_profile -> :ns1://profile,
 aa_change_profile(":ns1://profile");

the stack that is to cross an namespace boundary just includes the namespace name as part of the profile specification

 px /bin/foo -> local_profile//&:ns1://profile,            # specify the stack
 px /bin/foo -> &:ns1://profile,                           # stack against current confinement
 change_profile -> local_profile//&:ns1://profile,
 change_profile -> &:ns1://profile,
 aa_change_profile("local_profile//&:ns1://profile");
 aa_stack_profile(":ns1://profile");

Stacks and policy namespace views

When a task is confined by a stack the profile with the namespace that is lowest in the namespace hierarchy determines the namespace view for the task.

Using the above example and assuming that the view is the same as the current namespace then task C can not see the profile vm1 nor that its current namespace is :ns1:. When it introspects it self it will see

 unconfined

However another task S that is only confined by the system's root policy namespace when introspecting task C will see

 vm1//&:ns1://unconfined

Task C if it tries to introspect task S will only see

 ---


When a stack contains profiles from different namespaces, some profiles will be proceeded by their namespace specification.

 A//&:ns2://B

Profiles from the current namespace will always be shown without their namespace specifier while all other profiles in the stack will be proceeded by their namespace specifier.

Stacks and the current namespace

Because a stack has multiple profiles there is no singular current namespace, instead the namespace of each profile is the current namespace for that profiles transitions, and rule enforcement. That is if a domain transition or rule does not specify a specific namespace then the profile label it is referencing is relative to the namespace of the profile making the reference.

 Eg. given the stack A//&:ns1://C with the rules
A { px /bin/foo -> B, signal peer=B, }  :ns1://C { px /bin/foo -> D, signal peer=D, }
then
A's rules are relative to its namespace, while C's use of D references a profile D in :ns1:
so an exec of /bin/foo would result in a new stack of
B//&:ns1://D

the current namespace is always the namespace of the profile in the stack having its permissions evaluated

label permissions in stacking

When permissions on a given label are required (like in IPC) the permission check is done against each component of the target stack label.

 Eg. Task 1 is confined by A, and it is communicating with
     Task 2 confined by B//&C.
Profile A must contain the correct permissions to communicate with label B and with label C.

The component test that is done is two fold: a first test is done to check if profile A can communicate with the stack B//&C, if that test fails the individual components of the stack are checked, that is can profile A communicate with profile B and can profile A communicate with profile C, and the intersected permissions of the two tests is used to determine whether the communication can occur.

This two fold test allows for granting permissions to communicate with a specific label stack without granting permission to communicate with a single component of the stack, ie. the task can communicate with B//&C but not with a task labeled with just B.

stacking IPC and the cross check

IPC in AppArmor uses a cross check to ensure policy consistency, that is can task 1 send some communication to task 2, and can task 2 receive communication from task 1. In the context of stacking the label permission check is done for each direction.

 Eg. using the example from above. Task 1 is confined by A and Task 2 by B//&C.
    A is checked for permission to send to B//&C if successful
      B is checked for permission to receive from A
      C is checked for permission to receive from A
    if the full stack check was not successful then
      A is checked for permission to send to B
      A is checked for permission to send to C
      B is checked for permission to receive from A
      C is checked for permission to receive from A

IPC and labels crossing namespace boundaries in a stack

In a stack the General rule for evaluating labels that cross namespace (not just view boundaries) is that only the portion of the stack that is in the same namespace is considered.

 Eg. Give Task 1 confined by the stack A//&:ns1://B and Task 2 confined by C//&:ns1://D
     the label or IPC check will be between A and C as they are in the same namespace and B and D

The exception is when a stack does not have profile from the same namespace as the label or stack being considered.

 Eg. Given Task 1 confined by the stack A//&:ns1//B then
     if Task 2 is labeled with:
       C        - there is no profile in the same namespace as :ns1://B
       :ns1://D - there is no profile in the same namespace as A
       :ns2://E - there is no profile in the same namespace as A nor :ns1://B
       C//&:ns2://E - there is no profile in the same namespace as :ns1://B

In this situation a few different things can happen.

  • If access has been delegated to the missing namespace then the delegation can take the place of the missing element
  • If the stacks share a parent namespace then a missing child namespace is treated as unconfined
 Eg.  Task 1 is confined by A
      Task 2 is confined by B//&:ns1://C
      then for permission check purposes A is treated as A//&:ns1://unconfined
  • If the stacks do not share a common namespace, nor have delegation permission is denied (this is the same as cross namespace communication discussed in namespaces
 ie. label A communicating with :ns1:B
 eg.  :ns1://A//&:ns2://B communicating with D//&:ns3://E

Application Directed transitions and Stacks that cross namespace boundaries

Like IPC the behavior of application directed (aa_change_profile(), ...) transitions changes slightly when the stack of profiles crosses a namespace view boundry. The application directed transitions only applies to the portion of the stack that are visible (in view) to the application. This means that the permission check for change_profile is also only done against the portion of the stack that are visible. The remaining parts of the stack that are not visible (out of view) don't participate and remain unchanged.

Eg. If a task is confined by A//&:ns1://B, with a view set to :ns1: and the task requests to aa_change_profile("C"). Because the request for C does not specify a namespace it will transition relative to the profile's namespace which for A is the root and profile B is :ns1:. However A is not visible to the task so :ns1://B (which appears as just B to the task) is the only profile that participates in the transition.

 Given
   A {
   }
   :ns1://B {
change_profile -> D, } then the request will fail because B does not allow changing to profile C.
 Given
   A {
   }
 :ns1://B { change_profile -> C, } then the request will succeed and the stack of A//&:ns1://B will be replaced by the stack A//&:ns1://C which the task sees as just C.


Eg. given the above example but with a request to aa_change_profile("C//&D")

 Given
   A {
   }
 :ns1://B { change_profile -> C//&D, } then the request will succeed and the stack of A//&:ns1://B will be replaced by the stack A//&:ns1://C&:ns1://D which the task sees as just C//&D.


Eg. If a task is confined by A//&:ns1://B//&:ns1//ns2://C, with a view set to :ns1: and the task tasks sees its confinement ad B//&:ns2://C. The task requests to aa_change_profile("D"). Because the request for D does not specify a namespace it will transition relative to the profile's namespace which for A is the root, profile B is :ns1:, and C is :ns1//ns2:

 Given
   A {
      change_profile -> D,
   }
 :ns1://B { change_profile -> F, }  :ns1//ns2://C { change_profile -> D, } then the request will fail because B does not allow changing to profile D.
 Given
   A {
      change_profile -> D,
   }
 :ns1://B { change_profile -> D, }  :ns1//ns2://C { change_profile -> D, } then the request will succeed with the stack A//&:ns1://B//&:ns1//ns2://C transitioning to the stack A//&:ns1://D//:ns1//ns2://D which appears to the task to be D//&:ns2://D. Notice that the transition to profile D is actually to two different profiles, :ns1://D and :ns1//ns2://D


TODO: transition to specific profile in ns view.

transition to specific profile at root in ns view

transition to stack in ns view

transition involving onexec, show out of view profiles use their exec transition

Additional Information

Introspection and Profile Mode

In addition to the profile name the profile mode is available when using aa_getcon() and aa_getpeercon() api functions to do introspection on a process. In the case of stacking if the mode is the same for all profiles in the stack it will displayed as normal. That is if all profiles are in enforce mode then the mode will be (enforce) however if not all profiles in the stack have the same mode then the mode will be reported as (mixed).

The true mode of each profile can be looked up via the apparmor/policy/ directory under securityfs.

Audit Logging

Under most situations stacks are logged on a per profile basis, so that logging only occurs for the portion of the stack that triggers the denial or audit message.

Eg. Given a stack of A//&B then

If A allows a permission request and B denies it the there will be a log message for the denial from B but no message for A.

If both A and B deny the permission request then there will be two audit messages in the log one with one message with profile=A and another message with profile=B.

In neither case is the profile=A//&B.

In a few cases it is possible that the stack will be logged especially when reporting an object label or peer. It is also possible that a userspace trusted helper (dbus, xserver) will log messages for the entire stack instead of a message per profile.

Stacking with unconfined

Stacking against the unconfined profile does not result in reducing the stack to the profile(s) stacked against unconfined.

ie. unconfined & A does not reduce to just A but instead results in the stack unconfined//&A. While this may seem strange behavior this allows for setting up a stack where both the transition for unconfined and the stacking profile are applied.

eg. say we have a process in the unconfined state and we have the following profiles

  profile /bin/example {
     ...
  }
  profile A {
     /bin/example px -> B,
     ...
  }
  profile B {
     ...
  }

the call aa_stack_profile(A) on the unconfined process results in unconfined//&A, which at exec time turns into /bin/example//&B.

Seccomp and no_new_privs

If seccomp is in use and no_new_privs is set then most domain transitions are blocked as they could increase task permissions. Stacking transitions that add to the current confinement are still allowed as they strictly reduce the set of permissions that are available. More generally any transitions that is provably at the profile level a subset of the current confinement is allowed (that is to say rules with in the profiles are not considered, only the profile sets).

So when confined by A a transition to A//&B is allowed, as A//&B has a subset of permissions allowed in A.

However if confined by A a transition to B//&C is NOT allowed even if the resultant permissions of B//&C are a subset of A because the subset is not provable based only the profiles in the transition.

Open file handles and memory maps

Like aa_change_profile, open file descriptors, and memory maps may not be remediated after a call to aa_stack_profile() so the calling program should close(2) open file descriptors or ideally call exec() to clear out process state after calling aa_stack_profile().

To be clear if an immediate transition (aa_change_profile, aa_stack_profile) is used apparmor will revalidate file descriptors on access. However there are situations that AppArmor is not capable of revalidating, eg. a process accessing a memory mapped file via direct memory access. The only safe way to deal with these situations is to either audit the process to ensure all such resources have been closed or to create a new process via exec().

Implementation efficiency, Static vs Dynamic computation

The permission check on the stack and the cross check in particular can result in multiple sub permission checks. This is not necessarily what is done by AppArmor at run time. The examples provide the step by step break down of the checks for clarity. The AppArmor compiler is free to pre-compute the results of different stacks when policy is compiled together so that a set of checks can in fact be satisfied by a single permission lookup.

Precomputing the stack into a static policy for run time can result in slower compiles an larger loaded policy size but it can reduce the runtime cost to a single permission check, the same as a single profile.

Stacking Limitations

Stacking in 3.5-beta1 is limited to crossing 1 policy namespace boundry, with the limitation that when the current namespace of the task is NOT the system policy namespace then the current user namespace must not be the system user namespace.

This limitation is in place because AppArmor uses the CAP_MAC_ADMIN capability to help control who can manage policy in the current namespace. However the Linux kernel does not yet have hooks enabling AppArmor to follow or control user namespaces. Thus it is possible for a user to create a user namespace that has capability MAC_ADMIN and not transition or stack a policy namespace, thus giving the user the ability to manage the system policy namespace.

Once proper controls around user namespaces are established this limitation can be lifted.


References

 aa_stack_profile(2)
 aa_change_profile(2)
 aa_getcon(2)
 apparmor.d(5)