Rotors of M-209 cipher machine.

SELinux Policy Management

Managing SELinux Booleans and Creating Custom Policy Modules

Download Oracle Linux

Sometimes you have a problem with a Linux system that makes you say: "This is so strange, it should be working! The ownerships and permissions are correct. No helpful error or warning messages appear on the command line. What could be wrong?"

The answer in such a situation is often that Security-Enhanced Linux, or simply SELinux, is stopping something.

It isn't silent interference. All this is being logged. But the log is in a very verbose and hard to understand format, stored in a file that only root can read.

You may be able to fix the problem easily by toggling one or more SELinux boolean settings. Or, you may need to create and load a custom SELinux policy module. Or, as in this case, a little of each.

It isn't difficult to diagnose the problem and fix what's needed, let's see how. This example fixes a problem you encounter when trying to use the pam_yubico.so PAM module to authenticate with a YubiKey on a hardened RHEL 8 server.

The Problem Scenario

Configuring YubiKey Authentication with pam_yubico.so

Let's say you want to use Yubikey to do multi-factor authentication. However, Red Hat no longer includes the pam_pkcs11.so PAM module that was in earlier major releases. They also didn't include the pam_u2f.so module which you might have on other distributions. The RHEL media contains no YubiKey-specific PAM module. However, the EPEL collection, Extra Packages for Enterprise Linux, has packages you can install on RHEL and compatible derivatives including Oracle Linux and Fedora Linux. EPEL includes a pam_yubico package that includes the pam_yubico.so PAM module.

That can work, but you have to solve a two-part SELinux problem. You want to use a PAM module that is no longer a standard component of Red Hat Enterprise Linux, while Red Hat wants you to instead use Red Hat Identity Management or IdM.

If Things Are Strange, Suspect SELinux

I have found that if you run into a strange situation where it seems that something should work but it mysteriously doesn't, it's very likely the fault of SELinux.

Run a simple test. Have one terminal in which you are trying but failing to run something, probably as an unprivileged user. Or perhaps not, SELinux can interfere with root doing things, which is even more mysterious. As root in another terminal, use the setenforce command to switch SELinux to Permissive mode. It will analyze every action, and then if it normally would block the action, it will allow it but log it. All you really need of the following is the second command. The first and last commands show us what happens.

# getenforce
Enforcing
# setenforce Permissive
# getenforce
Permissive

Now return to the test window and try the command again. If it still fails, then the problem isn't SELinux. But if it works with SELinux in Permissive mode, we're on the track to solve this quickly.

Switch back to the root window and truncate the audit log:

# > /var/log/audit/audit.log

Re-run the problematic commands in the testing window. If there are multiple ones, run them all. Then switch back to the root window and save the small audit log that will contain just the events that were flagged for denial but permitted.

# cp /var/log/audit/audit.log .

Let's use the audit2allow command to see what changes would be required to allow those commands to run even when SELinux is in Enforcing mode. "AVC" stands for Access Vector Cache, it's where SELinux stores information about its decisions to allow and deny access. Notice that three blocked operations can be fixed by toggling the state of one SELinux boolean.

# audit2allow < audit.log

#============= staff_sudo_t ==============

#!!!! This avc can be allowed using the boolean 'authlogin_yubikey'
allow staff_sudo_t auth_home_t:dir { add_name read remove_name write };

#!!!! This avc can be allowed using the boolean 'authlogin_yubikey'
allow staff_sudo_t auth_home_t:file { create rename setattr unlink write };
allow staff_sudo_t udev_var_run_t:file { getattr open read };

#============= sysadm_su_t ==============
allow sysadm_su_t auth_home_t:dir { add_name read remove_name write };
allow sysadm_su_t auth_home_t:file { create getattr open read rename setattr unlink write };
allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt };
allow sysadm_su_t udev_var_run_t:file { getattr open read };
allow sysadm_su_t usb_device_t:chr_file { ioctl open read write };
allow sysadm_su_t wtmp_t:file write;

#============= sysadm_sudo_t ==============

#!!!! This avc can be allowed using the boolean 'authlogin_yubikey'
allow sysadm_sudo_t auth_home_t:dir { add_name read remove_name write };

#!!!! This avc can be allowed using the boolean 'authlogin_yubikey'
allow sysadm_sudo_t auth_home_t:file { create rename setattr unlink write };
allow sysadm_sudo_t udev_var_run_t:file { getattr open read };

Fix What We Can With Existing Booleans

The SELinux policy booleans are pre-defined rules we can switch off and on. We will use those rather than partially reinventing wheels with an overly broad custom policy. Our report told us that just one boolean, authlogin_yubikey, was needed.

We can see that it was off before, and the below sequence changed it to on. The -P option permanently changes the system's policy, so our modification will persist after reboots.

# getsebool authlogin_yubikey
authlogin_yubikey --> off
# setsebool -P authlogin_yubikey on
# getsebool authlogin_yubikey
authlogin_yubikey --> on

That Should Have Improved Things

Now truncate the audit log file again and repeat the experiment.

# > /var/log/audit/audit.log
[... Re-run all the troublesome commands in the user window ...]
# cp /var/log/audit/audit.log .
# audit2allow < audit.log

#============= staff_sudo_t ==============
allow staff_sudo_t udev_var_run_t:file { getattr open read };

#============= sysadm_su_t ==============
allow sysadm_su_t auth_home_t:dir { add_name read remove_name write };
allow sysadm_su_t auth_home_t:file { create getattr open read rename setattr unlink write };
allow sysadm_su_t self:capability dac_override;
allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt };
allow sysadm_su_t udev_var_run_t:file { getattr open read };
allow sysadm_su_t usb_device_t:chr_file { ioctl open read write };
allow sysadm_su_t wtmp_t:file write;

#============= sysadm_sudo_t ==============
allow sysadm_sudo_t udev_var_run_t:file { getattr open read };

That looks good, nothing is left that could be handled by a boolean. Nine things remain to be given special permission with our custom policy module.

Let's Start Buiding A Custom Policy Module

Rerun the audit2allow command with some added parameters:

You don't have to give the same name to the file and the module which it contains, as I do here. However, I would find it confusing if I didn't! The name doesn't matter, as long as it's unique. But again, choose a name that makes sense to yourself.

# audit2allow -r -m mfa_yubikey < audit.log > mfa_yubikey.te
# cat mfa_yubikey.te

module mfa_yubikey 1.0;

require {
	type staff_sudo_t;
	type wtmp_t;
	type usb_device_t;
	type sysadm_sudo_t;
	type auth_home_t;
	type udev_var_run_t;
	type sysadm_su_t;
	class file { create getattr open read rename setattr unlink write };
	class capability dac_override;
	class netlink_kobject_uevent_socket { bind create getattr setopt };
	class dir { add_name read remove_name write };
	class chr_file { ioctl open read write };
}

#============= staff_sudo_t ==============
allow staff_sudo_t udev_var_run_t:file { getattr open read };

#============= sysadm_su_t ==============
allow sysadm_su_t auth_home_t:dir { add_name read remove_name write };
allow sysadm_su_t auth_home_t:file { create getattr open read rename setattr unlink write };
allow sysadm_su_t self:capability dac_override;
allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt };
allow sysadm_su_t udev_var_run_t:file { getattr open read };
allow sysadm_su_t usb_device_t:chr_file { ioctl open read write };
allow sysadm_su_t wtmp_t:file write;

#============= sysadm_sudo_t ==============
allow sysadm_sudo_t udev_var_run_t:file { getattr open read };

That's The Custom Policy Module Source Code, Let's Compile It And Clean Up

Compile the policy module and see what results.

$ make -f /usr/share/selinux/devel/Makefile mfa_yubikey.pp
$ ls -l mfa_yubikey*
-rw-rw-r--. 1 cromwell cromwell    0 Aug 26 14:22 mfa_yubikey.fc
-rw-rw-r--. 1 cromwell cromwell   23 Aug 26 14:22 mfa_yubikey.if
-rw-rw-r--. 1 cromwell cromwell 8833 Aug 26 14:22 mfa_yubikey.pp
-rw-------. 1 cromwell cromwell 1155 Aug 26 14:21 mfa_yubikey.te
$ file mfa_yubikey*
mfa_yubikey.fc: empty
mfa_yubikey.if: SE Linux policy interface source
mfa_yubikey.pp: , created: Thu Jan 1 00:00:15 1970, modified: Tue June 22 05:08:35 2010
mfa_yubikey.te: C++ source, ASCII text
$ cat mfa_yubikey.if
## <summary></summary>

Clean up, we don't need the two trivial files:

$ rm mfa_yubikey.fc mfa_yubikey.if

However, of course you want to keep the source code somewhere safe.

Insert The Custom Policy Module And Test It

Insert the policy module:

$ sudo semodule -v -i mfa_yubikey.pp
Attempting to install module 'mfa_yubikey.pp':
Ok: return value of 0.
Committing changes:
Ok: transaction number 27.

Reboot the system and verify that the changes persist:

# getsebool -a | grep -i yubikey
authlogin_yubikey --> on
# semodules -l | grep -i yubikey
mfa_yubikey

And, of course, test whatever it was that used to be mysteriously broken!

Going Deeper

The audit2allow command will give me policy module source code that solves my problem. However, its solution might be overly generalized. It might allow some events that aren't needed to run the commands of interest, at least not in the ways in which I ran them during the tests. For example, I didn't expect to see this line:

allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt };

I did not expect network socket creation and use to appear. I could have tried commenting out that line, compiling the module without it, and testing to see if the resulting module was permissive enough for what I wanted to do. Or maybe I need that over all line but I could delete some of the actions in the {...} list.

Some people who worked with SELinux at the NSA, where it was initially developed, have told me that NSA staff with more academic interest in SELinux love it because of its auditable fine-grained control.

However, developing a (nearly) complete and (nearly) minimal policy is an enormous job. You may run into weird situations like the ping command checking the attributes of some file under /var/lock. Would it break functionality if SELinux blocked that test?

There's an enormous number of these apparently pointless tests. And so there would be an enormous number of functionality tests to perform. What if now allowing the attribute check did break functionality, but only when attempting esoteric option combinations in infrequently encountered situations?

If so, now there's a judgement call to make — is that obscure and seldom-used functionality critical to the mission?

To the main Security Page