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 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.
Important note
Gentoo, a source-based meta-distribution, distributes SELinux policies as (source) code, compiled and built at install time, just like it does with other software.
The following diagram shows the relationship between policy rules (policy code), policy modules, and a policy package (which is often a one-to-one mapping toward a policy store):
As we can see, policies are first written, then compiled in modules, after which they are bundled and distributed. The next few sections describe each of these phases in detail.
Writing SELinux policies
An SELinux policy writer can 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, which 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/SELinuxProject/refpolicy/), 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 ensure no rules are brought into the project that might jeopardize the security of any platform. Writing policies without the extensive set of M4 macros offered by the reference policy project is hard, which is why the reference policy has become the de facto source for policies.
The SELinux CIL format is reasonably recent, and although it is very much in use already (the SELinux user space converts everything to CIL in the background), it is not that common yet for policy writers to use it directly.
To show the differences between these three languages, 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, we write this 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 the CIL language, the rule expression is like so:
(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. - A
.if
file, which contains interface and template definitions, allowing policy writers to easily use the newly-generated policy rules to enhance other policies. You can compare this to header files in other programming languages. - A
.fc
file, which contains file context expressions. These are rules that assign labels to resources on the filesystem.
A finished policy is then packaged into an 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 following diagram shows the building of policy modules. It also shows where CIL comes into play, even when the policy rules themselves are not written in CIL:
The binary *.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 (which in previous SELinux versions were the binary blobs loaded in memory) as high-level allows the SELinux project to introduce the distinction between high-level languages and CIL in a backward-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 will copy those modules into a dedicated directory (/var/lib/selinux/mcs/active/modules
). When all modules are aggregated in a single location, the final policy binary is compiled, resulting in /etc/selinux/targeted/policy/policy.32
(or some other number) and loaded in memory.
On CentOS, 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 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 rebooting the system 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
(an SELinux status) as follows:
# sestatus | grep "Loaded policy name" Loaded policy name: mcs
In this example, mcs
is the currently loaded policy (store). The policy name that SELinux will use upon its next reboot is defined in the /etc/selinux/config
configuration file as the SELINUXTYPE
parameter.
The system's init
system (be it a SysV
-compatible init
system or systemd
) 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 should be loaded through the load_policy
command.
As we now have a better view of the flow used in policy development and distribution, let's see how Linux distributions can differentiate their SELinux offering.