[apparmor] Profile stacking

John Johansen john.johansen at canonical.com
Sun Sep 5 11:22:54 BST 2010


One of the features that has been planned for a while is stacking and
composition of profiles.  This will enable a task to be confined by
multiple profiles simultaneously.  With the resultant permissions set
being the intersection of the set of stacked profiles.

There are a couple of use cases for profile stacking:
 * user profiles, allowing a user to create and enforce their own profiles
   independent of what the system is enforcing.
 * child composition, this will allow a parent to launch a
   child, that retains the parents restrictions while picking up new ones.

Both forms of composition could be mostly done in userspace by creating
a new profile that is the composition of the parent and child profiles.
However this is not always the best solution because it could require
creating an exponential set of new profiles (1 for each possible 
combination).  For user profiles it is even more problematic as it means
the profiles user load will have to go through a privileged daemon,
to handle the intersection and load.


At a minimum stacking needs
* A way to specify that an x transition should stack instead of doing
  standard transitioning.

  eg.
    /child px+,

  This would be considered for each profile confining a task, so it is
  possible on an x transition that, the number of profiles in the stack
  could double.  eg. 2 becomes 4 if each profile in the stack specifies
  composition.

  Implementation wise stacking should also check that any new profile
  being added to the stack isn't already on it.

* A specification of how stacking interacts with exec
  the exec needs to be allowed by each profile and then the transitions
  for each profile in the stack are applied.  This allows each profile
  in the stack to transition independently.

* A way to introspect the profiles on a task
  We need a way to report all the profiles confining a task. Currently 
  AppArmor reports the single profile via

    /proc/<pid>/attr/current

  We need to create a standard to be able to report more than 1 profile.
  I was thinking possibly of using //+ as a separator.

  eg.
    /profile1
    /profile1//hat1
    /profile2
    /profile2//hat2
    :namespace1:/profile3
    /profile1//+/profile2
    /profile1//hat1//+/profile2
    /profile1//+/profile2//hat2
    /profile1//hat1//+/profile2//hat2
    /profile1//+:namespace1:/profile3
    ...

* A specification of how stacking works with file labeling and delegation
  Files are labeled with a special label indicating the set of profiles in
  the stack.  When comparison of dynamic labeling is done it is against
  the full set.

  Delegation/ipc would require the ability to talk with all profiles in
  the stack.

* An api
  If only strict intersection is allowed then the profile probably doesn't
  need any special rules like change_profile as this operation would
  only ever result in a reduced permission set.

  eg.
    int compose_profile(const char *profile);
  
* A specification of how stacking interacts with change_hat and
  change_profile.

  currently my thinking is that the call must succeed for all profiles in
  the stack or it fails.  This would mean that for standard change_hat
  all profiles would transition to the same hat.  For change_hatv it would
  be possible for each profile in the stack to transition to a different
  profile in the vector.

* Decide details of user profile case.  The user profile case is special
  and has a few extra consideration over generic stacking.
  + is user profile attachment special and automatic or handled generically
    - if special then when a user executes a task, a search is made for
      the user namespace and if it exists, a search for the matching
      profile is done.

      This basically means
      - user namespace and profiles don't have to exist before any user
        tasks are executed.  They will be automatically picked up and
        used when added.
    - non special via task confinement
      This means all profile lookup would be done off of current task
      confinement.  So for a task to pickup user profiles it must already
      be confined by a user profile (or the special user unconfined 
      profile).

      This would mean that tasks launched without user profile confinement
      would not get it unless they specifically asked for it.

      The generic case of having all user tasks properly confined could
      be setup by the pam_apparmor module.  Where it creates the user
      namespace, calls the api to create a stack and then setuids to
      the user.  All children tasks of the session then would inherit
      both confinements.

      This is less flexible than handling the user stacking specially
      but requires less code/no special casing.
  + What rules a user profile can contain
    - Does it make sense to allow a user profile to contain capability
      rules?  Possibly especially if the user has some system capabilities.
    - can the user create their own namespaces
  + What users can load profiles
    - probably a control, along with how much memory the users profiles can
      use. etc.
    - if pam_apparmor is used to setup the user namespace then, the 
      namespace can carry memory limits, and if it doesn't exist the user
      is not allowed to load profiles.
      Else will need special rules.
  + Who loads the user profiles?
    - user login session script?
    - pam_apparmor before any user task is run, ...
  + When are user profiles unloaded?
    - when user no longer has any running tasks
    - when user logs out (ref counted for multiple sessions?)
    - when user explicitly unloads them

  There is also the possibility of a slightly hybrid implementation of
  user profiles, where pam_apparmor is used to create the user namespace,
  but doesn't stack the user namespace on the first task, instead user
  attachment decisions are made dynamically.

  This avoids having a user unconfined profile on each user task as well
  as the system, unconfined.


- Beyond basic profile intersection -

Profile composition could be carried beyond simple intersection.  There are
a couple things that would be nice to have for some situations but I am not
sure they are worth the added complexity.

* The parent profile can control which rules can be intersected with the
  child.  This would make it so the child cannot remove certain permissions
  granted by the parent.  The trick would be how to specify these, would it
  be per rule in the profile, or would it be per x transition (how would
  compose_profile api be handled then?)

* A non strict intersection stacking.  This one gets complex fast, and
  I don't have a good way to handle this atm, and in general I think
  its dangerous as it in some sense widens permissions.

  The idea being that there are cases where you want a intersection of 
  parent and child unioned with some extra rules (usually from the child).

  The general reason you would want this is to have a tighter parent
  profile, so that it doesn't need to contain sufficient permissions
  for children to run when intersected with the child profile.  Basically
  the child profile almost needs to be split into two parts, the part
  that can be intersected and the minimum part to be able to run the
  application.

  In general I think much of this type of intersection can be handled
  adequately with delegation and standard profile transitions.




More information about the AppArmor mailing list