Running applications without restrictions – SELinux

July 06, 2021

The default behavior in many Linux distributions is to run new applications through unconfined domains. These are specially crafted domains that, while still being controlled by SELinux, are designed to have very, very broad permissions granted. You can compare such unconfined domains with a firewall that allows any possible flow: while the firewall is running, it is hardly doing any enforcement.

There is, however, another approach possible as well, namely, running an application as a permissive domain. Unlike unconfined domains, permissive domains are not enforced through SELinux: everything the domain does is allowed, even though SELinux might log every violation.

Let’s first look at unconfined domains and how administrators can modify system configuration to apply unconfined domains to other applications, or remove applications from being unconfined.

Understanding how unconfined domains work

An unconfined domain is an SELinux domain that has broad permissions, restricting only a very small amount of actions that a domain can do. Unconfined domains are not really a concept that SELinux, as technology, supports. Instead, it is used by SELinux policy developers who created a set of permissions they consider as being unconfined.

End users on many Linux distributions will have noticed that their own context is unconfined_t. While this is indeed a reference to being an unconfined domain, there are more domains that are unconfined than unconfined_t.

SELinux policy developers have aggregated most of the permissions related to unconfined domains either in the domains themselves (as is the case for the reference policy) or in SELinux attributes, such as unconfined_domain_type and unconfined_user_type (as is the case for CentOS and related Linux distributions). In the case of attributes, these attributes are then assigned to one or more domains to effectively make them unconfined in nature:

$ seinfo -a | grep unconfined
$ seinfo -a unconfined_domain_type -x

Once a process is running as an unconfined domain, that does not imply that every action of that domain remains unconfined. When an unconfined domain executes a process that has a proper SELinux policy assigned, it is possible for this execution to still invoke a domain transition, effectively running the executed command in a (possibly confined) SELinux domain.

As the decision whether a domain transition is allowed or not falls within the SELinux policy, it is recommended that administrators query which domain transitions are allowed and which ones aren’t. Given that we are mostly interested in single-step analysis, we can use the sesearch utility to have a quick overview of supported domain transitions:

$ sesearch -A -s unconfined_service_t -c process -p transition
allow unconfined_service_t chronyc_t:process transition;
allow unconfined_service_t rpm_script_t:process transition;
allow unconfined_service_t unconfined_service_t:process { transition ...};
allow unconfined_service_t virt_domain:process { transition ...};

We can see the (many) permissions related to an unconfined domain by either checking them for a single domain, or for the attribute that represents unconfined domains directly:

$ sesearch -A -s unconfined_domain_type -ds

Using unconfined domains is preferred over making domains permissive, so let’s see how we can mark a new application to run as an unconfined domain.

Making new applications run as an unconfined domain

When applications are executed, there are a number of checks that need to pass before this results in a domain transition:

  • The source SELinux domain must be able to execute the application (implying execute rights on the SELinux type associated with the application’s binary or script).
  • The source SELinux domain must be able to transition to the target domain.
  • The target domain must have its application binary or script labeled with an SELinux type that is marked as an entrypoint for that domain.
  • The target domain must be allowed for the SELinux role that the source domain is running with (or a role transition has to be allowed, but that is a corner case).

All these checks are related to the SELinux policy and the labels. It comes as no surprise then that, in order for us to enable applications to run in an unconfined domain, we need to associate the right labels.

Let’s consider two examples in the following sections, one being a user-triggered application, while the other is a daemonized service.

Running applications in an explicit unconfined domain

For applications that users execute, let’s take the example of Jailkit. By default, this application is not associated with any domain, so it runs within the same domain as the parent process. If we are logged in to the system through the unconfined_u user (in the unconfined_t SELinux domain), then we have nothing to do. But suppose that our staff user is confined, yet we want to have the command run in the unconfined_t domain.

Note:

This is used as an example that shows how to have applications run in a target domain – in our case, an unconfined domain. Allowing confined users to run unconfined applications always has a risk associated with it, because they might use this to break out of their confinement. Make sure that this is only done for applications or users where you have confidence that they will not breach security.

To allow the application to run in the unconfined_t domain, we will use sudo and its SELinux support. While we could also extend the SELinux policy to allow it transparently, this is not recommended. Updating the SELinux policy to allow confined users to run unconfined commands implies that several principles listed in the policy are overturned. You would need to allow the confined user to switch to the unconfined_r role (which is often not allowed for security reasons) transparently, for instance. It would require significant analysis to make sure that it cannot be used to break out of the confined role.

Using sudo allows us to limit the methods through which such more privileged commands are executed. SELinux-wise, the appropriate controls are put on the staff_sudo_t domain, for instance, which is only assigned when executing the sudo command, rather than the staff_t domain, which is where most of the user’s interactions are executed.

Let’s allow the lisa user to run the jk_init command as an unconfined process:

  • First, check whether the SELinux user for which we want to execute the command is allowed to do anything with the unconfined_r SELinux role (and if not, add the role to the SELinux user configuration):
# semanage user -l

Allowing a role does not imply that the user domain automatically switches role when needed though, but rather that it is an allowed role for the user.

  • Next, update the /etc/sudoers file to include a transition when executing the following command:
# visudo
lisa ALL=(root) ROLE=unconfined_r TYPE=unconfined_r NOPASSWD: /usr/sbin/jk_init

In this case, we not only use a ROLE and TYPE transition, but we also allow the command to be executed as the root user, as that is a requirement for the jk_init command. Of course, this can be adjusted as needed.

  • Our user can now run the command, prefixed by sudo, to have it execute in the right domain and using the right role:
$ sudo /usr/sbin/jk_init -v -j /srv/chroot \
 extshellplusnet

Using sudo for end user applications is common when the privileges of the user also have to switch (from the user privilege to the root privilege). It is less common to use it when staying within the Linux user context though.

Running daemons in an explicit unconfined domain

The second use case, and perhaps a more common one than for end user applications, is to run daemonized services in an unconfined domain. Most Linux distributions that use unconfined domains (such as CentOS) will by default have newly installed software run as an unconfined domain as well. For instance, any service that is enabled and activated through systemd (which runs as the init_t SELinux domain) and that does not have an explicit labeling set (meaning the executable commands are labeled as bin_t) will run in the unconfined_service_t domain.

But what if we have a confined application that we want to run in an unconfined domain? Let’s take PostgreSQL as an example. Suppose this is an isolated database that has certain extensions active that are incompatible with the existing PostgreSQL SELinux domain (postgresql_t). Administrators might not have the time to extend the current SELinux policy using methods such as audit2allow.

Luckily, we can easily move PostgreSQL to work and run in an unconfined domain. There are two ways to approach this:

  • We can remove the existing labels on its executable files (postgresql_exec_t) and set it to bin_t instead. This will then trigger the default transition when starting the PostgreSQL binary to the unconfined_service_t domain.
  • We can update the SELinux policy for postgresql_t to become an unconfined domain itself.

Switching the labels is easy, but is the least recommended method. It is, however, a quick and dirty way to see whether running the service in the unconfined_service_t domain is sufficient to resolve the issue immediately:

# chcon -t bin_t /usr/bin/postgres

If agreeable, make sure that the label change remains, even after a relabel operation occurs:

# semanage fcontext -a -t bin_t /usr/bin/postgres

Updating the SELinux policy for the PostgreSQL daemon is recommended though, as it retains the existing support within the policy (including the file transitions and other integrations that the postgresql_t domain has with other domains and resources). It also allows administrators to update the policy as needed later on, when there is more time available.

To make the postgresql_t domain unconfined, we need to assign the unconfined_domain_type attribute to the postgresql_t domain. This can be accomplished by loading in the following CIL-based SELinux policy:

(typeattributeset cil_gen_require postgresql_t)
(typeattributeset cil_gen_require unconfined_domain_type)
(typeattributeset unconfined_domain_type (postgresql_t))

Save this in a file and load it using semodule -i, and from that point onward the postgresql_t domain will be augmented with the privileges associated with the unconfined_domain_type attribute.

Extending unconfined domains

As unconfined domains are still enforced, it might be possible that SELinux is still preventing some actions from occurring. We can adjust the SELinux policy to extend unconfined domains with more privileges though. While the default unconfined_service_t domain has almost all possible permissions set, more specifically, identified domains might not be as expansive.

The trick to adding more privileges to the domains is to assign the appropriate attribute to them. The method is the same as seen in Running daemons in an explicit unconfined domain, adding more attributes as needed. The list of attributes that we can add is very significant (as you can see from seinfo -a), but the most important ones, especially for the CentOS-based SELinux policy, are the following:

  1. files_unconfined_type allows the domain to manage any possible file- or filesystem-based resource.
  2. devices_unconfined_type allows the domain to interact and manage any device resource.
  3. filesystem_unconfined_type allows the domain to interact and manage all filesystems.
  4. selinux_unconfined_type allows the domain to interact with and manage the SELinux subsystem and configuration.
  5. storage_unconfined_type allows the domain to interact with storage systems and removable devices.
  6. dbusd_unconfined allows the domain to interact with all possible D-Bus services.
  7. xserver_unconfined_type allows the domain to interact with and manage all X server resources.

Furthermore, there are several can_* attributes that fine-tune very specific, security-sensitive actions. The names of these attributes nicely explain what they allow. For instance, can_write_shadow_passwords allows the domain to write to /etc/shadow, whereas can_change_object_identity means that the domain can change the SELinux user of an object.

Not all attributes have their privileges reflected in regular allow rules or transitions that can be queried using sesearch. For instance, can_change_object_identity is used in SELinux constraints instead:

# seinfo --constrain | grep can_change_object_identity

Querying the constraints is an often forgotten method to see what or why a certain privilege is or isn’t assigned to a domain.

Suppose now that an application still fails to run correctly within an unconfined domain, then we can use permissive domains to allow this application to run unprotected, while having the rest of the system remain in enforcing mode.

Marking domains as permissive

We can mark a domain as permissive using semanage permissive:

# semanage permissive -a postgresql_t

The same command can be used to query (-l) or remove (-d) permissive states. However, administrators should take special care before marking domains as permissive:

  • First of all, if you mark a domain as permissive, then all processes running with that SELinux domain will run without any active SELinux enforcements. As an administrator, you really want to limit the number of processes that are running through permissive domains, so do not mark broadly used SELinux domains as permissive.

    A daemon that runs in an unconfined domain, yet still has problems, should not result in the unconfined domain being marked as permissive. Instead, have the daemon run as a different domain, and mark that domain as permissive.

  • Secondly, permissive domains will still trigger SELinux behavior by the SELinux subsystem. Transition rules, including process transitions and file transitions, are still executed. This is of course by design, as permissive domains are meant to be short-lived, allowing administrators and developers to capture information and adapt the policy as needed before they can remove the permissive flag again.

    This also implies that, if the domain does not have proper transition rules set, it might result in files being created on the system that have the wrong SELinux types set. Because of this, using permissive domains should not be considered for applications or daemons that have a wide impact on the system, but rather for more isolated situations where you, as an administrator, feel confident that you can easily fine-tune the policy if needed.

Consider the situation where we deploy pgpool-II, a load balancer for PostgreSQL databases, and find that the application does not run properly in an unconfined domain, even though it already runs in the unconfined_service_t SELinux domain. While we can put this domain in permissive mode, this would also apply to various other services running inside the unconfined_service_t domain.

What we can do is relabel its resources (executables mostly) so that the application is run through a different SELinux domain, and then mark that domain as permissive. We can either reuse an existing, unused domain or generate one, as we will see in the Generating policies with sepolicy generate section.

When we want to run an application in a (strictly) confined manner though, we need to take a completely different route and seek out how to put such applications in sandbox-like domains.

Related Articles

How to add swap space on Ubuntu 21.04 Operating System

How to add swap space on Ubuntu 21.04 Operating System

The swap space is a unique space on the disk that is used by the system when Physical RAM is full. When a Linux machine runout the RAM it use swap space to move inactive pages from RAM. Swap space can be created into Linux system in two ways, one we can create a...

read more

Lorem ipsum dolor sit amet consectetur

0 Comments

Submit a Comment

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

4 × two =