Sometimes, especially when users are converting their systems to be
SELinux-enabled, their user context is wrong. An example would be when,
after logon (in permissive mode), the user is in the
system_u:system_r:local_login_t
domain instead of a user domain like
staff_u:staff_r:staff_t
.
So, how does a login get its SELinux user context?
Let's look at the entire chain of SELinux context changes across a boot.
At first, when the system boots, the kernel (and all processes invoked
from it) run in the kernel_t
domain (I'm going to ignore the other
context fields for now until they become relevant). When the kernel
initialization has been completed, the kernel executes the init
binary. When you use an initramfs, then a script might be called. This
actually doesn't matter that much yet, since SELinux stays within the
kernel_t
domain until a SELinux-aware init is launched.
When the init binary is executed, init of course starts. But as
mentioned, init is SELinux-aware, meaning it will invoke SELinux-related
commands. One of these is that it will load the SELinux policy (as
stored in /etc/selinux
) and then reexecute itself. Because of that,
its process context changes from kernel_t
towards init_t
. This is
because the init binary itself is labeled as init_exec_t
and a
type transition is defined from kernel_t
towards init_t
when
init_exec_t
is executed.
Ok, so init now runs in init_t
and it goes on with whatever it
needs to do. This includes invoking init scripts (which, btw, run in
initrc_t
because the scripts are labeled initrc_exec_t
or with a
type that has the init_script_file_type
attribute set, and a
transition from init_t
to initrc_t
is defined when such files are
executed). When the bootup is finally completed, init launches the
getty processes. The commands are mentioned in /etc/inittab
:
$ grep getty /etc/inittab
c1:12345:respawn:/sbin/agetty --noclear 38400 tty1 linux
c2:2345:respawn:/sbin/agetty 38400 tty2 linux
...
These binaries are also explicitly labeled getty_exec_t
. As a result,
the getty (or agetty) processes run in the getty_t
domain
(because a transition is defined from init_t
to getty_t
when
getty_exec_t
is executed). Ok, so gettys run in getty_t
. But what
happens when a user now logs on to the system?
Well, the getty's invoke the login binary which, you guessed it
right, is labeled as something: login_exec_t
. As a result (because,
again, a transition is defined in the policy), the login process runs as
local_login_t
. Now the login process invokes the various PAM
subroutines which follow the definitions in /etc/pam.d/login
. On
Gentoo systems, this by default points to the system-local-login
definitions which points to the system-login
definitions. And in this
definition, especially under the sessions section, we find a reference
to pam_selinux.so
:
session required pam_selinux.so close
...
session required pam_selinux.so multiple open
Now here is where some of the magic starts (see my post on Using
pam_selinux to switch
contexts
for the gritty details). The methods inside the pam_selinux.so
binary
will look up what the context should be for a user login. For instance,
when the root user logs on, it has SELinux checking what SELinux user
root is mapped to, equivalent to running semanage login -l:
$ semanage login -l | grep ^root
root root
In this case, the SELinux user for root is root, but this is not always the case (that login and user are the same). For instance, my regular administrative account maps to the staff_u SELinux user.
Next, it checks what the default context should be for this user. This
is done by checking the default_contexts
file (such as the one in
/etc/selinux/strict/contexts
although user-specific overrides can be
(and are) placed in the users
subdirectory) based on the context of
the process that is asking SELinux what the default context should be.
In our case, it is the login process running as local_login_t
:
$ grep -HR local_login_t /etc/selinux/strict/contexts/*
default_contexts:system_r:local_login_t user_r:user_t staff_r:staff_t sysadm_r:sysadm_t unconfined_r:unconfined_t
users/unconfined_u:system_r:local_login_t unconfined_r:unconfined_t
users/guest_u:system_r:local_login_t guest_r:guest_t
users/user_u:system_r:local_login_t user_r:user_t
users/staff_u:system_r:local_login_t staff_r:staff_t sysadm_r:sysadm_t
users/root:system_r:local_login_t unconfined_r:unconfined_t sysadm_r:sysadm_t staff_r:staff_t user_r:user_t
users/xguest_u:system_r:local_login_t xguest_r:xguest_t
Since we are verifying this for the root SELinux user, the following
line of the users/root
file is what matters:
system_r:local_login_t unconfined_r:unconfined_t sysadm_r:sysadm_t staff_r:staff_t user_r:user_t
Here, SELinux looks for the first match in that line that the user has access to. This is defined by the roles that the user is allowed to access:
$ semanage user -l | grep root
root staff_r sysadm_r
As root is allowed both the staff_r and sysadm_r roles, the
first one found in the default context file that matches will be used.
So it is not the order in which the roles are displayed in the
semanage user -l output that matters, but the order of the contexts
in the default context file. In the example, this is
sysadm_r:sysadm_t
:
system_r:local_login_t unconfined_r:unconfined_t sysadm_r:sysadm_t staff_r:staff_t user_r:user_t
<-----------+-----------> <-------+-------> <------+------> <-----+----->
`- no matching role `- first (!) `- second `- no match
Now that we know what the context should be, this is used for the
first execution that the process (still login) will do. So login
changes the Linux user (if applicable) and invokes the shell of that
user. Because this is the first execution that is done by login, the
new context is set (being root:sysadm_r:sysadm_t
) for the shell.
And that is why, if you run id -Z, it returns the user context
(root:sysadm_r:sysadm_t
) if everything works out fine ;-)