[apparmor] stack and stack_onexec semantics again

Tyler Hicks tyhicks at canonical.com
Wed Mar 9 06:03:48 UTC 2016


On 2016-03-08 17:00:36, John Johansen wrote:
> We need to settle on the exact semantics stack and stack_onexec. This
> topic has been discussed before but without proper resolution.
> 
> First lets look at the semantics of change_profile and change_onexec
> 
> change_profile allows changing the profile immediately, while
> change_onexec allows delaying the change until the exec barrier. The
> change_profile api is really a special use api because of the
> limitations around memory and fd revalidation. It can be used to provide
> a different profile for an executable, but in those cases B becomes a
> special stub profile.
> 
> That is given a process 1 confined by profile A, an executable /e with
> the following defined policy
> 
>   profile A {
>      change_profile -> B,
>      px /e -> D,
>   }
> 
>   # stub profile
>   profile B {
>      include "stub_rules",
> 
>      px /e -> C,
>   }
> 
>   profile C {
>     ...
>   }
> 
>   profile D {
>     ...
>   }
> 
>   profile E {
>      ix /e,
>   }
> 
> if process 1 does NOT call change_profile when it execs /e it will change
> into profile D
> if process 1 does a change_profile (the only change allowed) it will end
> up in B and then /e is execed it will change into profile C
> 
> Of note is that change_profile allows changing the immediate confinement
> (obvious) but also the confinement of the exec. Also of note is that the
> stub profile contains a set of rules necessary to perform the necessary
> operations between the change_profile and the exec.
> 
> 
> We can get rid of the stub profile by using change_onexec.  This requires
> changing profile A into A' as follows
> 
>   profile A' {
>      include "stub_rules",
> 
>      change_profile /e -> C,
>   }
> 
> Of note is that A' now includes the stub profile's set of rules, and the
> change_profile rule is changed to have and exec constraint. The use of
> change_onexec simplifies policy but change_profile does have its use in
> a priv sep situation where it is desirable to separate the stub rules
> from the profile.
> 
> 
> 
> Now on to stack, and stack_onexec, which allow for a different way to
> change the tasks confinement. Instead of transitioning to a new profile,
> a stack of multiple profiles is created, with the effective permissions
> being the intersection of the set. The question is how should be stacking
> rules be restricted and what are the exact semantics of the stack_onexec
> case. The first thing to note is that a stack strictly reduces the
> permission set, and that each profile in the stack determines its own
> transitions.
> 
> So lets say process 1 confined by profile, calls aa_stack(E) resulting in
> a confinement of A//&E, when the process then execs /e the following
> happens
> 
>   A -> D
>   E -> E
> 
> resulting in a new stack of D//&E
> 
> Since E can not grant privilege not allowed by A or D, at first glance
> aa_stack() does not strictly need to be controlled by a rule, which would
> be good for the potential use where a user wants to further restrict
> system policy without modifying it directly and dealing with the package
> management grief that entails. However there are some complications, it
> is possible that a stack could result in a process being denied something
> it normally wouldn't which could include dropping of capabilities or
> transitioning to a new executable with a different confinement. Ideally
> these situation would be properly handled by the application but lets go
> with the assumption that appliations are buggy. So can an attacker
> leverage the stack api and a buggy application to gain privileges?
> Unfortunately in some cases yes.
> 
> Imagine the app has some form of privsep mode, obtained by dropping
> privileges or transitioning a forked child to an executable. If the
> privsep setup succeeds the app is happy but it is designed to work in
> environments where setting up the privsep fails (or it just doesn't
> handle the error) and continues to work without the privsep. The
> question is does this actually matter for the stacking api? In all but
> maybe a few cases the answer is actually no, because for an attacker to
> be in a situation that they can influence the confinement by calling the
> stacking api, or at least influencing what it is called with, they
> already have access to the larger permission set, and likely other attack
> options available to them. It is possible that the code that is being
> exploited is after the insertion of a call to the stack api, but again
> either the attacker has to influence what the application is specifying
> in an existing api call or it is managing to insert a new stack api call.
> 
> 
> The stack_onexec api however is a different story dependent on when/how
> the stacking is applied. There are 3 places/ways it can be applied.
> 
> 1. straight override applied to current confinement when stack_onexec is
>    called
>   ie.  A + stack_onexc(B) results in A//&B

Is this the same behavior as change_onexec()? In other words, does
"A + change_onexec(B)" result in B or D when /e is exec'ed?

> 
> 2. applied to current profile at the start of the exec, but the exec
>    transition is applied
>   ie. using the policy from above and execing /e
>    A + stack_onexec(B)  -> A//&B which then transitions to -> D//&C
> 
> 3. applied to transition profile after the exec transition is applied.
>   A -> D + stack(B) resulting in  D//&B
> 
> 
> #1 allows overriding exec transitions and allows a potential attack on
>    policy without strict rules to control it
> 
> #2 mimics the behavior of calling aa_stack() except that the stacked
>    profile won't be used for anything except its transition
> 
> #3 this allows allow for a transition that does not override the domain
>    transition and could be used with few to no rules to control it (ie.
>    it is not vulnerable to the attack in #1)
> 
> 
> 
> Before going any further lets look at a parallel to stacking, stacked (or
> compound) transition via change_profile.
> 
> It is possible for change_profile to specify a stacked target. That is
> 
>   aa_change_profile(B//&E)
> or
>   aa_change_onexec(C//&E)
> 
> where the necessary change_profile rules would be
>   profile A {
>     change_profile -> B//&E,
>     ...
>   }
> 
>   profile A'' {
>     change_profile /e -> C//&E,
>   }
> 
> currently we would also accept
>   profile A {
>      change_profile -> B,
>      change_profile -> E,
>      ...
>   }
> 
> and
>   profile A'' {
>      change_profile /e -> C,
>      chagne_profile /e -> E,
>      ...
>   }
> 
> as the final result is a subset of both of these. However these allow a
> broader set than is likely desired as it allows transitioning to C or E
> without the stack. Whether we want to keep this later form is a bit of
> an open question but it does allow for subset construction without
> having to specify each possible combination.
> 
> This change_profile example is interesting in that it shows standard
> change_profile rules can be applied to control the stack. Whether we want
> a different rule or extension to indicate that only stacking is allowed
> is still an open question.
>   ie.
>      change_profile -> &B,
>      change_profile -> &B//&E,
>   or
>      stack_profile -> B,
> 
> 
> now one more point to add before we get back to considering the semantics
> of the stack profile api. In the current iteration change_profile and
> stacking only apply to the current namespace. That is to say if a task is
> confined by multiple profiles across different namespaces, at this time
> self directed transitions (stack, change_profile) only apply to the
> profiles in the current namespace (lowest namespace in the stack).
> 
>   Eg. If a task is confined by
>     parent_profile//&:child_ns://child_profile
> 
>   change_profile(A) would result in
>     parent_profile//&:child_ns://A
> 
>   and aa_stack(A) would result in
>     parent_profile//&:child_ns://child_profile//&:child_ns://A
> 
>   with the ns spec being repeated to be very explicit about which ns the
>   profile being stacked belongs to
> 
> The important point to note here, is that we don't need to consider
> stacking/and change_profile across namespaces except for where they add a
> new namespace to the confinement. ie. We could do
>   change_profile(:child_ns://A)
> 
> to transition into a child namespace, or
>   aa_stack(:child_ns://child_profile)
> 
> to transition from
>   parent_profile
> 
> to the stack of
>   parent_profile//&:child_ns://child_profile
> 
> However these cases are covered by standard change_profile rules.
> 
> Finally we can get back to stacking
> 
> So how is stacking different than using change_profile with stack?
> Technically it isn't. It is the same as doing
>   confinement = aa_getcon()
>   confinement += //&stack
>   aa_change_profile(confinement)
> 
> what it does allow for is a task not to need to know about its current
> confinement. It also provides the opportunity to be slightly lazier and
> looser with policy where a stacking to reduce permissions could be
> allowed without policy require change_profile rules, and it also provies
> the opportunity to play with the semantics of stack_onexec.
> 
> So in the end the questions are?
> 
> 1. Do we want to provide access to stacking without rules, or reduced
> rule requirements.
> - this can be allowed in the straight stacking without much risk

I think there is a fair amount of risk since we now have a large number
of userspace programs that are making policy decisions based on AppArmor
confinement contexts. In some cases, they're using the query API to have
the kernel make the decision for them and I assume that the kernel is
handling that appropriately. However, in other cases, they're making
their own decisions based on the confinement context and I highly doubt
that they're all ready for something crazy like
":ns:foo//&bar//&:child_ns:baz (mixed)".

(waiting to chime in on #2 until the question above is answered)

Tyler

> - this is problematic for stack_onexec, but see 2 below
> 
> 
> 2. Which semantics of stack_onexec should we go with?
> It seems clear to me that #1 is the way to go as it is equivalent to
> doing aa_getcon(), concat, aa_change_profile() as outlined above.
> However this requires change_profile rules other wise it can be used to
> over ride policy transitions when it shouldn't. If we want to allow for
> stacking without requiring change_profile rules we could the requirements
> some, by
>   1. allowing the stack_onexec if the exec transition is respected
>      eg. if a task is confined by profile E and it stacks C
>         E -> E//&C
>      since the exec transition E -> E is specified in the profile (ix)
>      then stacking C on top is no different than doing a regular stack,
>      as it doesn't override the exec transition. This allows for
>      stacking without requiring the stub profile until the exec
>      transition. That is to say that it has a parallel to what is done
>      with change_profile vs change_onexec.
>      ie. in task 1 is confined by E then
>        E + stack(B) -> E//&B -> exec /e -> E//&C
> 
>      where B is the stub profile and
>        E + stack_onexec(C) -> exec /e -> E//&C
> 
> 
>   2. allowing the stack_onexec if there is a change_profile rule for the
>   existing labeling.
>     This is similar to point 1 above but instead of relying on just the
>     exec we could also allow transitions allowed by change_profile for
>     the current confinement.
>       eg. if a task is confined by profile A' which allows
>         change_profile /e -> C,
> 
>       then a change_onexec() or stack_onexec() that resulted in
>         C//&something would be allowed as
>         A'' is allowed to be replaced by C in the basic change_onexec() case.
> 
> I can't see another way to loosen the requirements for requiring
> change_profile rules except the #3 proposal above which does NOT
> correspond with the constructed behavior of stacking ie. aa_getcon(),
> concat, and aa_change_profile() so I find it surprising and less than
> intuitive.
> 
> -- 
> AppArmor mailing list
> AppArmor at lists.ubuntu.com
> Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/apparmor
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 819 bytes
Desc: not available
URL: <https://lists.ubuntu.com/archives/apparmor/attachments/20160309/d9e210aa/attachment.pgp>


More information about the AppArmor mailing list