Selinux Mystery
Let me set the stage. You’ve just installed RHEL7 on a workstation and applied a minimal base configuration but leave the SELinux policy untouched. You hand it off to the user and they immediately come back unable to log in. They say they’re able to “log in” but then they get kicked back to the display manager. Classic case of oddjob-mkhomedir failing.
So we check it out in the journal and find
SELinux is preventing /usr/libexec/oddjob/mkhomedir from write access on the directory /home
Odd, right? You would think that would be the only thing it’s allowed to access. Let’s check out /home and see what’s up.
s -ldZ /home
drwxr-xr-x. root root system_u:object_r:default_t:s0
So far no big deal, we’ll just clean up these contexts and everything will be just fine.
semanage fcontext --list | home
/home/(.*/)?\.snapshots(/.*)? all files system_u:object_r:snapperd_data_t:s0
/opt/NX/home(/.*)? all files system_u:object_r:nx_server_var_lib_t:s0
/usr/NX/home(/.*)? all files system_u:object_r:nx_server_var_lib_t:s0
/home/\.snapshots(/.*)? all files system_u:object_r:snapperd_data_t:s0
/opt/NX/home/nx/\.ssh(/.*)? all files system_u:object_r:nx_server_home_ssh_t:s0
/usr/NX/home/nx/\.ssh(/.*)? all files system_u:object_r:nx_server_home_ssh_t:s0
/var/lib/nxserver/home/.ssh(/.*)? all files system_u:object_r:nx_server_home_ssh_t:s0
/var/lib/containers/home(/.*)? all files system_u:object_r:openshift_var_lib_t:s0
/var/lib/nxserver/home/\.xauth.* regular file system_u:object_r:xauth_home_t:s0
/var/lib/nxserver/home/\.Xauthority.* regular file system_u:object_r:xauth_home_t:s0
/var/home = /home
/var/lib/xguest/home = /home
I’m pretty much out of ideas. Is there a way to ask SELinux if it thinks that
default_t
is the correct type for this file? After searching around the answer
was yes.
# Available in libselinux-utils.
matchpathcon /home
/home system_u:object_r:default_t:s0
So SELinux thinks that the correct type for this directory should be default_t
.
This has to be a bug. But I need some actual evidence that the policy is wrong
or corrupted.
rpm -ql selinux-policy-targeted | grep home
/etc/selinux/targeted/active/homedir_template
/etc/selinux/targeted/contexts/files/file_contexts.homedirs
/etc/selinux/targeted/contexts/files/file_contexts.homedirs.bin
So I just try opening that .homedirs
file.
less /etc/selinux/targeted/contexts/files/file_contexts.homedirs
#
#
# User-specific file contexts, generated via libsemanage
# use semanage command to manage system users to change the file_context
#
#
#
# Home Context for user user_u
#
/var/home/[^/]+/.+ unconfined_u:object_r:user_home_t:s0
/var/home/[^/]+/.maildir(/.*)? unconfined_u:object_r:mail_home_rw_t:s0
/var/home/[^/]+/.*/plugins/nppdf\.so.* -- unconfined_u:object_r:textrel_shlib_t:s0
/var/home/[^/]+/((www)|(web)|(public_html))(/.+)? unconfined_u:object_r:httpd_user_content_t:s0
/var/home/[^/]+/((www)|(web)|(public_html))/cgi-bin(/.+)? unconfined_u:object_r:httpd_user_script_exec_t:s0
/var/home/[^/]+/((www)|(web)|(public_html))(/.*)?/\.htaccess -- unconfined_u:object_r:httpd_user_htaccess_t:s0
/var/home/[^/]+/((www)|(web)|(public_html))(/.*)?/logs(/.*)? unconfined_u:object_r:httpd_user_ra_content_t:s0
/var/home/[^/]+/a?quota\.(user|group) -- unconfined_u:object_r:quota_db_t:s0
...
/var/home
? I mean that’s where we put our local users but SELinux doesn’t seem
like it would know anything about that. Also, this file is generated? By what?
Some more searching reveals that it’s /sbin/genhomedircon
.
ls -l /sbin/genhomedircon
lrwxrwxrwx. 1 root root 8 Aug 2 06:52 "/sbin/genhomedircon" -> "semodule"
Interesting, so semodule is one of those two-faced binaries that does different things depending on what you call it. Realistically, it has to be getting /var/home from some config file that we added, let’s just see what files it opens and go from there.
strace -e open /sbin/genhomedircon
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libsepol.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libsemanage.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libaudit.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libbz2.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libustr-1.0.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap-ng.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/etc/selinux/config", O_RDONLY) = 3
open("/etc/selinux/semanage.conf", O_RDONLY) = 3
open("/etc/selinux/targeted/semanage.trans.LOCK", O_RDONLY) = 3
open("/etc/selinux/targeted/active/modules/100/abrt/cil", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/abrt/cil.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/abrt/hll", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/abrt/hll.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/abrt/lang_ext", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/abrt/lang_ext.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/accountsd/cil", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/accountsd/cil.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/accountsd/hll", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/accountsd/hll.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/accountsd/lang_ext", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/accountsd/lang_ext.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/acct/cil", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/acct/cil.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/acct/hll", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/acct/hll.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
open("/etc/selinux/targeted/active/modules/100/acct/lang_ext", O_RDONLY) = 4
open("/etc/selinux/targeted/tmp/modules/100/acct/lang_ext.tmp", O_WRONLY|O_CREAT|O_TRUNC, 0100644) = 5
...
Okay, it’s probably not anything we changed in /etc/selinux
so let’s ignore those.
strace -e open /sbin/genhomedircon 2>&1 | grep -vF '/etc/selinux'
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libsepol.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libselinux.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libsemanage.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpcre.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libdl.so.2", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libaudit.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libbz2.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libustr-1.0.so.1", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libpthread.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libcap-ng.so.0", O_RDONLY|O_CLOEXEC) = 3
open("/proc/meminfo", O_RDONLY|O_CLOEXEC) = 5
open("/etc/default/useradd", O_RDONLY) = 5
open("/etc/libuser.conf", O_RDONLY) = 5
open("/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 5
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 5
open("/lib64/libnss_files.so.2", O_RDONLY|O_CLOEXEC) = 5
open("/etc/passwd", O_RDONLY|O_CLOEXEC) = 5
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=19468, si_uid=0, si_status=0, si_utime=11, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=19469, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=19470, si_uid=0, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++
One of these things is definitely not like the others: /etc/default/useradd
. If
you’re not familiar, this file is just a small bash-like file that’s sourced by
useradd to set some default options.
On the surface this has nothing to do with SELinux. But apparently
genhomedircon is using the HOME
variable as the base directory for that
.homedirs policy. Let’s change it and see what happens.
sed -i 's:/var/home:/home:' /etc/default/useradd
genhomedircon
restorecon -Rv /home
...
ls -ldZ /home
drwxr-xr-x. root root system_u:object_r:home_root_t:s0 /home
Boom!
I have to admit that this was a fun rabbit hole to go down, but it’s a pretty
good example of magic leading to unexpected behavior. Admins are typically told
that the most maintainable way to add additional home directory locations is by
defining an equivalence to /home
– which breaks if you make the mistake of
setting that as the default for new users.