[apparmor] stack and stack_onexec semantics again

Tyler Hicks tyhicks at canonical.com
Wed Mar 9 06:22:43 UTC 2016


On 2016-03-09 00:03:48, Tyler Hicks wrote:
> 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?

I've verified that the result is B, meaning that change_onexec()
also acts like a straight override much like #1.

> 
> > 
> > 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.

I also think that #1 is the way to go.

> > 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

This is difficult for me to understand and I'm in the middle of writing
tests cases for stacking. I don't see how we can easily document such
special, corner case behavior in a meaningful way. I feel like this
increases complexity (implementation, documentation, testing, etc.) for
very little benefit.

> >   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.

This feels more straightfoward to me. I wouldn't be against this but I
also don't personally see a strong need for it.

Tyler

> > 
> > 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/f40c466b/attachment-0001.pgp>


More information about the AppArmor mailing list