Nuts and Bolts Admin Story: SELinux Policies Lead image: Lead Image © Agustin Paz, 123RF.com
Lead Image © Agustin Paz, 123RF.com
 

Setting up SELinux policies

Save the Kittens

Writing custom SELinux policy modules is not hard with some basic knowledge of SELinux. We show you how to distribute those modules to all the machines in your own system landscape. By Thorsten Scherf

Following a public appeal [1], you should not resolve problems with SELinux by simply disabling the security mechanism. A better approach is to analyze the problem and write an appropriate ruleset. This approach is also recommended if you want to place your own program under the SELinux shield. Again, some analysis of the application is necessary before you set about developing an appropriate policy.

For the first of these cases, it makes sense to run ausearch and audit2allow to analyze the SELinux-related problems that occur in an application and then to bundle the necessary rulesets into your own policy module. Depending on the nature of the problem, the existing policy for the application might contain a bug. In this case, you should file a bug report with the appropriate policy distributor. In the short term, of course, you can fix the problem using your own policy module, but that would be like advocating reinvention of the wheel.

My Policies

The second case, in which no policy yet exists for the application, requires some extra work. To begin, you need to design a suitable framework for the new policy. You can do this manually or with the aid of a tool, such as sepolgen. In an iterative process, the policy then needs to be optimized. The topic of policy development is well beyond the scope of this article, however, especially considering that plenty of literature exists on the subject [2]. Instead, I will be looking at the options for distributing a new policy.

In both of the cases I've mentioned, the development process results in a new policy package. The package uses a binary format and must be loaded into the kernel security server on the target systems using semodule. A global policy file is composed from all the active modules and a basic policy and is then loaded into the security server. This process is transparent for the user who only needs to load the module to enable the changes defined in it.

On a Fedora system, the existing SELinux policy modules reside in the directory /usr/share/selinux/targeted/. If you are using the MLS policy, instead of the usual targeted policy, its modules are, of course, located in the /usr/share/selinux/mls/ directory. If you look closely at the size of the modules, you can see that the base module is by far the largest, weighing in at more than 200KB.

When a module is loaded, it is copied to /etc/selinux/targeted/modules/active/modules/. When the system is rebooted, the modules in this directory are reloaded into the kernel. The above-mentioned global policy, which is composed of the individual modules, resides in the /etc/selinux/targeted/policy/ directory. You can see from the file's Modify timestamp that it changes whenever a module is loaded or unloaded.

The process for generating the new file, and then uploading the modified version to the security server kernel, looks like this:

If this process needs to take place on a large number of systems, copying the binary policy file multiple times and loading it manually is very inconvenient. Instead, the modules should be implemented as RPM packages on the systems. This method gives you a clean approach to installing and uninstalling modules and also offers the advantage of versioning.

As I mentioned previously, you need to distinguish between two types when developing RPM packages for policy modules. For modules that resolve problems with an existing policy, you might want to develop your own RPM for this local ruleset. For a completely new policy that belongs to a specific application, you can either distribute the policy with the application RPM or create a standalone RPM. Both have advantages and disadvantages. In my experience, a single specfile that contains a main RPM for the application itself and a sub-RPM for the associated policy is the best approach.

The specfile for the RPM must be built so that all the necessary steps for generating the binary policy happen automatically when generating the RPM. Additionally, you need to write some scriptlets to make sure that the module really is loaded on the target system when the package is installed and to ensure that it's really unloaded during the uninstall. After that, you just need to add a digital signature to the RPM before distributing it to the desired systems. Various tools are available for doing so. For example, Pulp, Fedora's Spacewalk, Red Hat's Satellite Server, or even a standalone software repository are all useful choices.

Packaged

Listing 1 shows an example of an RPM specfile for an SELinux policy module that adds more rules to an existing policy. If you want to package the policy along with an application, you need to extend the specfile accordingly. If you want to make sure the new module works with the targeted policy and is also capable of running under the MLS policy, for example, you would need to expand the selinux_pol macro in the specfile and then modify the specfile itself so the module is copied to the correct directories at the appropriate places by iterating through a loop with the macro.

Listing 1: custom-selinux-policy.spec

01 %global selinux_pol targeted
02
03 Name:           custom-selinux-policy
04 Version:        1.0
05 Release:        1%{?dist}
06 License:        GPL v2 or later
07 Source0:        local.te
08 Source1:        local.fc
09 Source2:        local.if
10 Group:          Development/Tools
11 Summary:        Custom SELinux policy module
12 BuildRoot:      %{_tmppath}/%{name}-%{version}-build
13 BuildArch:      noarch
14 BuildRequires:  checkpolicy, selinux-policy
15 Requires:       selinux-policy-targeted
16
17 %description
18 Custom SELinux policy module.
19
20 %prep
21 if [ ! -d custom-selinux-policy ]; then
22   mkdir custom-selinux-policy
23 fi
24 cp -p %{SOURCE0} %{SOURCE1} %{SOURCE2} custom-selinux-policy
25
26 %build
27 cd custom-selinux-policy
28 make -f /usr/share/selinux/devel/Makefile
29
30 %install
31 install -d %{buildroot}%{_datadir}/selinux/%{selinux_pol}
32 install -p -m 644 custom-selinux-policy/local.pp %{buildroot}%{_datadir}/selinux/%{selinux_pol}/local.pp
33
34 %post
35 /usr/sbin/semodule -i \
36    %{_datadir}/selinux/%{selinux_pol}/local.pp &> /dev/null || :
37
38 %postun
39 if [ $1 -eq 0 ] ; then
40   /usr/sbin/semodule -r local &> /dev/null || :
41 fi
42
43 %clean
44 rm -rf %buildroot
45
46 %files
47 %defattr(-,root,root)
48 %{_datadir}/selinux/*/local.pp
49
50 %changelog
51 * Mon May 20 2013 Thorsten Scherf <tscherf@redhat.com>
52 - initial version

Running

rpmbuild -ba custom-selinux-policy

finally gives you the source and binary RPMs. You need the appropriate development tools in place on the build host. Scriptlets in the RPM specfile ensure that the policy module is loaded automatically when you install the RPM, and unloaded again if the latest version of the RPM is deleted.

I hope this article will help reduce the SELinux switch-off quota. If not, just remember: Every time someone sets setenforce 0, one kitten dies and Daniel Walsh cries [3].