Limiting the Scope of Transitions in SELinux

Posted on the 10 June 2021 by Satish Kumar @satish_kumar86

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 weexecute a higher-privileged command (be it asetuidapplication or one where capabilities are added to the session), theGNU C library(glibc) willsanitize 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.

Thissecure execution is controlled through anExecutable and Linkable Format(ELF) auxiliaryvector calledAT_SECURE. When set, environment variables such asLD_PRELOAD,LD_AUDIT,LD_DEBUG,TMPDIR, andNLSPATHare 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, thenoatsecurepermission 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 secondsecurity constraint that Linux supports is to mount a filesystem with thenosuidoption. When set, nosetuidandsetgidbinaries on that filesystem will have any effect on the effective user or group ID of the executing session. Essentially, asetuidapplication on a filesystem mounted withnosuidwill act as if nosetuidbit is set.

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

$ 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 currentcontext (or the command will fail to execute if the application is not allowed to run in the current context).

Abounded domainis not just calculated live based on the permissions though. SELinux has anexplicit 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.

Withseinfo, 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 useof filesystems mounted withnosuidis a specific case of Linux’sNo New Privilege(NNP) support. NNP is a process-specificattribute 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 thennp_transitionpermission, or toward a bounded domain if thennp_nosuid_transitionpolicy capability is not set.

The parameter can be set by applications themselves using the process control functionprctl(), but the user can also influence this. Thesetprivcommand can be used to launch applications withPR_SET_NO_NEW_PRIVSset (the parameter that applications can pass through theprctl()function).

As an example, create the following simple Python-based CGI script in acgi-bindirectory 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 =
  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

$ curl http://localhost:6020/cgi-bin/
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 CGI script:

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

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

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

Back to Featured Articles on Logo Paperblog