Replacing and updating existing SELinux policies

July 04, 2021

When we replace or update existing policies, we need to load them using the semodule commands, as shown in the Handling policy modules section. But how do we create or update the policies, exactly? Let’s consider a few use cases where SELinux policy adjustments are triggered.

Creating policies using audit2allow

When SELinux prevents certain actions, we know it will log the appropriate denial (assuming no dontaudit statements are defined) in the audit logs. This denial can be used as the source to generate a custom SELinux policy that allows the activity.

Consider the following denial, which occurred when a confined user called su to switch to the root user:

type=AVC msg=audit(...): avc: denied { write } for pid=58002 comm="su" name="btmp" dev="vda1" ino=4213650 scontext=staff_u:staff_r:staff_t:s0 tcontext=system_u:object_r:faillog_t:s0 tclass=file permissive=0

If we are certain that these operations need to be granted, then we can use the audit2allow command to generate a policy module for us that allows these activities. The audit2allow application transforms a denial (or set of denials) into SELinux allow rules. These rules can then be saved in a file, ready to be built into an SELinux policy module, which can then be loaded.

To generate SELinux policy allow rules, pipe the denials through the audit2allow application:

# grep btmp /var/log/audit/audit.log | audit2allow
#============ staff_t ============
allow staff_t faillog_t:file write;

Based on the denials, audit2allow prepared an allow rule. We can also ask audit2allow to immediately create an SELinux policy module:

# grep btmp /var/log/audit/audit.log | audit2allow -M localpolicy
************ IMPORTANT ************
To make this policy package active, execute:
semodule -i localpolicy.pp

A file called localpolicy.pp will be available in the current directory, which we can load using the semodule command. The source file will also be present, named localpolicy.te.

If the denials that occurred are considered cosmetic in nature (meaning that the system functions as expected and the denials should not cause any updates on the policy), you can use audit2allow to generate dontaudit rules rather than allow rules. In that case, the denials will no longer be visible in the audit logs, while still preventing the actions from taking place:

# grep btmp /var/log/audit/audit.log | audit2allow -D -M localpolicy

It is likely, after including the necessary rules, that the action will result in more denials that were not previously triggered. As long as the previous AVC denials are still available in the audit logs, it is sufficient to regenerate the policy and continue. After all, audit2allow will consider all AVC denials that it encountered, regardless of the current SELinux policy state.

Another popular approach is to put the system (or the application domain) in permissive mode to generate and fill up the audit logs with all the AVC denials related to the action. Although this generates more AVC denials to work with, it could also result in wrong decisions by the audit2allow command. Hence, always verify the denials before generating new policy constructs, and review the generated policy to make sure that it will enforce the right set of access controls and not grant more privileges than needed.

When the previous AVC denials are no longer available inside the audit log, a new policy module will need to be generated, as otherwise the previously fixed accesses will be denied again: the newly generated policy will no longer contain the allow rules from before, and when we load the new policy, the old policy is no longer active.

Using sensible module names

In the previous section, we used the audit2allow command to generate a policy module named localpolicy. However, this name does not reveal what the purpose of the module is.

Once we create a (binary) policy (such as the localpolicy.pp file) and load it, it is not always clear to the administrators and users at first glance what this module is meant to accomplish. Although it is possible to unpack the .pp file (using semodule_unpackage) and then disassemble the resulting .mod file into a .te file, it requires software not available on most distributions (the dismod application, for instance, part of the checkpolicy software, is often not included). Considering that we just want to get insights into the rules that are part of a module, this is a very elaborate and time-intensive approach.

The content of a module can also be somewhat deduced from its CIL code. For instance, an active screen module will have its code available at /var/lib/selinux/targeted/active/modules/100/screen, in a file called cil. On some distributions, this file will be a compressed file, so you might need to unzip it before viewing:

# file screen/cil
cil: bzip2 compressed data, block size = 500k
# bzcat screen/cil
(typealias secadm_screen_home_t
...

Still, having to dive into the rules to know what localpolicy is about is not only very cumbersome, but also requires sufficient privileges to be able to read these files.

Instead, it is a best practice to name the generated modules for their intended purposes. An SELinux policy that fixes a few AVC denials that come up when su executes from within the staff_t domain would be best named custom_staff_su_faillog, for instance.

It is also recommended to prefix (or suffix) the custom policies, so they can be more easily found:

# semodule -l | grep ^custom_
custom_staff_su_faillog

This identifies that the policy module has been added by the administrator (or organization) and is not sourced from the default Linux distribution’s policy.

Generating reference policy style modules with audit2allow

The reference policy project provides distributions and policy writers with a set of functions that simplify the development of SELinux policies. As an example, let’s see what the reference policy functions (called macros) can do with the su situation:

# grep btmp /var/log/audit/audit.log | audit2allow -R
require {
 type staff_t;
}
#============ staff_t ============
auth_rw_faillog(staff_t)

The rule in the example is auth_rw_faillog(staff_t). This is a reference policy macro that explains an SELinux rule (or set of rules) in a more human-readable way. In this case, it allows the staff_t domain to read/write on faillog_t labeled resources. The faillog_t type is part of the system authentication SELinux policy (as suggested by the auth_ prefix, which identifies the source SELinux policy module).

Note:

As audit2allow -R uses an automated approach for finding potential functions, we need to review the results carefully. Sometimes it selects a method that creates far more privileges for a domain than needed.

All major distributions base their SELinux policies upon the macros and content provided by the reference policy. The list of methods we can call while building SELinux policies is available on the local filesystem, at /usr/share/doc/selinux-policy/html.

These named methods bundle a set of rules related to the functionality that SELinux policy administrators want to enable. For instance, the storage_read_tape() method allows us to enhance an SELinux policy, providing a given domain with read access to tape storage devices.

Building reference policy – style modules

If we generate an SELinux policy using reference policy macros but do not have access to the binary policy module anymore, then we need to build the policy before loading it. CIL-based policies can be loaded directly, which is why this book uses CIL as much as possible. However, given the wide use of the reference policy, knowing how to build these modules is important as well.

Suppose that the reference policy-based SELinux policy code resides in a file called custom_staff_su_faillog.te, then we can build it into a .pp file as follows:

# make -f /usr/share/selinux/devel/Makefile custom_staff_su_faillog.pp
Compiling targeted custom_staff_su_faillog.pp policy package
rm tmp/custom_staff_su_faillog.mod tmp/custom_staff_su_faillog.mod.fc

Once built, we can load it using semodule. Every time we change the policy code (in the .te file) or other policy information (such as file context definitions in the .fc file), we need to rebuild the .pp file before we can load it.

Building legacy-style modules

If we ask audit2allow to generate the policy rules without using reference policy style macros (which we call a legacy-style SELinux policy), then building the .pp file from it requires a different approach.

Suppose we have the .te file as generated by audit2allow -M, but not the .pp file, then we can generate it as follows:

  • First, create the .mod file using checkmodule:
# checkmodule -M -m -o custom_nonrefpol.mod \
 custom_nonrefpol.te
  • Next, generate the .pp file using semodule_package:
# semodule_package -o custom_nonrefpol.pp \
 -m custom_nonrefpol.mod

If an .fc file (which contains file context definitions) is present, use the -f option:

# semodule_package -o custom_nonrefpol.pp \
 -m custom_nonrefpol.mod -f custom_nonrefpol.fc

The audit2allow command will automatically execute these commands, so this is only needed if the .pp file is no longer present, or when these more legacy-style SELinux policies are shared with you and you need to build and load them manually.

Replacing the default distribution policy

When adding custom SELinux policies, all that users can do is to add more allow rules. SELinux does not have a deny rule that can be used to remove currently allowed access rules from the active policy.

If the current policy is too permissive for the administrator’s liking, then the administrator will need to update the policy rather than just enhance it. That implies that the administrator has access to the current SELinux policy rules in use.

To replace an active SELinux policy, most Linux distributions allow you to get the source code of the policy. For instance, for RPM-based Linux distributions, the source RPM of the SELinux policy package can be downloaded and unpacked to gain access to the policy as follows:

  • First, find out what the current version of the SELinux policy is:
$ rpm -qi selinux-policy
Name		: selinux-policy
Version	: 3.14.3
...
Source RPM	: selinux-policy-3.14.3-20.el8.src.rpm
...
  • Next, try to obtain the source RPM shown in the output. Source RPMs can also be downloaded from third-party repositories. If the package is difficult to find, you can try to find it through https://rpmfind.net.
  • Next, use the rpmbuild utility to extract the source RPM:
$ rpmbuild --rebuild --nobuild \
 selinux-policy-3.14.3-20.el8.src.rpm

This will unpack the source RPM in the ~/rpmbuild directory.

  • When finished, the SELinux policy source code can be found inside ~/rpmbuild/SOURCES and is probably named selinux-policy-9c02e99.tar.gz or similar, which you can extract further:
$ tar xvf selinux-policy-9c92e99.tar.gz
$ tar xvf selinux-policy-contrib-c8ebb9f.tar.gz

The SELinux policy can then be found in the created subdirectories. For instance, the screen.te file can be found in the ./selinux-policy-contrib-c8ebb*/policy/modules/contrib subdirectory.

The policy files can now be safely copied over, manipulated at will, and built to replace the existing policy. If we load the updated SELinux policy module with the same (or higher) priority as the already loaded policy, it will take precedence in the policy.

Most distributions will also have their active SELinux policy available through an online source-controlled repository. For instance, the current SELinux policy for CentOS is available at https://github.com/fedora-selinux/selinux-policy.

Related Articles

How to add swap space on Ubuntu 21.04 Operating System

How to add swap space on Ubuntu 21.04 Operating System

The swap space is a unique space on the disk that is used by the system when Physical RAM is full. When a Linux machine runout the RAM it use swap space to move inactive pages from RAM. Swap space can be created into Linux system in two ways, one we can create a...

read more

Lorem ipsum dolor sit amet consectetur

0 Comments

Submit a Comment

Your email address will not be published. Required fields are marked *

5 + nine =