Defining and distributing policies
Enabling SELinux does not automatically start the enforcement of access. If SELinux is enabled and it cannot find a policy, it will refuse to start. That is because the policy defines the behavior of the system (what SELinux should allow). SELinux policies are generally distributed in a compiled form (just like with software) as policy modules. These modules are then aggregated into a single policy store and loaded in memory to allow SELinux to enforce the policy rules on the system.
Note
Gentoo, being a source-based meta-distribution, distributes the SELinux policies as (source) code as well, which is compiled and built at install time, just like it does with other software.
The following diagram shows the relationship between policy rules, policy modules, and a policy package (which is often a one-to-one mapping towards a policy store):
Writing SELinux policies
A SELinux policy writer can (currently) write down the policy rules in three possible languages:
- In standard SELinux source format--a human-readable and well-established language for writing SELinux policies
- In reference policy style--this extends the standard SELinux source format with M4 macros to facilitate the development of policies
- In the SELinux common intermediate language (CIL)--a computer-readable (and with some effort human-readable) format for SELinux policies
Most SELinux supporting distributions base their policy on the reference policy (https://github.com/TresysTechnology/refpolicy/wiki), a fully functional SELinux policy set managed as a free software project. This allows distributions to ship with a functional policy set rather than having to write one themselves. Many project contributors are distribution developers, trying to push changes of their distribution to the reference policy project itself, where the changes are peer-reviewed to make sure no rules are brought into the project that might jeopardize the security of any platform. It easily becomes very troublesome to write reusable policy modules without the extensive set of M4 macros offered by the reference policy project.
The SELinux CIL format is quite recent (RHEL 7.2 does not support it yet), and although it is very much in use already (the recent SELinux user space converts everything in CIL in the background), it is not that common yet for policy writers to use it directly.
As an example, consider the web server rule we discussed earlier, repeated here for your convenience: allow the processes labeled with httpd_t
to bind to TCP ports labeled with http_port_t
.
In the standard SELinux source format, this is written down as follows:
allow httpd_t http_port_t : tcp_socket { name_bind };
Using reference policy style, this rule is part of the following macro call:
corenet_tcp_bind_http_port(httpd_t)
In CIL language, the rule would be expressed as follows:
(allow httpd_t http_port_t (tcp_socket (name_bind)))
In most representations, we can see what the rule is about:
- The subject (who is taking the action); in this case, this is the set of processes labeled with the
httpd_t
type. - The target resource or object (the target for the action); in this case, it is the set of TCP sockets (
tcp_socket
) labeled with thehttp_port_t
type. In reference policy style, this is implied by the function name. - The action or permission; in this case, it is the action of binding to a port (
name_bind
). In reference policy style, this is implied by the function name. - The result that the policy will enforce; in this case, it is that the action is allowed (
allow
). In reference policy style, this is implied by the function name.
A policy is generally written for an application or set of applications. So the preceding example will be part of the policy written for web servers.
Policy writers will generally create three files per application or application set:
- A
.te
file, which contains the type enforcement rules. - An
.if
file, which contains interface and template definitions, allowing policy writers to easily use the newly-generated policy rules to enhance other policies with. You can compare this to header files in other programming languages. - An
.fc
file, which contains file context expressions. These are rules that assign labels to resources on the file system.
A finished policy will then be packaged into a SELinux policy module.
Distributing policies through modules
Initially, SELinux used a single, monolithic policy approach: all possible access control rules were maintained in a single policy file. It quickly became clear that this is not manageable in the long term, and the idea of developing a modular policy approach was born.
Within the modular approach, policy developers can write isolated policy sets for a particular application (or set of applications), roles, and so on. These policies then get built and distributed as policy modules. Platforms that need access controls for a particular application load the SELinux policy module that defines the access rules for that application.
The process of building policy modules is shown in the next diagram. It also shows where CIL comes into play, even when the policy rules themselves are not written in CIL. For distributions that do not yet support CIL, semodule
will directly go from the .pp
file to the policy.##
file.
With the recent SELinux user space, the *.pp
files (which are the SELinux policy modules) are considered to be written in a high-level language (HLL). Do not assume that this means they are human-readable: these files are binary files. The consideration here is that SELinux wants to support writing SELinux policies in a number of formats, which it calls high-level languages, as long as it has a parser that can convert the files into CIL. Marking the binary module formats as high-level allowed the SELinux project to introduce the distinction between high-level languages and CIL in a backwards-compatible manner.
When distributing SELinux policy modules, most Linux distributions place the *.pp
SELinux policy modules inside /usr/share/selinux
, usually within a subdirectory named after the policy store (such as targeted
). There, these modules are ready for administrators to activate them.
When activating a module, the semodule
command (part of the policycoreutils
package) will copy those modules into a dedicated directory: /etc/selinux/targeted/modules/active/modules
(RHEL) or /var/lib/selinux/mcs/active/modules
(Gentoo). This location is defined by the version of the SELinux user space--more recent versions use the /var/lib
location. When all modules are aggregated in a single location, the final policy binary is compiled, resulting in /etc/selinux/targeted/policy/policy.30
(or some other number) and loaded in memory.
On RHEL, the SELinux policies are provided by the selinux-policy-targeted
(or -minimum
or -mls
) package. On Gentoo, they are provided by the various sec-policy/selinux-*
packages (Gentoo uses separate packages for each module, reducing the number of SELinux policies that are loaded on an average system).
Bundling modules in a policy store
A policy store contains a single comprehensive policy, and only a single policy can be active on a system at any point in time. Administrators can switch policy stores, although this often requires the system to be rebooted and might even require relabeling the entire system (relabeling is the act of resetting the contexts on all files and resources available on that system).
The active policy on the system can be queried using sestatus
(SELinux status, provided through the policycoreutils
package), as follows:
# sestatus | grep "Loaded policy name"
Loaded policy name: targeted
In this example, the currently loaded policy (store) is named targeted
. The policy name that SELinux will use upon its next reboot is defined in the /etc/selinux/config
configuration file as the SELINUXTYPE
parameter.
It is the system's init system (be it a SysV-compatible init system or systemd) that is generally responsible for loading the SELinux policy, effectively activating SELinux support on the system. The init system reads the configuration, locates the policy store, and loads the policy file in memory. If the init system does not support this (in other words, it is not SELinux-aware) then the policy can be loaded through the load_policy
command, part of the policycoreutils
package.