[apparmor] "default"/"system" profile
John Johansen
john.johansen at canonical.com
Thu May 16 00:13:15 UTC 2013
So this is a new attempt to frame the default/init/system profile discussion
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.
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>)
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
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).
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.
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)
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)
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
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.
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.
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.
2. special case init and children in the kernel code
- No, just no.
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).
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
More information about the AppArmor
mailing list