Assigning common policies to new applications – SELinux

July 06, 2021

In between the strong isolation of an SELinux sandbox and the broad permissions of unconfined domains (or even permissive domains) sits the sufficiently privileged application domain. For most administrators, having a proper SELinux domain for applications is the best way forward, as it allows all the common behaviors and restricts unwanted ones.

When we start looking at application domains, however, we notice that there is differentiation in complexity, and as an administrator, we need to understand what the complexity is about before we can make the right choice.

Understanding domain complexity

SELinux is able to provide full system confinement: each and every application runs in its own restricted environment that it cannot break out of. But that requires fine-grained policies that are developed as quickly as the new releases of all the applications they confine.

Developing fine-grained policies at this speed is not possible, so a balance has to be struck between the maintainability of a policy and the security of the domain. This balance is the policy design complexity or domain complexity, which can be roughly categorized as follows:

  • Fine-grained policies have separate, individual domains for each sub component of an application or service. Such policies have the advantage that they really attempt to restrict applications as much as possible. Through fine-grained policies, roles developed with users and administrators in mind become fine-grained as well, for instance, by differentiating sub-roles in the application.

    The disadvantage of such policies is that they are hard to maintain, requiring frequent updates as the application itself evolves. The policies also need to take into account the impact of the various configuration options that the application supports.

    Such fine-grained policies are not frequently found. An example is the policy set provided for the Postfix mail infrastructure. Each sub-service of the Postfix infrastructure has its own SELinux domain.

  • Application-level policies use a single domain for an application, regardless of its sub-components. This balances the requirement for application confinement versus the maintainability of the application and its SELinux policy.

    Such application-level policies are the most common in SELinux policies. They do still suffer from regular maintenance as applications expand their functionality, but the complexity of this is limited and SELinux policy developers should not have too many problems maintaining these policies.

  • Category-wide policies use a single domain definition for a set of applications that implement the same functionality. This is popular for services that act very similarly and whose user-role definitions can be described without really considering the application-specific nature.

    A good example of a category-wide policy is the policy for web servers. While this policy was initially written for the Apache HTTP daemon, the policy has become reusable for a number of web servers, such as the Cherokee, Hiawatha, Nginx, and Lighttpd projects.

    While such policies are easier to maintain, the downside of category-wide policies is that they often have more broad privileges than really needed. As more applications are joined in the category-wide policy, additional rules and privileges are added to support those specific functions.

  • Coarse-grained policies are used for applications or services whose behavior is hard to define. End user domains are examples of coarse-grained policies, as are unconfined domains.

When we are dealing with a new application, and we want to quickly assign a decent-enough policy, the most common method is to see whether a category-wide policy exists that we can reuse for the application.

Running applications in a specific policy

Let’s consider the situation for the pgpool-II application. When we install it without any additional changes, it will run with the unconfined_service_t domain, as mentioned in the Marking domains as permissive section. But perhaps we can find a suitable policy to run the pgpool-II application with, through which it is more confined.

As the pgpool-II solution is a load balance-like application for PostgreSQL databases, it is likely we can run it in the PostgreSQL domain. If there are no PostgreSQL databases running on the same system, then lending this domain for the pgpool-II application might not do much harm. Let’s see how well this goes:

  • The PostgreSQL policy uses the postgresql_exec_t SELinux type for its executables, so let’s assign that one to the pgpool binary:
# chcon -t postgresql_exec_t /usr/bin/pgpool
  • If we try to start the pgpool system service, we might get one or more failures:
# systemctl start pgpool
# systemctl status pgpool
WARNING: Failed to open status file at: "/var/log/pgpool/pgpool_status"
FATAL: could not read pid file
  • One of the failures mentioned is that the daemon cannot access its logs (in /var/log/pgpool) while another complains about the process ID file (in /var/run/pgpool) being unreachable. As these were previously created by an unconfined domain, it is indeed likely that their context is wrong as well. Let’s apply the PostgreSQL-specific types to these locations:
# chcon -R -t postgresql_log_t /var/log/pgpool
# chcon -R -t postgresql_var_run_t /var/run/pgpool
  • After restarting pgpool, we notice it has a new failure:
# systemctl start pgpool
# systemctl status pgpool
LOG: Setting up socket for ::1.9999
FATAL: failed to create INET domain socket
DETAIL: bind on socket failed with error "Permission denied"

This time, we get a permission failure, which most of the time implies that the SELinux policy is refusing a certain action:

# ausearch -i -m avc -ts recent
... avc: denied { name_bind } for pid=20065 comm=pgpool src=9999 scontext=system_u:system_r:postgresql_t:s0 tcontext=system_u:object_r:jboss_management_port_t:s0 tclass=tcp_socket

The denial seems to reflect the information displayed earlier: pgpool wants to listen on port 9999, but SELinux is refusing this.

  • Let’s create a small policy enhancement to allow postgresql_t to bind to this port:
(typeattributeset cil_gen_require jboss_management_port_t)
(typeattributeset cil_gen_require postgresql_t)
(allow postgresql_t jboss_management_port_t (tcp_socket (name_bind)))
  • Load this policy and restart pgpool. With this in place, pgpool starts up fine.

    Of course, having the daemon launch without problems does not mean that it will work without problems, so it is recommended to continue testing, using the service as intended.

Finding out which policy can be reused for a process requires a bit of practice and searching. For instance, you can query the policy for which domains are able to bind to the port that the daemon needs. Or you can search for a domain that has a behavior very similar to the application involved. In our example, we only had to allow the domain to bind to port 9999. We could also use this information point to seek a different policy—one that is allowed to bind to this port (such as the httpd_t domain) and see whether that one fits better.

While this approach is trial and error, it could allow running the service in a more confined domain than the unconfined domain would. A much better approach, however, is to generate a new, custom policy and work from there.

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


Submit a Comment

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

two × one =