SELinux System Administration - Third Edition

5 (1 reviews total)
By Sven Vermeulen
    What do you get with a Packt Subscription?

  • Instant access to this title and 7,500+ eBooks & Videos
  • Constantly updated with 100+ new titles each month
  • Breadth and depth in over 1,000+ technologies
  1. Chapter 1: Fundamental SELinux Concepts

About this book

Linux is a dominant player in many organizations and in the cloud. Securing the Linux environment is extremely important for any organization, and Security-Enhanced Linux (SELinux) acts as an additional layer to Linux system security.

SELinux System Administration covers basic SELinux concepts and shows you how to enhance Linux system protection measures. You will get to grips with SELinux and understand how it is integrated. As you progress, you’ll get hands-on experience of tuning and configuring SELinux and integrating it into day-to-day administration tasks such as user management, network management, and application maintenance. Platforms such as Kubernetes, system services like systemd, and virtualization solutions like libvirt and Xen, all of which offer SELinux-specific controls, will be explained effectively so that you understand how to apply and configure SELinux within these applications. If applications do not exert the expected behavior, you’ll learn how to fine-tune policies to securely host these applications. In case no policies exist, the book will guide you through developing custom policies on your own.

By the end of this Linux book, you’ll be able to harden any Linux system using SELinux to suit your needs and fine-tune existing policies and develop custom ones to protect any app and service running on your Linux systems.

Publication date:
December 2020
Publisher
Packt
Pages
458
ISBN
9781800201477

 

Chapter 1: Fundamental SELinux Concepts

Security-Enhanced Linux (SELinux) brings additional security measures to your Linux system to further protect its resources. As part of the Linux kernel, it is a mandatory access control system supported by major Linux distributions. In this book, we cover all aspects of SELinux, from basic fundamentals to resolving SELinux issues, configuring applications to deal with SELinux, and even writing our own policies.

Before we embark on the details of SELinux, let's first cover the concepts of this technology: why SELinux uses labels to identify resources, how SELinux differs from traditional Linux access controls, how SELinux enforces security rules, and other mandatory access control systems that are supported in the Linux kernel. We will also see how the access control rules enforced by SELinux are provided through policy files. At the end of the chapter, we will cover an overview of the differences between SELinux implementations across Linux distributions.

In this chapter, we're going to cover the following main topics:

  • Providing more security for Linux
  • Labeling all resources and objects
  • Defining and distributing policies
  • Distinguishing between policies
 

Technical requirements

Check out the following video to see the Code in Action: https://bit.ly/2FFaUdm

 

Providing more security for Linux

Seasoned Linux administrators and security engineers already know that they need to put some trust in the users and processes of their system in order for the system to remain secure. This is partly because users can attempt to exploit vulnerabilities found in the software running on the system, but a large contribution to this trust level is because the secure state of the system depends on the behavior of the users. A Linux user with access to sensitive information could easily leak that out to the public, manipulate the behavior of the applications they launch, and do many other things that affect the security of the system. The default access controls active on a regular Linux system are discretionary; it is up to the users how the access controls should behave.

The Linux discretionary access control (DAC) mechanism is based on the user and/or group information of the process and is matched against the user and/or group information of the file, directory, or other resource being manipulated. Consider the /etc/shadow file, which contains the password and account information of the local Linux accounts:

$ ls -l /etc/shadow 
-rw-r-----. 1 root root 1019 Nov 28 20:44 /etc/shadow

Without additional access control mechanisms in place, this file is readable and writable by any process owned by the root user, regardless of the purpose of the process on the system. The shadow file is a typical example of a sensitive file that we don't want to see leaked or abused in any other fashion. Yet the moment someone has access to the file, that user can copy it elsewhere, for example to a home directory, or even mail it to another computer and attempt to attack the password hashes stored within.

Another example of how Linux DAC requires trust from its users is the configuration of a database server. Database files themselves are (hopefully) only accessible to the runtime account of the database management system (DBMS) itself, and the Linux root user. Properly secured systems will only grant trusted users access to these files (for instance, through sudo) by allowing them to change their effective user ID from their personal user to the database runtime user or even the root account, but only for a well-defined set of commands that the system administrator has configured up front. These users too, can analyze the database files and gain access to potentially confidential information in the database without going through the DBMS. Administrators often have to put significant trust in these users to provide a secure system, rather than being able to enforce this.

However, regular users are not the only reason for securing a system. Lots of software daemons run as the Linux root user or have significant privileges on the system. Errors within those daemons can easily lead to information leakage or might even lead to remotely exploitable vulnerabilities. Backup software, monitoring software, change management software, scheduling software, and so on: they all often run with the highest privileged account possible on a regular Linux system. Even when the administrator does not allow privileged users, their interaction with daemons introduces a potential security risk. So, the users are still trusted to correctly interact with these applications in order for the system to function properly. Through this, the administrator leaves the security of the system to the discretion of its (many) users.

Enter SELinux, which provides an additional access control layer on top of the standard Linux DAC mechanism. SELinux provides a mandatory access control (MAC) system that, unlike its DAC counterpart, gives the administrator full control over what is allowed on the system and what isn't. It accomplishes this by supporting a policy-driven approach over what processes are and aren't allowed to do and by enforcing this policy through the Linux kernel.

Mandatory means that the operating system enforces the access control, defined solely by the policy rules that the system administrator (or security administrator) has enabled. Users and processes do not have permission to change the security rules, so they cannot work around the access controls; security is not left to their discretion anymore.

Considering the relational database example, a mandatory access control system would no longer require the administration to trust certain users, as it has full control over what these users can and cannot do. PostgreSQL, as we will see in Chapter 8, SEPostgreSQL – Extending PostgreSQL with SELinux, can interact with the SELinux subsystem to allow the administrator full coverage over the data access involved, even inside the database.

The word mandatory here, just like the word discretionary before, was not chosen accidentally to describe the abilities of the access control system: both are known terms in the security research field. Many security publications use these terms, including the Trusted Computer System Evaluation Criteria (TSEC) (http://csrc.nist.gov/publications/history/dod85.pdf) standard (also known as the Orange Book) published by the Department of Defense in the United States of America in 1985. This publication has led to the Common Criteria standard for computer security certification (ISO/IEC 15408), available at http://www.commoncriteriaportal.org/cc/.

Next, we'll describe how the Linux kernel is responsible for the SELinux implementation.

Introducing Linux Security Modules (LSM)

Consider the example of the shadow file again. A MAC system can be configured to only allow a limited number of processes to read from and write to the file. On such specifically configured systems, a user logged on as root cannot directly access the file or even move it around. They can't even change the attributes of the file:

# id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),
4(adm),6(disk),10(wheel),11(floppy),26(tape),27(video) context=sysadm_u:sysadm_r:sysadm_t:s0-s0:c0.c1023
# cat /etc/shadow
cat: /etc/shadow: Permission denied
# chmod a+r /etc/shadow
chmod: changing permissions of '/etc/shadow': Permission denied

The system enforces this through rules that describe when the contents of this file can be read, or when its attributes can be changed. With SELinux, these rules are defined in the SELinux policy and are loaded when the system boots. It is the Linux kernel itself that is responsible for enforcing the rules.

Mandatory access control systems such as SELinux are supported in the Linux kernel through Linux Security Modules (LSM), a Linux subsystem called before processing a user space request. Such requests are called system calls, and Linux supports over 100 of them.

Figure 1.1 – High-level overview of how LSM integrates into the Linux kernel

Figure 1.1 – High-level overview of how LSM integrates into the Linux kernel

LSM has been available in the Linux kernel since version 2.6, released in December 2003. It is a framework that provides hooks inside the Linux kernel at various locations, including the system call entry points. When these hooks trigger, registered security implementations such as SELinux have their functions executed automatically. In SELinux, these functions check the policy and other information before returning a go/no-go. LSM by itself does not provide any security functionality; instead, it relies on security implementations that do the heavy lifting: the framework is modular.

Within the LSM framework, two types of security modules exist: exclusive and non-exclusive modules. Two exclusive modules cannot be active simultaneously: each exclusive LSM module needs exclusive control over some kernel objects (generally those related to a security context) and is not able to deal with other LSM modules that need these objects as well. Non-exclusive modules don't have this need and can be combined (also known as stacking) at will, regardless of whether an exclusive LSM module is active or not.

A major use case for stacking LSM modules is to enable different security models within containers running on the system. Right now, it is not possible to implement a different security module within a Linux container, and the security within the container falls back to the security module of the host. To support this, more and more exclusive LSM implementations (like SELinux) are working to make their implementation non-exclusive, and we can expect improvements in this area within the next year.

SELinux is one implementation that uses LSM. Several other implementations exist:

  • AppArmor is a mandatory access control system that has a strong focus on application-level protections (called profiles), based largely on filesystem paths. This makes AppArmor easy to understand and implement for administrators, as it does not have the complexity of abstracting rules to labels (as SELinux does). In the Labeling all resources and objects section, we explain why SELinux uses labels. AppArmor is an exclusive LSM module at the time of writing, but will most likely become non-exclusive very soon.
  • Smack is a mandatory access control system that uses labels on processes and resources. The labels contain security identifiers interpreted by Smack to enforce access control, requiring fewer access rules in Smack (unlike SELinux, which does not perform an interpretation of labels – excluding sensitivity – and thus requires a higher number of policy rules). Smack is an exclusive LSM module.
  • TOMOYO Linux is a mandatory access control system, but its access control mechanism is also easy to use for system analysis. It automatically builds up policies based on application behavior, and like AppArmor, its policies primarily use paths rather than labels. TOMOYO Linux (and its fork, AKARI) is a non-exclusive LSM module.
  • LoadPin is an LSM module that ensures that the Linux kernel resources (such as kernel modules and firmware) are all loaded from a single non-writable filesystem. LoadPin is a non-exclusive LSM module.
  • Yama is an LSM module that adds additional access controls on activities that are not sufficiently fine-grained by Linux, such as by attaching them to the memory of another process (using ptrace). Yama is a non-exclusive LSM module.
  • SafeSetId is an LSM module that allows finer control over which users can use setuid (switching to another user) toward another user. Rather than granting the use of setuid, SafeSetId can limit for which users this is allowed. This ensures that vulnerabilities or misconfigurations in tools such as sudo are still contained. SafeSetId is a non-exclusive LSM module.
  • Lockdown is an LSM module that protects the Linux kernel memory. It has two modes: in integrity mode, it prevents modifying kernel objects from user space (such as direct memory access or PCI access); in confidentiality mode, it additionally prevents extracting potentially confidential information from kernel objects. Lockdown is a non-exclusive LSM module.
  • The capability LSM module is, by default, enabled on systems and provides support for Linux capabilities (a set of permissions granted to a user when the user is assigned a certain capability). It is a non-exclusive LSM module.

To query the list of active LSM modules on a system, read /sys/kernel/security/lsm:

$ cat /sys/kernel/security/lsm
capability,selinux

Next, we'll explain how SELinux works on top of regular Linux access controls.

Extending regular DAC with SELinux

SELinux does not change the Linux DAC implementation, nor can it override denials made by the Linux DAC permissions. If a regular system (without SELinux) prevents a particular access, there is nothing SELinux can do to override this decision. This is because the LSM hooks are triggered after the regular DAC permission checks execute, a conscious design decision from the LSM project.

For instance, if you need to allow an additional user access to a file, you cannot add an SELinux policy to do that for you. Instead, you will need to look into other features of Linux, such as the use of POSIX access control lists. Through the setfacl and getfacl commands, the user can set additional permissions on files and directories, opening up the selected resource to additional users or groups.

As an example, let's grant a user admin read-write access to a file using setfacl:

$ setfacl -m u:admin:rw /srv/backup/setup.conf

Similarly, to view the current POSIX ACLs applied to the file, use this command:

$ getfacl /srv/backup/setup.conf
getfacl: Removing leading '/' from absolute path names
# file: srv/backup/setup.conf
# owner: root
# group: root
user::rw-
user::admin:rw-
group::r--
mask::rw-
other::r—

This shows that the file is writable not only by its owner but also by the admin user.

Restricting root privileges

The regular Linux DAC allows an all-powerful user: root. Unlike most other users on the system, the logged-on root user has all the rights needed to fully manage the entire system, ranging from overriding access controls to controlling audits, changing user IDs, managing the network, and much more. This is supported through a security concept called capabilities (for an overview of Linux capabilities, check out the capabilities manual page: man capabilities). SELinux is also able to restrict access to these capabilities in a fine-grained manner.

Due to this fine-grained authorization aspect of SELinux, even the root user can be confined without impacting the operations on the system. The previous example of accessing /etc/shadow is just one example of an activity that a powerful user such as root still might not be able to perform due to the SELinux access controls in place.

Reducing the impact of vulnerabilities

If one benefit of SELinux needs to be stressed, then it is its ability to reduce the impact of vulnerabilities. But this vulnerability reduction is also often misunderstood.

A properly written SELinux policy confines applications so that their allowed activities are reduced to a minimum set. This least-privilege model ensures that abnormal application behavior is not only detected and audited but also prevented. Many application vulnerabilities can be exploited to execute tasks that an application is not meant to do. When this happens, SELinux will prevent this.

However, there are two misconceptions about SELinux's ability to thwart exploits, namely, the impact of the policy and the exploitation itself.

If the policy is not written in a least-privilege model, then SELinux might consider this non-standard behavior as normal and allow the actions to continue. For policy writers, this means that their policy rules have to be very fine-grained. Sadly, that makes writing policies very time-consuming: with more than 130 classes and over 250 permissions known to SELinux, policy rules need to take all these classes and permissions into account for each interaction.

As a result, policies tend to become convoluted and harder to maintain. Some policy writers make policies more permissive than is absolutely necessary, which might result in exploits becoming successful even though the action is not expected behavior from an application's point of view. Some application policies are explicitly marked as unconfined (which we discuss in Chapter 14, Dealing with New Applications), showing that they are very liberal in their allowed permissions. Fedora, CentOS, and Red Hat Enterprise Linux even start application policies as permissive and only start enforcing access controls for those applications after a few releases (and additional testing).

The second misconception is the exploit itself. If an application's vulnerability allows an unauthenticated user to use the application services as if the user were a regular, authorized user, then SELinux will not play a role in reducing the impact of the vulnerability; it will only notice the behavior of the application itself and not of the sessions internal to the application. As long as the application itself behaves as expected (such as accessing its own files and not poking around in other filesystems), SELinux will happily allow the actions to take place.

It is only when the application starts behaving erratically that SELinux stops the exploit from continuing. SELinux will prevent exploits such as remote command execution (RCE) against applications that should not be executing random commands (such as database management systems or web servers, excluding CGI-like functionality), whereas session hijacking or SQL injection attacks are not controllable through SELinux policies.

Enabling SELinux support

Enabling SELinux on a Linux system is not just a matter of enabling the SELinux LSM module within the Linux kernel.

An SELinux implementation contains the following:

  • The SELinux kernel subsystem, implemented in the Linux kernel through LSM
  • Libraries, used by applications that need to interact with SELinux
  • Utilities, used by administrators to interact with SELinux
  • Policies, which define the access controls themselves

The libraries and utilities are bundled by the SELinux user space project (https://github.com/SELinuxProject/selinux). Next to the applications and libraries provided by the SELinux user space project, various components on a Linux system are updated with SELinux-specific code, including the init system and several core utilities.

Because SELinux isn't just a switch that needs to be toggled, Linux distributions that support it usually come with SELinux predefined and loaded: Fedora, CentOS, and Red Hat Enterprise Linux (with its derivatives, such as Oracle Linux) are well-known examples. Other supporting distributions might not automatically have SELinux enabled but can easily support it through the installation of additional packages (which is the case with Debian and Ubuntu), and others have a well-documented approach to how to convert a system to SELinux (for example, Gentoo and Arch Linux).

Throughout the book, we will show examples for Gentoo and CentOS 8 (which is based on the free software of the Red Hat Enterprise Linux releases and is sponsored by Red Hat). These two distributions have different implementation details, which allow us to demonstrate the full potential of SELinux. To ensure the commands used within this book are available, some SELinux support tools might need to be installed.

On Gentoo Linux, install at least the following packages:

# emerge app-admin/setools sys-apps/policycoreutils

On CentOS Linux, install at least the following packages:

# yum install setools-console policycoreutils-python-utils

As packages can change over time, it is sensible to look up which package provides a particular command.

Important note

If the mentioned packages no longer exist or do not cover all commands, please consult your distribution's documentation on which software packages to install. Most distributions allow searching for the most appropriate package as well, such as with e-file in Gentoo, or yum whatprovides on CentOS or related distributions.

With the SELinux main functionality described, let's look at how SELinux knows what is on the system, and which abstraction it uses to allow policies to be developed for a wide set of users.

 

Labeling all resources and objects

When SELinux has to decide whether it has to allow or deny a particular action, it makes a decision based on the context of both the subject (who is initiating the action) and the object (which is the target of the action). These contexts (or parts of the context) are mentioned in the policy rules that SELinux enforces.

The context of a process is what identifies the process to SELinux. SELinux has no notion of Linux process ownership and does not care how the process is called, which process ID it has, and what account the process runs as. All it wants to know is what the context of that process is, represented to users and administrators as a label. Label and context are often used interchangeably, and although there is a technical distinction (one is a representation of the other), we will not dwell on that much.

Let's look at an example label – the context of the current user:

$ id -Z 
sysadm_u:sysadm_r:sysadm_t:s0-s0:c0.c1023

The id command, which returns information about the current user, is shown executing with the -Z switch (a commonly agreed upon switch for displaying additional security information obtained from the LSM-based security subsystem). It shows us the context of the current user (actually the context of the id process itself when it was executing). As we can see, the context has a string representation and looks as if it has five fields (it doesn't; it has four fields – the last field just happens to contain a colon character).

SELinux developers decided to use labels instead of real process and file (or other resource) metadata for its access controls. This is different from MAC systems such as AppArmor, which uses the path of the binary (and thus the process name) and the paths of the resources to handle permission checks. The following reasons inspired the decision to make SELinux a label-based mandatory access control:

  • Using paths might be easier to comprehend for administrators, but this doesn't allow us to keep the context information close to the resource. If a file or directory moves or remounts, or if a process has a different namespace view on the files, then the access controls might behave differently as they look at the path instead of the file. With label-based contexts, the system retains this information and keeps controlling the resource's access properly.
  • Contexts reveal the purpose of the process very well. The same binary application can be launched in different contexts depending on how it got started. The context value (such as the one shown in the id -Z output earlier) is exactly what the administrator needs. With it, they know what the rights are of each of the running instances, but they can also deduce from it how the process was launched and what its purpose is.
  • Contexts also make abstractions of the object itself. We are used to talking about processes and files, but contexts are also applicable to less tangible resources such as pipes (inter-process communication) or database objects. Path-based identification only works as long as you can write a path.

As an example, consider the following policy statements:

  • Allow the httpd processes to bind to TCP port 80.
  • Allow the processes labeled with httpd_t to bind to TCP ports labeled with http_port_t.

In the first example, we cannot easily reuse this policy when the web server process isn't using the httpd binary (perhaps because it was renamed or it isn't Apache but another web server) or when we want to have HTTP access on a different port. With the labeled approach, the binary could be called apache2 or MyWebServer.py; as long as the process is labeled with httpd_t, the policy applies. The same happens with the port definition: you can label the port 8080 with http_port_t and thus allow the web servers to bind to that port as well without having to write another policy statement.

Dissecting the SELinux context

To come to a context, SELinux uses at least three, and sometimes four, values. Let's look at the context of the SSH server as an example:

$ ps -eZ | grep sshd
system_u:system_r:sshd_t:s0-s0:c0.c1023 2629 ? 00:00:00 sshd

As we can see, the process is assigned a context that contains the following fields:

  • The SELinux user system_u
  • The SELinux role system_r
  • The SELinux type (also known as the domain when we are looking at a running process) sshd_t
  • The sensitivity level s0-s0:c0.c1023

When we work with SELinux, knowing the contexts is extremely important. In most cases, it is the third field (called the domain or type) that is most important since the majority of SELinux policy rules (over 99 percent) consist of rules related to the interaction between two types (without mentioning roles, users, or sensitivity levels).

SELinux contexts are aligned with LSM security attributes and exposed to the user space in a standardized manner (compatible with multiple LSM implementations), allowing end users and applications to easily query the contexts. An easily accessible location where these attributes are presented is within the /proc pseudo filesystem.

Inside each process's /proc/<pid> location, we find a subdirectory called attr, inside of which the following files can be found:

$ ls /proc/$$/attr
current  exec  fscreate  keycreate  prev  sockcreate

All these files, if read, display either nothing or an SELinux context. If it is empty, then that means the application has not explicitly set a context for that particular purpose, and the SELinux context will be deduced either from the policy or inherited from its parent.

The meaning of the files are as follows:

  • The current file displays the current SELinux context of the process.
  • The exec file displays the SELinux context that will be assigned by the next application execution done through this application. It is usually empty.
  • The fscreate file displays the SELinux context that will be assigned to the next file written by the application. It is usually empty.
  • The keycreate file displays the SELinux context that will be assigned to the keys cached in the kernel by this application. It is usually empty.
  • The prev file displays the previous SELinux context for this particular process. This is usually the context of its parent application.
  • The sockcreate file displays the SELinux context that will be assigned to the next socket created by the application. It is usually empty.

If an application has multiple subtasks, then the same information is available in each subtask directory at /proc/<pid>/task/<taskid>/attr.

Enforcing access through types

The SELinux type (the third part of an SELinux context) of a process (called the domain) is the basis of the fine-grained access controls of that process with respect to itself and other types (which can be processes, files, sockets, network interfaces, and more). In most SELinux literature, SELinux's label-based access control mechanism is fine-tuned to say that SELinux is a type enforcement mandatory access control system: when some actions are denied, the (absence of the) fine-grained access controls on the type level are most likely to blame.

With type enforcement, SELinux can control an application's behavior based on how it got executed in the first place: a web server launched by a user will run with a different type than a web server executed through the init system, even though the process binary and path are the same. The web server launched from the init system is most likely trusted (and thus allowed to do whatever web servers are supposed to do), whereas a manually launched web server is less likely to be considered normal behavior and as such will have different privileges.

Important note

The majority of SELinux's online resources focus on types. Even though the SELinux type is just the third part of an SELinux context, it is the most important one for most administrators. Most documentation will even just talk about a type such as sshd_t rather than a full SELinux context.

Take a look at the following dbus-daemon processes:

# ps -eZ | grep dbus-daemon
swift_u:swift_r:swift_dbusd_t:s0-s0:c0.c512 571 ? 00:00:01 dbus-daemon
swift_u:swift_r:swift_dbusd_t:s0-s0:c0.c512 649 ? 00:00:00 dbus-daemon
system_u:system_r:system_dbusd_t:s0-s0:c0.c1023 2498 ? 00:00:00 dbus-daemon

In this example, one dbus-daemon process is the system D-Bus daemon running with the aptly named system_dbusd_t type, whereas two other ones are running with the swift_dbusd_t type assigned to it. Even though their binaries are the same, they both serve a different purpose on the system and as such have a different type assigned. SELinux then uses this type to govern the actions allowed by the process toward other types, including how system_dbusd_t can interact with swift_dbusd_t.

SELinux types are by convention suffixed with _t, although this is not mandatory.

Granting domain access through roles

SELinux roles (the second part of an SELinux context) allow SELinux to support role-based access controls. Although type enforcement is the most used (and known) part of SELinux, role-based access control is an important method to keep a system secure, especially from malicious user attempts. SELinux roles define which types (domains) can be accessed from the current context. These types (domains) on their part define the permissions. As such, SELinux roles help define what a user (who has access to one or more roles) can and cannot do.

By convention, SELinux roles are defined with an _r suffix. On most SELinux-enabled systems, the administrator can assign the following SELinux roles to users:

  • The user_r role is meant for restricted users. This role is only allowed to have processes with types specific to end-user applications. Privileged types, including those used to switch to another Linux user, are not allowed for this role.
  • The staff_r role is meant for non-critical operations. This role is generally restricted to the same applications as the restricted user, but it has the ability to switch roles. It is the default role for operators to have (so as to keep those users in their least privileged role as long as possible).
  • The sysadm_r role is meant for system administrators. This role is very privileged, enabling various system administration tasks. However, certain end-user application types might not be supported (especially if those types are used for potentially vulnerable or untrusted software) to keep the system free from infections.
  • The secadm_r role is meant for security administrators. This role allows changing the SELinux policy and manipulating the SELinux controls. It is generally used when a separation of duties is needed between system administrators and system policy management.
  • The system_r role is meant for daemons and background processes. This role is quite privileged, supporting the various daemon and system process types. However, end-user application types and other administrative types are not allowed in this role.
  • The unconfined_r role is meant for end users. This role allows a limited number of types, but those types are very privileged as they allow running any application launched by a user (or another unconfined process) in a more or less unconfined manner (not restricted by SELinux rules). This role, as such, is only available if the system administrator wants to protect certain processes (mostly daemons) while keeping the rest of the system operations almost untouched by SELinux.

Other roles might exist, such as guest_r and xguest_r, depending on the distribution. It is wise to consult the distribution documentation for more information about the supported roles. The seinfo command is the most common method to obtain an overview of available roles:

# seinfo –-role
Roles: 9
   auditadm_r
   object_r
   secadm_r
   …
   user_r

With the SELinux roles identified, let's look at how we assign roles to users.

Limiting roles through users

An SELinux user (the first part of an SELinux context) is not the same as a Linux (account) user. Unlike Linux user information, which can change while the user is working on the system (through tools such as sudo or su), the SELinux policy can (and generally will) enforce that the SELinux user remains the same even when the Linux user itself has changed. Because of the immutable state of the SELinux user, we can implement specific access controls to ensure that users cannot work around the set of permissions granted to them, even when they get privileged access.

An example of such an access control is the user-based access control (UBAC) feature that some Linux distributions (optionally) enable, which prevents users from accessing files of different SELinux users even when those users try to use the Linux DAC controls to grant access to each other's files.

The most important feature of SELinux users, however, is that SELinux user definitions restrict which roles the (Linux) user can assume. A Linux user is first assigned to an SELinux user, which does not need to be unique: multiple Linux users can be assigned to the same SELinux user. Once set, that user cannot switch to an SELinux role not associated with that SELinux user.

The following diagram shows the role-based access control implementation of SELinux:

Figure 1.2 – Mapping Linux accounts to SELinux users

Figure 1.2 – Mapping Linux accounts to SELinux users

SELinux users are, by convention, defined with an _u suffix, although this is not mandatory. The SELinux users that most distributions have available are named after the role they represent, but instead of ending with _r, they end with _u. For instance, for the sysadm_r role, we have the sysadm_u SELinux user.

Controlling information flow through sensitivities

The fourth part of an SELinux context, the sensitivity, is not always present (some Linux distributions, by default, do not enable sensitivity labels, but most do). This part of the label is needed for the multilevel security (MLS) support within SELinux, which is an optional setting. Sensitivity labels allow the classification of resources and the restriction of access to those resources based on a security clearance. These labels consist of two parts: a confidentiality value (prefixed with s) and a category value (prefixed with c).

In many larger organizations and companies, documents are labeled internal, confidential, or strictly confidential. SELinux can assign processes certain clearance levels for these resources. With MLS, we can configure SELinux to follow the Bell-LaPadula model, a security model characterized by no read up, no write down: based on a process's clearance level, that process cannot read anything with a higher confidentiality level nor write to (or communicate otherwise with) any resource with a lower confidentiality level. SELinux does not use internal, confidential, and other labels. Instead, it uses numbers from zero (the lowest confidentiality) to whatever the system administrator has defined as the highest value (this is configurable and set when the SELinux policy is built).

Categories allow us to assign resources with one or more categories, and to define access controls across categories. One of the functionalities resulting from using categories is to support multitenancy (for example, systems hosting applications for multiple customers) within a Linux system. Multitenancy is provided by assigning a set of categories to the processes and resources of one tenant, whereas the processes and resources of another tenant get a different set of categories. When a process does not have the proper categories assigned, it cannot touch the resources (or other processes) that have other categories assigned.

Important note

An unwritten convention in the SELinux world is that (at least) two categories are used to differentiate between tenants. By having services randomly pick two categories for a tenant out of a predefined set of categories, while ensuring each tenant has a unique combination, these services receive proper isolation. The use of two categories is not mandatory, but services such as sVirt and Docker successfully implement this methodology.

In that sense, categories are like tags, allowing us to grant access only when the tags of the process and the target resource match. As multilevel security is not often used, the benefits of only using categories are persisted in what is called multi-category security (MCS). This is a special MLS case, which only supports a single confidentiality level (s0).

Now that we know how labels are used by SELinux policies, let's look at how SELinux policies are defined and distributed.

 

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):

Figure 1.3 – Relationship between policy rules (code), policy modules, and policy store

Figure 1.3 – Relationship between policy rules (code), policy modules, and 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 the http_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:

Figure 1.4 – Build process from policy rule to policy store

Figure 1.4 – Build process from policy rule to policy store

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.

 

Distinguishing between policies

The most common SELinux policy store names are strict, targeted, mcs, 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:

staff_u:sysadm_r:sysadm_t

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
1

Policy stores that have MLS enabled are generally targeted, mcs, 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 allow, deny, 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
  unconfined_t

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.

Important note

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.

 

Summary

In this chapter, we saw that SELinux offers a more fine-grained access control mechanism on top of the Linux access controls. SELinux is implemented through Linux Security Modules and uses labels to identify its resources and processes based on ownership (user), role, type, and even the security sensitivity and categorization of the resource. We covered how SELinux policies are handled within an SELinux-enabled system and briefly touched upon how policy writers structure policies.

Linux distributions implement SELinux policies, which can differ between distributions based on supported features, such as sensitivity labels, the default behavior for unknown permissions, support for confinement levels, or specific constraints put in place, such as UBAC. However, most of the policy rules themselves are similar and are even based on the same upstream reference policy project.

Switching between SELinux enforcement modes and understanding the log events that SELinux creates when it prohibits certain access is the subject of our next chapter. In it, we will also cover how to approach the often-heard requirement of disabling SELinux, and why doing so is the wrong way forward.

 

Questions

  1. What is the most important difference between a DAC and a MAC system?
  2. How does Linux support the different MAC technologies?
  3. What four fields constitute an SELinux context?
  4. How does SELinux support role-based access controls?
  5. Why isn't there a single SELinux policy for all Linux distributions?

About the Author

  • Sven Vermeulen

    Sven Vermeulen (sjvermeu on Twitter) is a long-term contributor to various free software projects and the author of several online guides and resources, including the Gentoo Handbook. He got his first taste of free software in 1997 and never looked back. Within SELinux, Sven contributed several policies to the Reference Policy project, and actively participated in policy development and user space development projects. In his daily job, Sven is an enterprise architect in a European financial institution as well as a self-employed solution engineer and consultant. Prior to this, he graduated with an MSE in computer engineering from Ghent University and an MSc in ICT enterprise architecture from IC Institute.

    Browse publications by this author

Latest Reviews

(1 reviews total)
very usefully document for linux administrators.

Recommended For You

SELinux System Administration - Third Edition
Unlock this book and the full library FREE for 7 days
Start now