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.
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.
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
sudoare 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.
$ 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
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 -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—
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
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.
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
- 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.
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.