Distinguishing between SELinux policies

May 25, 2021

The most common SELinux policy store names are stricttargetedmcs, and mls. None of the names assigned to policy stores are fixed though, so it is a matter of convention. Hence, we recommend consulting the distribution documentation to verify what the proper name of the policy should be. Still, the name often provides some information about the SELinux options enabled through the policy.

Supporting MLS

One of the options that can be enabled is MLS support. The SELinux context will not have a fourth field with sensitivity information in it if this option is disabled, making the contexts of processes and files look as follows:


To check whether MLS is enabled, it is sufficient to see whether a process context doesn’t contain such a fourth field. Another way is to check the Policy MLS Status line in the output of sestatus:

# sestatus | grep MLS
Policy MLS status:       enabled

Yet another method would be to look into the pseudo file, /sys/fs/selinux/mls. A value of 0 means disabled, whereas a value of 1 means enabled:

# cat /sys/fs/selinux/mls

Policy stores that have MLS enabled are generally targetedmcs, and mls, whereas strict generally has MLS disabled.

Dealing with unknown permissions

Permissions (such as read, open, and lock) are defined both in the Linux kernel and in the policy itself. However, sometimes, newer Linux kernels support permissions that the current policy does not yet understand.

Take the block_suspend permission (to be able to block system suspension) as an example. If the Linux kernel supports (and checks) this permission but the loaded SELinux policy does not understand that permission yet, then SELinux has to decide how it should deal with the permission. We can configure SELinux to perform one of the following actions:

  • Allow every action related to an unknown permission (allow).
  • Deny every action related to an unknown permission (deny).
  • Stop and halt the system when an unknown permission is checked (reject).

We configure this through the deny_unknown value. To see the state for unknown permissions, look for the Policy deny_unknown status line in sestatus:

# sestatus | grep deny_unknown
Policy deny_unknown status:      allowed

Administrators can set this for themselves in the /etc/selinux/semanage.conf file through the handle-unknown variable (with allowdeny, or reject).

Supporting unconfined domains

An SELinux policy can be very strict, limiting applications as close as possible to their actual behavior, but it can also be very liberal in what applications are allowed to do. One of the concepts available in many SELinux policies is the idea of unconfined domains. When enabled, it means that certain SELinux domains (process contexts) are allowed to do almost anything they want (of course, within the boundaries of the regular Linux DAC permissions, which still hold) and only a select number of domains are truly confined (restricted) in their actions.

Unconfined domains are introduced to allow SELinux to be active on desktops and servers where administrators do not want to fully restrict the entire system, but only a few of the applications running on it. Generally, these implementations focus on constraining network-facing services (such as web servers and database management systems) while allowing end users and administrators to roam around unrestricted.

With other MAC systems, such as AppArmor, unconfinement is inherently part of the design of the system as they only restrict actions for well-defined applications or users. However, SELinux is designed to be a full mandatory access control system and thus needs to provide access control rules even for those applications that aren’t the security administrator’s primary focus. By marking these applications as unconfined, almost no restrictions are imposed by SELinux.

We can see whether unconfined domains are enabled on the system using seinfo, by querying the policy and asking it whether the unconfined_t SELinux type is defined. On a system where unconfined domains are supported, this type will be available:

# seinfo -t unconfined_t
Types: 1

For a system where unconfined domains are not supported, the type will not be part of the policy:

# seinfo -t unconfined_t
Types: 0

Most distributions that enable unconfined domains call their policy targeted, but this convention is not always followed. Hence, it is always best to consult the policy using seinfo. CentOS enables unconfined domains, whereas with Gentoo, this is a configurable setting through the unconfined USE flag.

Limiting cross-user sharing

When UBAC is enabled, certain SELinux types will be protected by additional constraints. This will ensure that one SELinux user cannot access the files (or other specific resources) of another user, even when those users are sharing their data through the regular Linux permissions. UBAC provides some additional control over information flow between resources, but it is far from perfect. Essentially, it is made to isolate SELinux users from one another.


A constraint in SELinux is an access control rule that uses all parts of a context to make its decision. Unlike type enforcement rules, which are purely based on the type, constraints can take the SELinux user, SELinux role, or sensitivity label into account. Constraints are generally developed once and left untouched – most policy writers will not touch constraints during their development efforts.

Many Linux distributions, including CentOS, disable UBAC. Gentoo allows users to decide whether they want UBAC through the Gentoo ubac USE flag (which is enabled by default).

Incrementing policy versions

While checking the output of sestatus, we see that there is also a reference to a policy version:

# sestatus | grep version
Max kernel policy version:       32

This version has nothing to do with the versioning of policy rules but with the SELinux features that the currently running kernel supports. In the preceding output, 32 is the highest policy version that the running kernel supports. Every time a new feature is added to SELinux, the version number is increased. We can find the policy file itself (which contains all the SELinux rules loaded at boot time by the system) in /etc/selinux/targeted/policy (where targeted refers to the policy store used, so if the system uses a policy store named mcs, then the path will be /etc/selinux/mcs/policy).

If multiple policy files exist, use seinfo to discover which policy version file is used:

# seinfo | grep Version
Policy version:                  31 (MLS enabled)

A list of policy feature enhancements and the Linux kernel version in which that given feature is introduced is provided next. Many of the features are only of concern to policy developers, but knowing the evolution of the features gives us a good idea about the evolution of SELinux:

  • Version 12 represents the “old API” for SELinux, which is now deprecated.
  • Version 15, introduced in Linux 2.6.0, provided the new API for SELinux.
  • Version 16, introduced in Linux 2.6.5, added support for conditional policy extensions.
  • Version 17, introduced in Linux 2.6.6, added support for IPv6.
  • Version 18, introduced in Linux 2.6.8, added support for fine-grained netlink socket permissions.
  • Version 19, introduced in Linux 2.6.12, added support for MLS.
  • Version 20, introduced in Linux 2.6.14, reduced the size of the access vector table.
  • Version 21, introduced in Linux 2.6.19, added support for MLS range transitions.
  • Version 22, introduced in Linux 2.6.25, added policy capabilities.
  • Version 23, introduced in Linux 2.6.26, added support for per-domain permissive mode.
  • Version 24, introduced in Linux 2.6.28, added support for explicit hierarchy (type bounds).
  • Version 25, introduced in Linux 2.6.39, added support for filename-based transitions.
  • Version 26, introduced in Linux 3.0, added support for role-transitions for non-process classes, as well as support for role attributes.
  • Version 27, introduced in Linux 3.5, added support for the flexible inheritance of the SELinux user and SELinux role for newly-created objects.
  • Version 28, introduced in Linux 3.5, added support for the flexible inheritance of the SELinux type for newly-created objects.
  • Version 29, introduced in Linux 3.14, added support for attributes within SELinux constraints.
  • Version 30, introduced in Linux 4.3, added support for extended permissions, implemented first on ioctl controls. It also introduced enhanced SELinux Xen support.
  • Version 31, introduced in Linux 4.13, added support for InfiniBand access controls.
  • Version 32, introduced in Linux 5.5, added support for automatically deducing the intersection in sensitivity labels, called greatest lower bound, largest upper bound (glblub).

By default, when an SELinux policy is built, the highest supported version as defined by the Linux kernel and libsepol (the library responsible for building the SELinux policy binary) is used. Administrators can force a version to be lower using the policy-version parameter in /etc/selinux/semanage.conf.

Different policy content

Besides the policy capabilities described in the previous section, the main difference between policies (and distributions) is the policy content itself. We already covered that most distributions base their policy on the reference policy project. Although the reference policy project is considered the master for most distributions, each distribution has its own set of deviations from this main policy set.

Many distributions make extensive additions to the policy without directly passing the policies to the upstream reference policy project. There are several possible reasons why this is not directly done:

  • The policy enhancements or additions are still immature: Fedora, CentOS, and Red Hat initially start with active, permissive policies, meaning the policies are not enforced. Instead, SELinux logs what it would have prevented and, based on those logs, the policies are then enhanced. This means that a policy is only ready after a few releases.
  • The policy enhancements or additions are too specific to the distribution: If a policy set is not reusable for other distributions, then some distributions will opt to keep those policies to themselves as the act of pushing changes to upstream projects takes quite some effort.
  • The policy enhancements or additions haven’t followed the upstream rules and guidelines: The reference policy has a set of guidelines that policies need to adhere to. If a policy set does not comply with these rules, then the reference policy will not accept the contribution.
  • The policy enhancements or additions are not implementing the same security model as the reference policy project wants: As SELinux is a very extensive mandatory access control system, it is possible to write completely different policies.
  • The distribution does not have the time or resources to push changes upstream.

This means that SELinux policies can differ between distributions (and even releases of the same distribution).

With this, we can conclude on some of the differentiation that distributions can put into their SELinux policies: they can opt to enable or disable MLS support, allow or deny unknown permissions, add distribution-provided unconfined domains, support user-based access controls, and/or deviate from the reference policy project to suit the distribution’s principles.

Related Articles

No Results Found

The page you requested could not be found. Try refining your search, or use the navigation above to locate the post.

Lorem ipsum dolor sit amet consectetur


Submit a Comment

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

16 − 8 =