[apparmor] "default"/"system" profile
John Johansen
john.johansen at canonical.com
Thu May 16 10:33:52 UTC 2013
On 05/16/2013 02:20 AM, Steve Beattie wrote:
> On Wed, May 15, 2013 at 05:13:15PM -0700, John Johansen wrote:
>> So this is a new attempt to frame the default/init/system profile discussion
>
> Thanks for writing this up (and opening the whole can of worms to begin
> with, even if you end up regretting it :) ).
>
>> The goal of the default/system profile is to replace the unconfined profile
>> and make it easier to have a default system policy and also to confine
>> applications from boot. The unconfined profile has few different properties
>> 1. It is the profile that is attached to init as the first process
>> 2. It is the target profile for ux/Ux profile transitions
>> 3. It is the fallback target for Pux profile transitions
>> 4. It is the fallback target for Pix from the unconfined profile. That is
>> to say unconfined does the equivalent of
>> /** pix,
>> for all execs from profiles confined by pix.
>
> Note that that is one interpretation of the existing unconfined
> profile's policy, it could also reasonably considered as
>
> /** pux,
>
correct. For unconfined as things are currently pix, or pux are equivalent. To
be clear it is pix only because that is how it is implemented in the code
> for all execs from profiles that are unconfined. It's a distinction
> without a difference in the existing implicit unconfined setup;
> however, it has subtle differences when considering unconfined as a
> mode for a default policy, or that there may be *multiple* policies
> in unconfined mode (and I consider it a possibility that the default
> policy may not be one of them, but that may be confusion on my part
> on what it means to be in unconfined mode), and what the implications
> of that are.
>
No you are correct they could be different
> So we should probably document explicitly what unconfined mode is
> equivalent to as expressed in apparmor policy, and how it relates to
> the default policy. (Even if the documented policy is not the actual
> implementation, so long as it's semantically equivalent).
>
yep
> (It also raises the question for me what the bare 'file' keyword is
> equivalent to; looking at our apparmor.d(5) man page, I don't see that
> made explicit anywhere. It's a little confusing because it's intuitive
> what all 'network', 'dbus', or 'capability' access might mean, but
> because file access conflates both read/write/exec operations and
> policy transitions, it's not straightforward what 'file' means.)
>
ah so glad you asked, as I was going to propose making the bare file
keyword not imply any x permissions. Currently it does ix but I believe
this is wrong, and the x permissions should be separated out unless
they are explicitly provided.
So bare file would imply all file permissions excluding the profile
(domain) permissions.
>> 5. unconfined is assumed by most policy to be the common system default,
>> though there is nothing in apparmor that requires this as profile
>> transition are determined by profile rules not globally.
>> 6. unconfined is the default profile transitioned to when the profile a
>> task is confined by is removed.
>> 7. the unconfined profile is exposed on interfaces (historical artifact) as
>> unconfined
>> instead of
>> unconfined (<mode>)
>
> Well, sort of. An enforcing policy was implicitly considered to be
> enforcing if no mode was listed in an exposed interface (which is still
> the case). The unconfined policy could be considered to be enforcing
err is it? The mode is always exposed via the current interface. The intention
may have been that mode was enforcing when not specified but that still
doesn't really change anything.
And yes unconfined could be interpreted as a profile enforcing its rules,
where the rules allow everything.
> (even if it can't actually reject anything). But in the scheme of
> things, it's a minor thing anyway.
>
yep, for the purpose of the change the point being we shouldn't break completely
but there is some updating of the tools required.
>> 8. the unconfined profile can't be replaced or removed as it is special
>> and not in the profile lists
>> 9. the unconfined profile can't be directly transitioned to by
>> /foo/bar px -> unconfined
>> 10. the unconfined profile is treated special to bail out of mediation
>> early
>
> Which is really an optimization feature of the current implementation,
> and (should) not result in a semantic difference.
>
yes theoretically, but practically it can have a difference when no policy based
errors get throwing trying to mediate something. Its unusual but can happen
>> 11. each namespace has its own unconfined profile used to make sure profile
>> attachment occurs from the correct namespace
>>
>>
>> To replace unconfined we must be able to address each of these points.
>>
>> Fixing 2 & 3:
>> If there is to be a default profile then having a separate unconfined
>> profile that can be used to escape the default profiles confinement is
>> not acceptable. U/ux based transitions need to become transitions to the
>> default profile what ever that is (whether it is unconfined or some other
>> profile).
>
> I think that the distinction you've been drawing between the default
> policy and a policy that is unconfined is a good one, and that using
> 'unconfined' to reference both is confusing. I think it's helpful for
> discussion to simply refer to it as 'default_policy', even if that's not
> the name we end up using as its (default, sigh) name.
>
yep it can be confusing, and it was hard trying to split. As the old unconfined
profile served the roll of default policy and init profile, and I wanted to
establish what the unconfined profile did so I needed to refer to it as such.
I will endeavor to keep the split clearer in the future
> Thus, your statement above be "U/ux based transitions would be
> transitions to the default_policy."
>
> (It's a bit unfortunate that our exec modifier U/u is shorthand for
> unconfined, and thus continues the conflation.)
>
yes, I'd like to deprecate its use. But we still have to define how it should
behave with a new default policy scheme
>> Fixing 4.
>> Nothing to do. Pix is behaving correctly if you are in a different
>> profile than unconfined it is the one that gets inherited if that rule
>> is used
>>
>> Fixing 5.
>> Nothing to do. Policy should just work. If an author sets up a system
>> confinement then things that are breaking due to not being unconfined
>> need to be address in the modified policy.
>
> To be more explicit for addressing 4 and 5, existing policy should
> continue to work semantically as before; that is, a U/ux transition
> that before would result in the unconfined state before would result in
> a transition to the 'default_policy' which is a policy in unconfined
> mode. This is also the policy that init will start with. Or to frame
> it differently, mapping the current setup to the new proposal is
> like so: the default_policy is a policy named 'unconfined', which
> is in unconfined mode (and is the only policy to be in that mode,
> modulo apparmor namespace complexities).
>
> What the proposal under discussion gives is the ability for policy
> writers to change that behavior.
>
yes
>> Fixing 6:
>> The solution to this is the same as 2, removal of a profile should result
>> in transitioning to the default profile what ever it is (unconfined or
>> otherwise)
>>
>>
>> Fixing 7.
>> We can just convert to using
>> <profile> (<mode>)
>> and stop using a bare unconfined, the unconfined profile shows up as
>> unconfined (unconfined)
>
> Should enforcing profiles report that status, or should we continue with
> the implicit lack of status mean enforcing?
>
err we explicitly output the enforcing status and have for years, as far as
I am aware this was the behavior that shipped in the first iteration and I
have tried hard not to change it. It would have been nice to not have to
special case unconfined.
> ps -Z 4511
LABEL PID TTY STAT TIME COMMAND
/sbin/dhclient 4511 ? S 0:00 /sbin/dhclient -d -sf
> cat /proc/4511/attr/current
/sbin/dhclient (enforce)
its just that ps -Z only takes the "word" before the whitespace
> And again, using a different name makes the distinction clearer, i.e.:
>
> default_policy (unconfined)
>
I would like to get away from any implicit modes
>
>> this will require some tooling updates to deal with any conditions based
>> on things being "unconfined" but won't out right break tools either
>>
>> Fixing 8 & 9
>> We just make it available and maybe special case its removal, replacement.
>> As there must always be a profile on a task
>>
>>
>> Fixing 10.
>> We add an unconfined mode, it can be placed on any profile. (DONE)
>
> Can you be explicit and say what it means for a profile to be in
> unconfined mode? What are the exec transitions for it (i.e. '/** pix'
> or '/** pux')? Can it have additional restrictions in the form of deny
> rules, or other modifiers in the form of audit rules as well stricter
> (from a path dominance definition) exec transition rules?
>
It currently mimics the unconfined profile behavior 100%. That is no rules
are enforced, no denie are enforced, short circuiting is done and transitions
are pix
Note: that in this case pix and pux are different as the profile in question
may not be the default_policy profile.
There is some wiggle room in the semantics but not much. If you want to enforce
deny rules you are in an enforcing mode.
>> Fixing 11.
>> Nothing to fix, profiles will still need to be per namespace, and tracked
>> per namespace. That means the default/system/init profile that replaces
>> unconfined needs to be per namespace as well. However having a global
>> kernel config that sets the name of the "default" profile for all
>> namespaces may not be correct for cases where a container is being used
>> as a VM. In that case any "default" profile name could be configured as
>> part of the container setup, but defaulting to the system one or the
>> parent namespaces default profile name is an acceptable first pass
>> solution.
>>
>> That is there will need to be work to define this per namespace but that
>> can be done later
>
> AppArmor namespaces throw in a whole additional layer of complexity to
> this that I have a hard time thinking about it. That said, given that
> in the existing implementation, namespaces have different unconfined
> profiles, each namespaces default_policy can be considered an attribute
> of that namespace.
>
yes it has to be
> I will note that I personally have a myopia around namespaces in that,
> because they were grafted on, I tend not to think of them as first class
> objects in apparmor.
>
interesting, can you elaborate as to why?
To me they are fully integrated and are first class, though they still need
some work.
>> Now to the harder point
>>
>> Fixing 1. Confining init
>> First up the unconfined profile conflates two issues, confinement of init
>> and the default profile. This is largely because these two issues are not
>> so easily separated. But for the discussion lets tackle each separately
>> and then decide whether they should be combined.
>
> I agree that we're conflating the default_policy (an attribute of
> the toplevel system namespace) and the initial_policy that is applied
> to init (which is, I grant, going to be the default_policy. At least
> *initially*).
>
>> When the system starts up the kernel attaches a profile to the initial
>> process (init). What exactly init is will vary depending on the system,
>> but it is responsible for bringing the system up. At this point we do
>> not have real context about what init is, just that it is the first
>> process. This first process runs and forks/execs children to do
>> different jobs and they inherit its profile (unconfined). At some point
>> policy is loaded and profile attachments can begin.
>>
>> In a system with an initrd, we can think of init as the early init
>> process that is responsible for doing the tasks required by the initrd
>> (loading modules, etc) before the real init takes over. It is possible to
>> load policy from the initrd, though if doing so it should be only what
>> is required and precompiled. This way policy is in place when the system
>> init is started and policy is correctly applied to the whole system.
>>
>> Loading policy from the initrd is a pita, and it doesn't work systems
>> that don't have an initrd, so it is desirable to be able to trust early
>> boot, and do an early policy load which provides system confinement. This
>> is not currently possible as the unconfined profile can not be replaced,
>> nor can individual tasks have their profile set by an external task. To
>> be able to confine the processes that are present before policy load
>> occurs (init, and several children) the profile on them must be
>> replaceable. However all these processes share a single profile, because
>> the initial profile has no policy transition rules, and no other profiles
>> are available.
>
> The reason init's confinement is an issue is because in this proposal
> we're granting the ability to an administrator to change the default
> policy to be more restrictive than straight unconfined (mode). But
> I can envision wanting to make the default policy be restrictive in
> a way that is inappropriate for init and causes it to break.
>
yes, but practically splitting them might only really be possible with an
early policy in which case init gets a different profile than the default
policy.
>> There are several potential solutions to the problem of confining init
>> and its early children
>> 1. Policy load in the initrd (assuming you have an initrd), and having
>> the early initrd init exec into the system init process
>> This is clean and allows policy to be specified separate from the
>> kernel.
>
> This is clean if you're a distribution/OS vendor, and you're
> willing to do the work to support it *and* add the flexibility for
> an administrator to customize it. Requiring that an administrator
> make custom modifications to their initrd (and roll those out across
> an enterprise) doesn't really lead to maintainable systems, and will
> mean this feature/flexibility is unlikely to be used.
>
yes
>> 2. special case init and children in the kernel code
>> - No, just no.
>
> Agreed.
>
>> 3. provide a basic policy compiled into the kernel. This is like the
>> initrd solution but is built into the kernel. The initrd solution is
>> more flexible but may not be available on some systems
>>
>> 4. set the profile on individual tasks, after policy load
>> - this is racy as you are chasing different tasks trying to properly
>> confine tasks with the initial profile
>> - this ability does not currently exist and likely won't for a long
>> while
>>
>> 5. provide the ability to replace the initial profile, and accept all
>> tasks under the initial profile will have the same confinement
>> - changing the confinement of the tasks is not really an option as
>> attachments and parent child hierarchy chains aren't reliable
>> - what name to use
>>
>> 6. Put the initial profile into a "complain" like mode where each child
>> process gets its own child profile and the hierarchy is maintained.
>> - this is somewhat racy as you still need to chase down and replace
>> the dynamic profiles before they fork a child and exec to yet
>> another new profile
>> - do not want complain messages to the log, so new mode?
>>
>>
>> The name of the initial profile is not terribly important as long as it
>> is consistent and conveys the correct meaning. This could be "init",
>> "system", "default", ... (remember at this point we are not yet
>> discussing the default confinement profile). When renaming replacement
>> becomes available this profile can also be renamed as it is replaced,
>> and several profiles (if the hierarchy option is chosen) can be
>> collapsed into a single profile. Also as suggested this initial
>> profile name could easily be defined as a grub boot option, or as part
>> of the Kconfig when a kernel is built
>>
>>
>>
>> Now on to the default profile
>>
>> The default/system profile is used when ux/Ux is used or a profile is
>> removed and application confinement falls back to the default.
>> Traditionally this and the init role have been played by the unconfined
>> profile, but it is possible to separate them. The question is how and is
>> it worth it. If the default/system profile is not attached on boot then
>> it is the init profile will be attached to processes on boot and the
>> default profile won't see use until a profile is removed or ux/Ux/pux is
>> encountered. This means the system is confined by the init profile by
>> default.
>>
>> For a finer grained confinement on boot, multiple profiles need to be
>> defined along with their transition rules as discussed above, without
>> that the "init" profile is the default in use by the system after boot,
>> and having a separate default profile that is transitioned to on profile
>> removals is confusing.
>>
>> So without having a more detailed policy defined at boot what can be
>> done?
>>
>> Two profiles could be define at boot (well actually for each namespace),
>> an init and default profile. The init process would start in the "init"
>> profile and at some point the default profile begins to be used. The
>> question is when?
>>
>> a. on every exec until policy is loaded
>> this is equivalent to having the init profile doing pux
>>
>> this is problematic unless the init profile has a defined attachment
>> so that it can keep the init processes in the init profile if it
>> re-execs itself. Also note we can not arbitrarily change what this
>> attachment is at boot. It is possible to have the attachment based on
>> a name and change it at boot time, but the kernel will not have the
>> dfa compiler in it so it is not possible to user regexs as part of
>> the attachment.
>>
>> b. define an attachment for init
>> the attachment needs to be fixed so it is not flexible to different
>> systems. While the attachment could be defined as a grub boot option
>> it could not use regexes/globbing (so exactly one name).
>>
>> c. once default is loaded
>> in this case init is aliased to the default profile until a new
>> default profile is loaded, at which point the new default takes over.
>>
>> This is different from a in that all the early processes are confined
>> by the init profile, and default is only different once a new one is
>> loaded. This is also racy as to what processes receive a give
>> confinement unless the policy load is ordered and other boot
>> processes wait on it
>>
>> d. A variation of c
>> Except default doesn't have to be loaded, it will go into effect as
>> soon as the first profile is loaded. I would assume it would start
>> out as just the default (unconfined) {} profile.
>>
>> e. build init, and default as actual profiles and compile into the
>> kernel (I am not found of this)
>>
>> The problem with confining early boot and having init have a different
>> profile is that you need a way to identify which processes should be
>> confined by which profiles. The best way to do this is an early policy
>> load, any other solution is somewhat hackish. Splitting the init and
>> default profiles has some merit but the question is it enough for the
>> extra effort. As without a defined early policy we are very limited
>> in how processes are divided between separate init and default
>> profiles.
>>
>> Aside: the problem with a default profile with broad attachment specification
>> It is possible to define a fixed broad attachment specification for the
>> original default profile, but there is no point as having init use
>> a specific attachment or pux will have the same results. And the broad
>> attachment breaks pix
>>
>> If the default profile is define like
>> profile default /** {
>>
>> }
>>
>> then it will break the fallback in pix rules, where a profile may want
>> to transition to itself if an application profile is not defined,
>> because there is always a profile defined (ie the p portion of the
>> specification always finds a profile and the ix fallback never happens).
>
> I get what you're saying but the default_policy that U/ux is
> *different* than a profile with overly-wide attachment rules. We can
> say that U/ux is equivalent to P/px -> default_policy, *except* that
> it's not when it comes to Pux (at least not in the existing language
> and implementation); there is no P/px -> self, default_policy.
>
>
> I think the problem with all the above is that you're still conflating
> the initial_policy that is applied to init with the default_policy, an
Sigh, no sorry I guess I wasn't clear enough. I really tried to make that
split and provide several options for doing so while pointing out the
issues involved. What I did do was mix the discussion of the split and
implementation, because implementation issues have a very real impact
on what is chosen.
The first part of the confined init discussion with parts 1-6 where
purposely just about dealing with init. The second part default profile
with parts a-e where about dealing with the default profile
So for example if we chose to implement things via 1. Then there does
not need to be a "split". The policy provided in early boot determines
whether init gets its own profile or the "default" profile. If init is
given the default profile then it will be replaced just like every
other task in the default profile when profile replacement happens.
c and d) However are very much in the vein of what you propose where
init and default are separate.
I think the difference is an implementation pov mixed in. That is there
are two profiles at the start (not one). They both exist in an unconfined
state and can be replaced separately.
The difference between c and d is when the switch from using the init
profile to the default profile occurs. Conceptually you can think of
them as the same profile except for the tracking of tasks, for replacement
it is very important to know which tasks are under which profile.
In c the split happens when a new default profile is loaded. In d the
split happens as soon as any policy loaded.
> attribute of the 'default' or 'system' namespace. (I grant that I too
> didn't fully see the distinction, and didn't consider it an attribute
> of the namespace, until halfway through responding to this email.)
>
err I am not sure I follow, but I assume that you mean that init and
default profiles are per namespace, which is sort of true. The default
profile must exist per namespace. The init would depend on what we
choose, it is fully possible to load profiles into a namespace on
setup so that namespaces other than the root do not have to setup
and init profile, unless they want one (like with a container acting
as a VM)
> So let me try to unconflate them even further:
>
> - When we initially boot, we have one profile in the system namespace
> (there are no other namespaces at this time), let's name it
> 'initial_policy'.
>
yep but uh how about root namespace (system works too I am just
emphasizing the hierarchical nature). And we have to make sure we
can setup a subnamespace such that it looks and behaves like the
root namespace (which they do once you transition into them).
> - This 'initial_policy' starts out in unconfined mode. It can be
> replaced.
>
yep
> - The default_policy attribute of the system namespace *points to*
> the profile 'initial_policy'. *Initially*. But we could allow
yep
> an administrator to change this attribute after boot to point to
> something besides 'initial_policy', like an administrator defined
> profile named 'my_nifty_default_policy'. This is a different action
> than replacing 'initial_policy'.
>
I would in fact rather avoid this as its introducing another knob for
policy control that can be done with replacement as long as the "default"
profile has a well known name.
Is all that the administrator has to do is replace that profile. If the
administrator wants a different name he can define it as a boot arg
or use renaming replace.
Renaming replace does replacement and rename at the same time, and avoids
some of the race problems of set profile.
The issue with the default and init profiles having different names could
be solved by having the default profile be an alias to the init profile
until it is replaced
this is slightly different than you propose, with the difference being
whether we introduce a new knob
> Note that changing the default_policy namespace attribute would not
> change the existing attachment on init and it's children, which have
> 'initial_policy' still applied, only future U/ux transitions would
right
> be affected (though that's one of the reasons why it's important
> to define whether unconfined mode implies '/** pix' or '/** pux').
yes
> Of course, 'initial_policy' can be reloaded/modified as well,
> such that it has '/bin/init ix, /** pux' so that re-execing itself
> will keep 'initial_policy' applied but 'my_nifty_default_policy'
> will get applied to anything else it execs. All this could occur
> before or after your initrd completes.
>
yes
> Does that help at all?
>
sure it cleared up how you are seeing it
> Now I grant that we don't really have anything toolwise that works
> on a namespace independently; because they were grafted on, they've
> always been tightly coupled to the policies that make them up.
>
yes they are, though there has always been plans to add additional
controls to them
> (My only hesitation is the issue cboltz raised, that if you configure
> your default_policy too restrictively, you may not be able to complete
> booting, and you wouldn't have a kernel command line argument to
> tweak to not change the default_policy to 'my_nifty_default_policy'.)
>
sure you can at least if its a separate profile name that aliases init
at first. you can also boot with apparmor=0 if you really need
>> Choosing a name
>>
>> IFF we choose to split the init and default profile then I think the
>> names should be
>> init and default (or maybe system)
>>
>> otherwise
>> system seems to make sense
>
> I think getting the concepts nailed down first is more important than
> the specifics of the names.
>
yep
More information about the AppArmor
mailing list