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 (which 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, once running, 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, which 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 (try it out yourself if you are on a SELinux-enabled system):
$ id -Z
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
The id
command, which returns information about the current user, is executed here with the -
Z
switch (a commonly agreed-upon switch for displaying additional security information obtained from the LSM-based security subsystems). 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 :
).
SELinux developers decided to use labels instead of real process and file (or other resource) metadata for its access controls. This is different to 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 decision to make SELinux a label-based mandatory access control was taken for various reasons, which are as follows:
- 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 is moved or remounted or 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, this information is retained and the system keeps controlling the resource 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, he knows what the rights are of each of the running instances, but he can also deduce from it how the process might have been 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 policies:
- 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 can be called apache2
or MyWebServer.py
; as long as the process is labeled 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.
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 an Apache web server as an example:
$ ps -eZ | grep httpd
system_u:system_r:httpd_t:s0 511 ? 00:00:00 httpd
As we can see, the process is assigned a context that contains the following fields:
system_u
: This represents the SELinux usersystem_r
: This represents the SELinux rolehttpd_t
: This represents the SELinux type (also known as the domain in case of a process)s0
: This represents the sensitivity level
This structure can be depicted as follows:
When we work with SELinux, contexts are all we need. In the majority of 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) consists 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 interesting place where these attributes are presented is within the /proc
pseudo file system.
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 fscreate prev
exec keycreate sockcreate
All these files, if read, display either nothing or a 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 that is 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 is able to control what an application is allowed to do based on how it got executed in the first place: a web server that is launched interactively 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.
Note
The majority of SELinux resources will focus on types. Even though the SELinux type is just the third part of a SELinux context, it is the most important one for most administrators. Most documentation will even just talk about a type such as httpd_t
rather than a full SELinux context.
Take a look at the following dbus-daemon
processes:
# ps -eZ | grep dbus-daemon
system_u:system_r:system_dbusd_t 4531 ? 00:00:00 dbus-daemon
staff_u:staff_r:staff_dbusd_t 5266 ? 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 another one is running with the staff_dbusd_t
type assigned to it. Even though their binaries are completely 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 towards other types, including how system_dbusd_t
can interact with staff_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 a 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 the allowed types (domains) processes can run with. These types (domains) on their part define the permissions. As such, SELinux roles help define what a user (which 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 following roles are made available to be assigned to users:
Roles |
Description |
|
This role is meant for restricted users: the |
|
This role is meant for non-critical operations: the SELinux |
|
This role is meant for system administrators: the |
|
This role is meant for security administrators: the |
|
This role is meant for daemons and background processes: the |
|
This role is meant for end users: the |
Other roles might be supported as well, 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. An overview of available roles can be obtained through the seinfo
command (part of setools-console
in RHEL or app-admin/setools
in Gentoo):
# seinfo --role
Roles: 14
auditadm_r
dbadm_r
...
unconfined_r
Limiting roles through users
A SELinux user (the first part of a SELinux context) is different from a Linux 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 remain the same even when the Linux user itself has changed. Because of the immutable state of the SELinux user, specific access controls can be implemented 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 open up 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 is allowed to be in. A Linux user is first assigned to a SELinux user--multiple Linux users can be assigned to the same SELinux user. Once set, that user cannot switch to a SELinux role he isn't meant to be in.
This is 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, there is a sysadm_u
SELinux user.
Controlling information flow through sensitivities
The fourth part of a SELinux context, the sensitivity, is not always present (some Linux distributions by default do not enable sensitivity labels). If they are present though, then this part of the label is needed for the multilevel security (MLS) support within SELinux. Sensitivity labels allow classification of resources and 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 a certain clearance level towards these resources. With MLS, SELinux can be configured to follow the Bell-LaPadula model, a security model that can be characterized by no read up and no write down: based on a process' 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 the internal, confidential, and other labels. Instead, it uses numbers from 0
(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 resources to be tagged with one or more categories, on which access controls are also possible. One of the functionalities resulting from using categories is to support multitenancy (for example, systems hosting applications for multiple customers) within a Linux system, by having processes and resources belonging to one tenant be assigned a particular set of categories, whereas the processes and resources of another tenant get a different set of categories. When a process does not have proper categories assigned, it cannot do anything with the resources (or other processes) that have other categories assigned.
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 is implemented by services such as sVirt and Docker.
In that sense, categories can be seen as tags, allowing access to be granted 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 is persisted in what is called multi-category security (MCS). This is a special MLS case, where only a single confidentiality level is supported (s0
).