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 port80
. - Allow the processes labeled with
httpd_t
to bind to TCP ports labeled withhttp_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:
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.