Home » SELinux » Limiting the scope of transitions in SELinux

Limiting the scope of transitions in SELinux

Follow Us

Our Communities

For security reasons, Linux systems can reduce the ability of processes to gain elevated privileges under certain situations or provide additional constraints to reduce the likelihood of vulnerabilities to be exploitable. SELinux developers, too, honor these situations.

Sanitizing environments on transition

When we execute a higher-privileged command (be it a setuid application or one where capabilities are added to the session), the GNU C library (glibc) will sanitize the environment. This means that a set of security-sensitive environment variables are discarded to make sure that attackers, malicious persons, or malicious applications cannot negatively influence the session.

This secure execution is controlled through an Executable and Linkable Format (ELF) auxiliary vector called AT_SECURE. When set, environment variables such as LD_PRELOAD, LD_AUDIT, LD_DEBUG, TMPDIR, and NLSPATH are removed from the session.

SELinux will force this sanitation on domain transitions as well, ensuring that the newly executed domain does not have access to these sensitive environment variables. Of course, sometimes the transitioned domain requires these variables. Not all domains can deal with sanitized environments, or use these environment variables to pass along important information, so always dropping the environment variables might result in unusable application domains.

To allow transitions without sanitizing the environment, the noatsecure permission can be granted to domain transitions. For instance, let’s consider the execution of a Firefox plugin:

# sesearch -t mozilla_plugin_t -p noatsecure -A
allow unconfined_t mozilla_plugin_t:process { ... noatsecure ...};

When an application running in the unconfined_t domain executes the plugin (which results in a domain transition to mozilla_plugin_t), the environment variables need to be kept as otherwise the plugin might not function properly. As such, the SELinux policy grants the noatsecure permission to the domains that invoke Firefox plugins.

Disabling unconstrained transitions

A second security constraint that Linux supports is to mount a filesystem with the nosuid option. When set, no setuid and setgid binaries on that filesystem will have any effect on the effective user or group ID of the executing session. Essentially, a setuid application on a filesystem mounted with nosuid will act as if no setuid bit is set.

To ensure that transitions triggered by applications hosted on a nosuid-mounted filesystem do not allow for elevated privileges, SELinux policy developers must explicitly mark a transition as allowed for nosuid-mounted filesystems, using the nosuid_transition permission. This permission is part of the process2 class:

$ sesearch -s unconfined_t -p nosuid_transition -A
allow unconfined_t initrc_t:process2 { nnp_transition nosuid_transition };

This allows policy developers to differentiate regular domain transitions from nosuid-constrained domain transitions.


SELinux has a limit on the number of privileges that can be assigned to a class. When the number of privileges exceeds 32, the SELinux developers will create a different class and the permissions continue in this second class. Right now, the two classes that have more than 32 permissions are the capability class and the process class.

This permission-based approach might not be in place on all SELinux-enabled systems though. It is enabled when the nnp_nosuid_transition policy capability is defined and set to 1:

# cat /sys/fs/selinux/policy_capabilities/nnp_nosuid_transition

If this capability value is 0, then SELinux will use a concept called type bounds to support domain transitions for applications hosted on nosuid-mounted filesystems. Any executable with a file context that would result in a domain transition will only result in a domain transition if the target domain is bounded by the parent domain.


Policy capabilities cannot be tweaked by administrators. They are used by policy developers to inform the Linux kernel which behavior it expects. For the nnp_nosuid_transition capability, the policy developer informs the kernel that the nosuid_transition and nnp_transition permission checks should be used rather than bounded domains, and that its policy will generally only include support for the transitions and not for the bounded domains.

If it is not bounded, then the domain transition will not occur, and the session will remain in the current context (or the command will fail to execute if the application is not allowed to run in the current context).

A bounded domain is not just calculated live based on the permissions though. SELinux has an explicit rule that enforces a target domain to be bounded by a parent domain. Even when permissions are later added to the bounded domain, they will be denied by the SELinux security subsystem if they aren’t part of the parent domain.

With seinfo, these type bounds can be listed as follows:

# seinfo --typebounds

Most distributions, however, do not have bounded domains defined in their SELinux policy anymore, as the new nosuid_transition permission is much more flexible. The use of bounded domains required policy developers to extend the permissions of the parent domain every time the child domain needed to be extended, which was a major nuisance when the parent domain is a generic one (be it a container management platform or a system service daemon).

Using Linux’s NO_NEW_PRIVS

The use of filesystems mounted with nosuid is a specific case of Linux’s No New Privilege (NNP) support. NNP is a process-specific attribute that tells the Linux kernel that the process is no longer to be granted additional privileges. From that point onward, the constraints as mentioned before hold, and SELinux will only allow domain transitions if it has the nnp_transition permission, or toward a bounded domain if the nnp_nosuid_transition policy capability is not set.

The parameter can be set by applications themselves using the process control function prctl(), but the user can also influence this. The setpriv command can be used to launch applications with PR_SET_NO_NEW_PRIVS set (the parameter that applications can pass through the prctl() function).

As an example, create the following simple Python-based CGI script in a cgi-bin directory inside a regular user’s home directory:

#!/usr/bin/env python3
import sys, time
import subprocess
import cgi, cgitb
print('Content-Type: text/html;charset=utf-8\n')
PIPE = subprocess.PIPE
STDOUT = subprocess.STDOUT
pd = subprocess.Popen(['ping', '-c', '1', 'localhost'], 
stdout=PIPE, stderr=STDOUT)
while True:
  output = pd.stdout.read(1)
  if output == '' and pd.poll() != None:
  if output != '':

With this CGI script now available, first launch a simple CGI-capable web server (we will pick port 6020 as unprivileged users should be able to bind processes to this port) and connect to it:

$ python3 -m http.server --cgi 6020

In a different session, connect to the web server and call the newly created Python script (here named test.py):

$ curl http://localhost:6020/cgi-bin/test.py
PING localhost(localhost(::1)) 56 data bytes ...

Now, launch the same CGI-capable web server, but with NNP enabled:

$ setpriv --no-new-privs python3 -m http.server --cgi 6020

Again, connect to the web server and call the test.py CGI script:

$ curl http://localhost:6020/cgi-bin/test.py
ping: socket: Permission denied

Because Linux’s NNP is enabled, the ping command is not able to obtain the higher privileges needed to open the socket.

Sometimes, you’ll notice a denial for the execute_no_trans permission in the SELinux audit logs. This occurs when the SELinux policy does not allow an application to be executed without transitioning.


Submit a Comment

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

twelve − nine =