SELinux

1. Introduction

Security-Enhanced Linux (SELinux) is a mandatory access control (MAC) security mechanism implemented in the kernel. SELinux was first introduced in CentOS 4 and significantly enhanced in later CentOS releases. These enhancements mean that content varies as to how to approach SELinux over time to solve problems.

1.1. Some of the Problems

In order to better understand why SELinux is important and what it can do for you, it is easiest to look at some examples. Without SELinux enabled, only traditional discretionary access control (DAC) methods such as file permissions or access control lists (ACLs) are used to control the file access of users. Users and programs alike are allowed to grant insecure file permissions to others or, conversely, to gain access to parts of the system that should not otherwise be necessary for normal operation. For example:

Administrators have no way to control users: A user could set world readable permissions on sensitive files such as ssh keys and the directory containing such keys, customarily: ~/.ssh/

Processes can change security properties: A user's mail files should be readable only by that user, but the mail client software has the ability to change them to be world readable

Processes inherit user's rights: Firefox, if compromised by a trojaned version, could read a user's private ssh keys even though it has no reason to do so.

Essentially under the traditional DAC model, there are two privilege levels, root and user, and no easy way to enforce a model of least-privilege. Many processes that are launched by root later drop their rights to run as a restricted user and some processes may be run in a chroot jail but all of these security methods are discretionary.

1.2. The Solution

SELinux follows the model of least-privilege more closely. By default under a strict enforcing setting, everything is denied and then a series of exceptions policies are written that give each element of the system (a service, program or user) only the access required to function. If a service, program or user subsequently tries to access or modify a file or resource not necessary for it to function, then access is denied and the action is logged.

Because SELinux is implemented within the kernel, individual applications do not need to be especially written or modified to work under SELinux although, of course, if written to watch for the error codes which SELinux returns, vide infra, might work better afterwards. If SELinux blocks an action, this is reported to the underlying application as a normal (or, at least, conventional) "access denied" type error to the application. Many applications, however, do not test all return codes on system calls and may return no message explaining the issue or may return in a misleading fashion.

Please note, however, that the hypothetical examples posed to provide possible greater safety of e.g., constraining programs authorized to a limited set of programs permitted to read a user's ~/.ssh/ directory, preventing a Mail Delivery Agent from tampering with group owernship or setting on group or other file read permissions, or a web browser being constrained from reading the user's home directory have not been implemented in SELinux policies accompanying any version of CentOS up to version 6. CentOS 6 and 7 have limited support for confining user programs as described above, but doesn't have as much coverage over user programs as targeted system daemons. If an admin wishes to change from the default unconfined login configuration, they can see the section below on Role-Based Access Control.

2. SELinux Modes

SELinux has three basic modes of operation, of which Enforcing is set as the installation default mode. There is, however, an additional qualifier of targeted or mls which control how pervasive SELinux rules are applied, with targeted being the less stringent level.

Enforcing: The default mode which will enable and enforce the SELinux security policy on the system, denying access and logging actions

Permissive: In Permissive mode, SELinux is enabled but will not enforce the security policy, only warn and log actions. Permissive mode is useful for troubleshooting SELinux issues

Disabled: SELinux is turned off

The SELinux mode can be viewed and changed by using the SELinux Management GUI tool available on the Administration menu or from the command line by running 'system-config-selinux' (the SELinux Management GUI tool is part of the policycoreutils-gui package and is not installed by default).

Users who prefer the command line may use the 'sestatus' command to view the current SELinux status:

# sestatus SELinux status: enabled SELinuxfs mount: /selinux Current mode: enforcing Mode from config file: enforcing Policy version: 21 Policy from config file: targeted

The 'setenforce' command may be used to switch between Enforcing and Permissive modes on the fly but note that these changes do not persist through a system reboot.

To make changes persistent through a system reboot, edit the 'SELINUX=' line in /etc/selinux/config for either 'enforcing', 'permissive', or 'disabled'. For example: 'SELINUX=permissive'

Note: When switching from Disabled to either Permissive or Enforcing mode, it is highly recommended that the system be rebooted and the filesystem relabeled.

3. SELinux Policy

As noted, SELinux follows the model of least-privilege; by default everything is denied and then a policy is written that gives each element of the system only the access required to function. This description best describes the strict policy. However, such a policy is difficult to write that would be suitable in the wide range of circumstances that a product such as Enterprise Linux is likely to be used. The end result is that SELinux is likely to cause problems for system administrators and end users and rather than resolve these issues, system administrators may just disable SELinux thereby defeating the built-in protections.

By design, SELinux allows different policies to be written that are interchangeable. The default policy in CentOS is the targeted policy which "targets" and confines selected system processes. In CentOS 4 only 15 defined targets existed (including httpd, named, dhcpd, mysqld). Later, in CentOS 5 this number had risen to over 200 targets.

All other system processes and all remaining userspace programs, as well as any in-house applications, that is everything else on the system, runs in an unconfined domain and is not covered by the SELinux protection model.

One goal might be for every process that is installed and, by default, running at boot should be run in a confined domain. The targeted policy is designed to protect as many key processes as possible without adversely affecting the end user experience and most users should be totally unaware that SELinux is even running.

4. SELinux Access Control

The targeted SELinux policy on CentOS ships with 4 forms of access control:

Type Enforcement (TE): Type Enforcement is the primary mechanism of access control used in the targeted policy

Role-Based Access Control (RBAC): Based around SELinux users (not necessarily the same as the Linux user), but not used in the default configuration of the targeted policy

Multi-Level Security (MLS): Not commonly used and often hidden in the default targeted policy.

Multi-Category Security(MCS): An extension of Multi-Level Security, used in the targeted policy to implement compartmentalization of virtual machines and containers through sVirt.

All processes and files have an SELinux security context. Let's see these in action by looking at the SELinux security context of the Apache homepage: '/var/www/html/index.html' {{{$ ls -Z /var/www/html/index.html -rw-r--r-- username username system_u:object_r:httpd_sys_content_t /var/www/html/index.html }}}

Note: The -Z switch will work with most utilities to show SELinux security contexts (e.g, 'ls -Z', 'ps axZ' etc).

In addition to the standard file permissions and ownership, we can see the SELinux security context fields: system_u:object_r:httpd_sys_content_t.

This is based upon user:role:type:mls. In our example above, user:role:type fields are displayed and mls is hidden. Within the default targeted policy, type is the important field used to implement Type Enforcement, in this case httpd_sys_content_t.

Now consider the SELinux security context of the Apache web server process: 'httpd'

$ ps axZ | grep httpd system_u:system_r:httpd_t 3234 ? Ss 0:00 /usr/sbin/httpd

Here we see the from the type field that Apache is running under the httpd_t type domain.

Finally, let's look at the SELinux security context of a file in our home directory:

$ ls -Z /home/username/myfile.txt -rw-r--r-- username username user_u:object_r:user_home_t /home/username/myfile.txt

where we see the type is user_home_t, the default type for files in a user's home directory.

Access is only allowed between similar types, so Apache running as httpd_t can read /var/www/html/index.html of type httpd_sys_content_t. Because Apache runs in the httpd_t domain and does not have the userid:username, it can not access /home/username/myfile.txt even though this file is world readable because /home/username/myfile.txt SELinux security context is not of type httpd_t. If Apache were to be exploited, assuming for the sake of this example that the root account right needed to effect a SELinux re-labeling into another context were not obtained, it would not be able to start any process not in the httpd_t domain (which prevents escalation of privileges) or access any file not in an httpd_t related domain.

4.1. Role-Based Access Control (RBAC)

Although the default configuration of the targeted policy is to use unconfined logins, the administrator can quite easily switch to the Role-Based Access Control model. This model also switches to 'strict' mode for user domains, to allow targeting each program individually. To enable this, use semanage-login to add a login mapping for your user.

semanage login -a -s "staff_u" -r "s0-s0:c0.c1023" <username>

The semanage-login command maps a Linux username to an SELinux user named "staff_u", with an MLS/MCS range of "s0-s0:c0.c1023". After this, logging in will result in id -Z returning staff_u:staff_r:staff_t:s0-s0:c0.c1023 opposed to unconfined_u:unconfined_r:unconfined_t:s0 . Though staff_r is not a role meant for administration, it is a role that allows the user to change to other roles. When an admin would like to do system administration tasks they should switch to the sysadm_r role using the -r flag in sudo,

sudo -r sysadm_r -i

This can be automated by adding a configuration file under /etc/sudoers.d/, to map the user to a default admin role.

%wheel ALL=(ALL) TYPE=sysadm_t ROLE=sysadm_r ALL

It is still possible to login as an unconfined user or switch to the unconfined role via newrole, although the benefits of confined user domains are then lost. It is also possible to remove the ability to do this by creating a new SELinux user associated with only a select set of roles,

semanage user -a -R "staff_r sysadm_r system_r -r "s0-s0:c0.c1023" my_staff_u

Then substituting staff_u for my_staff_u in the semanage-login command. Now attempting to switch to the unconfined_r role will result in an AVC and SELINUX_ERR message. If the admin wishes to remove the ability to login as an unconfined user completely, they should remap the __default__ login to a more suitable SELinux user, again using semanage-login.

semanage login -m -s "user_u" -r "s0" __default__

If a user wishes to login as a role other than their default it is up to the login program to provide this functionality. SSH allows logging in with an alternative SELinux role by specifying it as part of the login identifier (e.g., as a staff user logging in as unconfined_r).

ssh <username>/unconfined_r@hostname.net

The strict model that comes with Role-Based Access Control isn't perfect from a perspective of least privilege; running a quick search using policy analysis tools we can see that several confined programs can still read a users private SSH keys.

sesearch -ACS -t ssh_home_t -c file -p read Found 132 semantic av rules: allow snapperd_t file_type : file { ioctl read getattr lock open } ; allow oddjob_mkhomedir_t user_home_type : file { ioctl read write create getattr setattr lock append unlink link rename open } ; allow mplayer_t non_security_file_type : file { ioctl read getattr lock open } ; allow sendmail_t user_home_type : file { ioctl read getattr lock open } ; allow systemd_tmpfiles_t non_auth_file_type : file { ioctl read write create getattr setattr lock relabelfrom relabelto append unlink link rename open } ; allow login_pgm ssh_home_t : file { ioctl read getattr lock open } ; allow ssh_keygen_t ssh_home_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; allow colord_t user_home_type : file { read getattr } ; ... snip ...

mplayer_t likely doesn't need to read SSH private keys, but it is granted access to that transiently by being allowed to read types of content associated with non_security_file_type that is allowing mplayer to read any content that isn't security related so the user can play multimedia from anywhere on the filesystem. This could be further constrained in the base SELinux policy, but as previously mentioned is not a major focus for the Upstream Vendor.

Beyond the strict model, Role-Based Access Control also provides a mechanism for limiting the scope of what a user can do when they use sudo to switch to root. It is often desirable to enforce least privilege on users with specific roles like DBAs or auditors and the targeted policy includes several user roles for purposes like those, with documentation in their respective manual pages as mentioned in Policy Documentation.

seinfo -r Roles: 14 auditadm_r dbadm_r guest_r staff_r user_r logadm_r object_r secadm_r sysadm_r system_r webadm_r xguest_r nx_server_r unconfined_r

To map a user to one of these admin roles, the same semanage-user command is used as before to create a new SELinux user associated with the desired roles, and then semanage-login to associate the Linux login with the SELinux user. If the user should also be able to start system daemons they administrate from their user domain (i.e., to start mysql as dbadm_r for debugging from a shell) the system_r role should be included in their list of associated roles.

semanage user -a -R "staff_r system_r auditadm_r" -r "s0-s0:c0.c1023" auditor_u semanage login -a -s "auditor_u" -r "s0-s0:c0.c1023" <username>

4.2. Multi-Category Security (MCS)

Multi-Category Security provides a way to associate a set or range of compartments with SELinux contexts. The targeted policy model implements compartmentalization of types associated with mcs_constrained_type . To understand how this works, it's required to know how to inspect the MLS part of security contexts. This is the part after the user:role:type section, and includes a range, which indicates a low and high security level.

system_u:system_r:httpd_t:s0 - s0:c0.c5 ▼ ▼ Low security level, High security level, also associated with no associated with compartments compartments. c0, c1, c2, c3, c4 and c5.

One thing that is noticeable above is the lack of compartments on the low security level, as well as both security levels being the same. The first point is an implementation detail of the MCS model in the targeted policy. When an access vector is computed for a process that is associated with mcs_constrained_type , only the MCS compartments of the high level are compared. The second point is due to the fact that MLS is not in use.

The compartment part of the above security context is a category range, but can also be a set of categories separated by commas. A range of categories results in the context being associated with an inclusive set of categories in that range. Understanding how access is computed for two processes with a set of categories requires looking at the dominance rules for SELinux security levels (access is only allowed if the source type's high security level dominates the target type's high security level). Those rules are as follows (only accounting for categories, and not MLS security levels)

Source dominates the target if the categories in the source context are the same as or a superset of those in the target context.

Source is dominated by the target if the categories in the source context are a subset of the categories of the target context.

Source and target are equal and dominate each other if the set of categories are the same in each context.

With that in mind, we know that a context with a category set of c0.c5 will be granted access to a context with a category set of c0,c3 , but not a category set of c0,c6 , or c0.c1023 . This rule is the reason that sVirt generates a random set of categories, so there will be no overlap where one virt domain will dominate another. The Android project also does the same thing, to put applications in isolated domains.

An example use of Multi-Category Security could be using NGINX with multiple vhosts that connect to backend servers that are also running as httpd domains (e.g., PHP-FPM). Normally these instances of the backend servers would be able to modify and manage each others domains simply due to type-enforcement rules. If they're associated with categories, they can only do so if one backend server dominates the other. Since NGINX itself is a HTTPD domain, it should dominate all backend servers, so if we have categories c0 through c5 available for HTTPD domains we would want to run NGINX as system_u:system_r:httpd_t:s0-s0:c0.c5, so it could connect to the upstream servers. Each backend server would run with a single category within c0-c5, and a context such as system_u:system_r:httpd_t:s0-s0:c1 .

There are a couple of presequities to achieving this. First, httpd_t must be associated with the mcs_constrained_type attribute that is currently only associated with the following types on CentOS 7:

seinfo -xamcs_constrained_type mcs_constrained_type netlabel_peer_t openshift_t openshift_app_t sandbox_min_t sandbox_x_t sandbox_web_t sandbox_net_t svirt_t svirt_tcg_t svirt_lxc_net_t svirt_qemu_net_t svirt_kvm_net_t

To add to this list of types it is necessary to create a local policy module that associates the desired type with the attribute. This is done using the typeattribute statement, and can be done like so:

policy_module(httpd_mcs, 1.0) gen_require(` type httpd_t; attribute mcs_constrained_type; ') typeattribute httpd_t mcs_constrained_type;

See Customizing Local Policy for instructions on building policy modules.

Once the type is associated with mcs_constrained_type each backend server must have their content relabeled to include the respective categories in their file context specifications. This can be achieved by adding the file types to /etc/selinux/targeted/contexts/customizable_types , but this can potentially break on a policy upgrade. An alternative is to add new file contexts specifications that include categories using semanage-fcontext:

semanage fcontext -a -t httpd_sys_content_t -r "s0-s0:c1" "/srv/backend1(/.*)?" semanage fcontext -a -t httpd_sys_content_t -r "s0-s0:c2" "/srv/backend2(/.*)?"

The next step is making sure the backend servers are started with the correct security context. On CentOS 7 with systemd this can be achieved with the SELinuxContext= directive in the unit file, and in previous versions can be achieved using the runcon command.

runcon "system_u:system_r:httpd_t:s0-s0:c1" "/usr/local/bin/backend-server"

Or in the systemd unit:

SELinuxContext=system_u:system_r:httpd_t:s0-s0:c1

Now each backend server should be isolated from the other, while allowing NGINX access to manage and send messages to both of them.

5. Troubleshooting SELinux

Sooner or later you may run into situations where SELinux denies access to something and you need to troubleshoot the issue. There are a number of fundamental reasons why SELinux may deny access to a file, process or resource:

A mislabeled file.

A process running under the wrong SELinux security context.

A bug in policy. An application requires access to a file that wasn't anticipated when the policy was written and generates an error.

An intrusion attempt.

The first 3 we can deal with, whereas giving alarm and notice in the 4th case is exactly the intended behaviour.

To troubleshoot any issue, the log files are key and SELinux is no different. By default SELinux log messages are written to /var/log/audit/audit.log via the Linux Auditing System auditd , which is started by default. If the auditd daemon is not running, then messages are written to /var/log/messages . SELinux log messages are labeled with the "AVC" keyword so that they might be easily filtered from other messages, as with grep.

Starting with CentOS 5 the SELinux Troubleshooting tool can be used to help analyze log files converting them into a more human-readable format. The tool consists of a GUI tool for displaying messages in human-readable format and possible solutions, a desktop notification icon alerting of new issues and a daemon process, setroubleshootd , that checks for new SELinux AVC alerts and feeds the notification icon. Email notifications may also be configured, as for those not running an X server. The SELinux Troubleshooting tool is provided by the setroubleshoot package. The tool may be launched from the X Window GUI manager System menu or from the command line:

sealert -b

Those not running an X server may generate human-readable reports from the command line:

sealert -a /var/log/audit/audit.log > /path/to/mylogfile.txt

5.1. SELinux And Auditd

As described above, SELinux interacts with auditd to generate messages that aid in both auditing and troubleshooting of a system. The most common types of messages seen in the audit log from SELinux are AVCs. They describe operations that were denied (or allowed) by the SELinux security server. Although AVC messages are the most common, they aren't the only types of messages generated by SELinux and sent to the audit subsystem.

Auditd Record Type Description AVC Messages that are generated by the kernel whenever access to an operation is granted or denied. Audit messages can be generated when access is granted by using auditallow policy rules, and messages can also be prevented from being logged when access is denied by using dontaudit rules. USER_AVC Similar to AVC messages, but are generated by userspace programs that use the SELinux security server. These programs are known as userspace object managers, and includes D-Bus and systemd. SELINUX_ERR An error emitted by the SELinux security server in cases where an operation fails and an AVC doesn't sufficiently describe the issue. This typically occurs when systemd tries to transition to a service that has NoNewPrivileges=True in it's unit file, but does not have type bounds in the SELinux policy between systemd and its own type. This specific issue can also occur if a process wishes to setcon(3) to change the context of one of its threads. It can also occur when a program tries to set an invalid context, e.g., PAM uses setcon(3) to change to the login users domain and misconfigured SELinux logins can result in an incorrect context being returned from SELinux userspace. USER_SELINUX_ERR Similar to SELINUX_ERR, messages of this type are logged when a userspace object manager experiences an SELinux related error. MAC_POLICY_LOAD, USER_MAC_POLICY_LOAD A message that is logged when a SELinux policy is loaded. The USER variant is also emitted by userspace object managers to notify that they have processed the policy load. MAC_CONFIG_CHANGE A message that is logged when an administrator changes the value of an SELinux boolean using setsebool. MAC_STATUS When the status of SELinux is changed from enforcing to permissive or vice versa. USER_ROLE_CHANGE A message that is logged when a user changes role via the newrole(1) command, at login time, or by using sudo. Only applicable to Role-Based Access Control. USER_LABEL_EXPORT Logged whenever a user exports a labeled object using CUPS.

NOTE: There are several other SELinux related audit events that are used in IPSec/NetLabel that are not covered here at this time. Those events are MAC_UNLBL_ALLOW, MAC_UNLBL_STCADD, MAC_UNLBL_STCDEL, MAC_MAP_ADD, MAC_MAP_DEL, MAC_IPSEC_EVENT, MAC_CIPSOV4_ADD, MAC_CIPSOV4_DEL.

While sealert can be slightly useful for interpreting AVC records, the audit tools can give the admin a more powerful view of the audit log. ausearch can be used to search for specific events in the audit log, and has a variety of options available for working with audit records. So instead of interpreting messages using sealert , it is possible to examine any potential causes of problems from SELinux using ausearch . The types we usually want to look at when troubleshooting a problem are AVC, USER_AVC, SELINUX_ERR, and USER_SELINUX_ERR. ausearch provides an -m option that takes a comma-separated list of audit record types to filter by, as well as an -i flag that causes numeric values to be interpreted into strings depending on the system. This includes converting UID/GID's to user and group names, as well as mapping a syscall and architecture pair to the syscall name:

ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i

Searching for records and creating a checkpoint, to ensure that audit records that have already been seen don't show up again on the next search:

ausearch --checkpoint="./audit-checkpoint" -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i

Searching for records based on the processes comm name, this is the executable name from the task in the kernel:

ausearch -c "httpd" -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -i

Finding events where an admin (or program) changed the enforcing state of SELinux:

ausearch -m MAC_STATUS -i

Since it is possible to prevent audit records from being logged using dontaudit, it is possible that not all AVC or USER_AVC messages will be seen. To prevent dontaudit rules from hiding these messages the administrator can run semodule -DB to rebuild their SELinux policy without the dontaudit rules. After reproducing the issue there should be more messages available than before, as well as some records that aren't relevant to the problem (noatsecure, rlimitinh, and siginh are permissions that are always checked when a program is executed and can generally be ignored). When finished with reviewing records prevented from dontaudit rules, run semodule -B to rebuild the policy with dontaudit roles included again.

The inverse of this is the ability to log operations that are successful using auditallow. These logs can be searched for by filtering by successful operations (though this will also return successful events in permissive mode):

ausearch -m AVC,USER_AVC -i --success yes

5.2. Relabeling Files

The 'chcon' command may be used to change SELinux security context of a file or files/directories in a similar way to how 'chown' or 'chmod' may be used to change the ownership or standard file permissions of a file.

Let's look at some examples.

Using Apache as an example, suppose you want to change the DocumentRoot to serve web pages from a location other than the default /var/www/html/ directory. Assume we create a directory (or maybe a mount point) at /html/ and create an index.html file there:

# mkdir /html # touch /html/index.html # ls -Z /html/index.html -rw-r--r-- root root user_u:object_r:default_t /html/index.html # ls -Z | grep html drwxr-xr-x root root user_u:object_r:default_t html

We see that both the directory /html/ and file /html/index.html have the security context type: default_t. If we start our web browser and try to view the page, SELinux will properly deny access and log the error because the directory and file(s) have the wrong security context. We need to set the correct security context type for Apache of: httpd_sys_content_t.

# chcon -v --type=httpd_sys_content_t /html context of /html changed to user_u:object_r:httpd_sys_content_t # chcon -v --type=httpd_sys_content_t /html/index.html context of /html/index.html changed to user_u:object_r:httpd_sys_content_t # ls -Z /html/index.html -rw-r--r-- root root user_u:object_r:httpd_sys_content_t /html/index.html # ls -Z | grep html drwxr-xr-x root root user_u:object_r:httpd_sys_content_t html

Equally we could have set both in one go using the -R recursive switch:

# chcon -Rv --type=httpd_sys_content_t /html

Modifying security contexts in this manner will persist between system reboots but only until the modified portion of the filesystem is relabeled. This is a not uncommon operation and the proper solution, after testing, is to write a local custom rule (a so-called Policy Module) and merge it into the base local rules. This will be an additional rule on top of the 200+ rules mentioned above. To make the security context changes permanent, even through a complete filesystem relabel, we can use the SELinux Management Tool or the 'semanage' command from the command line:

semanage fcontext -a -t httpd_sys_content_t "/html(/.*)?"

to add a file context of type httpd_sys_content_t for everything under /html.

5.3. Restore Default Security Contexts

The 'restorecon' command may be used to restore file(s) default SELinux security contexts.

Again, let's use Apache as an example. Suppose a user edits a copy of index.html in his/her home directory and moves (mv) the file to the DocumentRoot /var/www/html. Whilst the copy (cp) command will typically adopt the destination directory's or file's security context, move (mv) will maintain the source's security context. We could use the 'chcon' command to change the security context of the file(s) in question but as the file(s) are now in the default Apache DocumentRoot (/var/www/html) we can just restore the default security contexts for that directory or file(s). To restore just the index.html file, we would use:

# restorecon -v /var/www/html/index.html

or to recursively restore the default security contexts for the whole directory:

# restorecon -Rv /var/www/html

Additionally, if we simply wanted to examine the security contexts of the /var/www/html directory to see if any files needed their security contexts restored, we can use restorecon with the -n switch to prevent any relabelling occurring:

# restorecon -Rv -n /var/www/html

In some cases it might also be the case that the user has moved files with a type that is listed in /etc/selinux/targeted/contexts/customizable_types . These types are treated as special by restorecon , in that they won't usually be relabeled by restorecon unless the extra -F flag is passed. This is because these types are either associated with categories (as mentioned in Multi-Category Security), or they are user content types that the user is allowed to customize. To relabel content that has a customizable type associated with it, run restorecon as above with the extra flag:

# restorecon -RvF /var/www/html

5.4. Relabel Complete Filesystem

Sometimes it is necessary to relabel the complete filesystem although this should only be necessary when enabling SELinux after it has been disabled or when changing the SELinux policy from the default targeted policy to strict. To automatically relabel the complete filesystem upon reboot, do:

# touch /.autorelabel # reboot

Sometimes a complete filesystem relabel will fail if the system has been upgraded to CentOS-5.2 with SELinux disabled, and SELinux is then enabled. If the above procedure doesn't correctly perform a complete filesystem relabel, try issuing the 'genhomedircon' command first:

# genhomedircon # touch /.autorelabel # reboot

5.5. Allowing Access to a Port

We may want a service such as Apache to be allowed to bind and listen for incoming connections on a non-standard port. By default, the SELinux policy will only allow services access to recognized ports associated with those services. If we wanted to allow Apache to listen on tcp port 81, we can add a rule to allow that using the 'semanage' command:

# semanage port -a -t http_port_t -p tcp 81

A full list of ports that services are permitted access by SELinux can be obtained with:

# semanage port -l

5.6. Gathering Audit Logs In Permissive Mode

When a program is being denied an operation repeatedly by SELinux, it is sometimes easier to continue debugging while in permissive mode. The usual operation for this is setenforce 0 , however that puts all domains on the system into permissive mode rather than just the domain of the process encountering an issue. To avoid this, SELinux supports the concept of permissive types, allowing the administrator to put just a single domain into permissive mode rather than the entire system.

To do this based on an audit log entry, look at the type in the context of the scontext field:

type=AVC msg=audit(1218128130.653:334): avc: denied { connectto } for pid=9111 comm="smtpd" path="/var/spool/postfix/postgrey/socket" scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:system_r:initrc_t:s0 tclass=unix_stream_socket

Here it is: postfix_smtpd_t . To temporarily grant access to any operation this domain wants we can use semanage to add a new permissive type:

semanage permissive -a postfix_smtpd_t

Now we can watch the audit logs to see what postfix_smtpd_t needs to be allowed to do in order to succesfully operate. Once we're finished we can put that type back into enforcing mode:

semanage permissive -d postfix_smtpd_t

This approach doesn't suffer from the heavy-handedness of setenforce 0 and allows all other services on the system to keep benefiting from the access controls of SELinux.

Quite often when encountering SELinux denials it will be the case that the operation that is denied is actually allowed in policy, but wasn't permitted due to a file not being labeled correctly or a process not transitioning to the correct domain. Issues like these are best reported to the policy authors and maintainers, but are not impossible to figure out using the analysis tools provided by the setools-console package.

For an AVC record in the audit log we can use sesearch to identify any type-enforcement rules that permit the requested access to the target, as well as any rules that are toggled by booleans that would enable the access. The important parts of the AVC record message for us to look at are the scontext, tcontext, tclass fields as well as the requested permission in the denied: <permission> message.

Using an example AVC denial record with scontext=antivirus_t , tcontext=antivirus_t , tclass=process and a message of { denied: execmem } that is likely due to an anti-virus program wishing to do JIT compilation, we can identify any type-enforcement rules dependant on booleans that could allow the requested access:

sesearch -AC -s antivirus_t -t antivirus_t -c process -p execmem Found 2 semantic av rules: DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ] DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ]

Note: The options in use here are documented in the sesearch(1) manual. -s and -t specify source and target types, -c specifies an SELinux objet class, -p can be specified multiple times to search for rules with those permissions, while -A searches for allow rules and -C expands that to search for conditional allow rules.

This was an easy one. The identifier in square brackets is the name of the boolean that would allow this access, and the DT prefixing the rule indiciates it is currently disabled. We can turn this on using setsebool -P antivirus_use_jit=1 , but we might also want to inspect exactly what this boolean is allowing first, and the same sesearch utility lets us do that:

sesearch -AC -b antivirus_use_jit Found 2 semantic av rules: DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ] DT allow antivirus_t antivirus_t : process execmem ; [ antivirus_use_jit ]

In this case the antivirus_use_jit boolean only allows exactly what we want, but in the future we could use sesearch like this to discern between 2 booleans that allow the same access, where one of them might also allow other undesirable operations. An example of this is where we have HTTPD content labeled as httpd_sys_content_t that we wish to write to. A recommendation for this might be to enable the httpd_unified boolean, so we can look at what that would do for us:

sesearch -AC -b httpd_unified -c file -p write Found 5 semantic av rules: DT allow httpd_sys_script_t httpdcontent : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && ] DT allow httpd_user_script_t httpd_user_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && ] DT allow httpd_t httpd_sys_rw_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ] DT allow httpd_user_script_t httpd_user_ra_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && ] DT allow httpd_t httpdcontent : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ]

There are only a few rules associated with this boolean related to writing to files, so it is easy to analyze what it allows. The above rules tell us that while it allows writing to this the file the AVC record is for, it also allows r/w access to previously append-only and read-only content. We can see above that there is a httpd_sys_rw_content_t type that, based on the name, would likely solve the problem. However, we could also identify on our own what types of file that httpd_t is allowed to write to. Note that there may be a lot of types that httpd is allowed to write to transiently via attributes, or disabled booleans so we use the -R flag that indicates that the symbol names we're providing are regular expressions and filter the target types by types starting with httpd .

sesearch -ACR -s httpd_t -t "httpd.*" -c file -p write DT allow httpd_t httpd_sys_rw_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ] ET allow httpd_t httpd_sys_rw_content_t : file { ioctl read write create getattr setattr lock append unlink link rename open } ; [ httpd_builtin_scripting ] DT allow httpd_t httpd_sys_rw_content_t : lnk_file { ioctl read write create getattr setattr lock append unlink link rename } ; [ httpd_enable_cgi httpd_unified && httpd_builtin_scripting && ] ET allow httpd_t httpd_sys_rw_content_t : lnk_file { ioctl read write create getattr setattr lock append unlink link rename } ; [ httpd_builtin_scripting ] ET allow httpd_t httpd_sys_rw_content_t : sock_file { read write getattr append open } ; [ httpd_builtin_scripting ]

A couple of these rules are disabled, but 3 of them are enabled by default. So based on that we can conclude that the type for content that is read-write by HTTPD domains should be httpd_sys_rw_content_t . Using this knowledge, we can follow the same steps to figure out what domains are allowed access to other target types to assist in identifying programs that are running with the wrong context.

6. Customizing SELinux Policies

Minor modifications to SELinux policies can be made without modifying and recompiling the policy source by setting boolean values for optional features. Such features include allowing users to share their home directories under Samba or allowing Apache to serve files from users home directories that would otherwise be denied by the SELinux policy.

There is a separate Wiki page dealing with booleans.

7. Creating Custom SELinux Policy Modules with audit2allow

Sometimes there are occasions when none of the above methods deal with a given situation and we need to extend the SELinux policy by creating a custom policy module to allow for a certain set of conditions. For example, consider the postgrey service add-on for an smtp mail server. Our smtp server needs to communicate with postgrey over a Unix socket and that is something the default SELinux policy for our smtp server does not allow. Consequently the service is blocked by SELinux. This is an issue that can not be fixed by changing or restoring file type security contexts and isn't something that has a boolean value we can toggle to allow. We could disable SELinux protection of the smtp server through a boolean, which would be better than disabling SELinux completely, but that is still far from ideal.

If we switch SELinux into Permissive mode and run our mail server for a set period of time, we can log SELinux issues whilst still permitting access (as mentioned in Gathering Audit Logs in Permissive Mode). Checking our logs, we see the following SELinux AVC messages:

type=AVC msg=audit(1218128130.653:334): avc: denied { connectto } for pid=9111 comm="smtpd" path="/var/spool/postfix/postgrey/socket" scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:system_r:initrc_t:s0 tclass=unix_stream_socket type=AVC msg=audit(1218128130.653:334): avc: denied { write } for pid=9111 comm="smtpd" name="socket" dev=sda6 ino=39977017 scontext=system_u:system_r:postfix_smtpd_t:s0 tcontext=system_u:object_r:postfix_spool_t:s0 tclass=sock_file

Then we can use 'audit2allow' to generate a set of policy rules that would allow the required actions. We can generate a local postgrey Type Enforcement policy file (postgreylocal.te):

# grep smtpd_t /var/log/audit/audit.log | audit2allow -m postgreylocal > postgreylocal.te # cat postgreylocal.te module postgreylocal 1.0; require { type postfix_smtpd_t; type postfix_spool_t; type initrc_t; class sock_file write; class unix_stream_socket connectto; } #============= postfix_smtpd_t ============== allow postfix_smtpd_t initrc_t:unix_stream_socket connectto; allow postfix_smtpd_t postfix_spool_t:sock_file write;

Above we see that we can grep the audit.log file for issues relating to our smtp server and pipe those issues to audit2allow which generates a set of rules that it thinks would permit the actions currently denied by the SELinux policy. Reviewing these rules we see our smtp server wants to connect and write to a Unix socket which we see from out logs is the Unix socket that the postgrey service is listening on. As this seems perfectly reasonable, we can go ahead and use audit2allow to make a custom policy module to allow these actions:

# grep smtpd_t /var/log/audit/audit.log | audit2allow -M postgreylocal

We then load our postgrey policy module using the 'semodule' command into the current SELinux policy:

semodule -i postgreylocal.pp

which will add our postgrey policy module to /etc/selinux/targeted/modules/active/modules/postgreylocal.pp. We can check the policy module loaded correctly by listing loaded modules with 'semodule -l'.

We can then continue to monitor our SELinux log files to check that our custom policy module works and once we are satisfied we can re-enable SELinux Enforcing mode and again benefit from SELinux protection of our now fully functional smtp server.

7.1. Manually Customizing Policy Modules

Often audit2allow will automatically create a custom policy module that will resolve a particular issue, but there are times when it doesn't get it quite right and we may want to manually edit and compile the policy module. For example, consider the following AVC audit log:

Summary: SELinux is preventing postdrop (postfix_postdrop_t) "getattr" to /var/log/httpd/error_log (httpd_log_t). Detailed Description: SELinux denied access requested by postdrop. It is not expected that this access is required by postdrop and this access may signal an intrusion attempt. It is also possible that the specific version or configuration of the application is causing it to require additional access. Allowing Access: Sometimes labeling problems can cause SELinux denials. You could try to restore the default system file context for /var/log/httpd/error_log, restorecon -v '/var/log/httpd/error_log' If this does not work, there is currently no automatic way to allow this access. Instead, you can generate a local policy module to allow this access - see FAQ (http://fedora.redhat.com/docs/selinux-faq-fc5/#id2961385) Or you can disable SELinux protection altogether. Disabling SELinux protection is not recommended. Please file a bug report (http://bugzilla.redhat.com/bugzilla/enter_bug.cgi) against this package. Additional Information: Source Context system_u:system_r:postfix_postdrop_t Target Context root:object_r:httpd_log_t Target Objects /var/log/httpd/error_log [ file ] Source postdrop Source Path /usr/sbin/postdrop Port <Unknown> Host sanitized Source RPM Packages postfix-2.3.3-2 Target RPM Packages Policy RPM selinux-policy-2.4.6-137.1.el5 Selinux Enabled True Policy Type targeted MLS Enabled True Enforcing Mode Enforcing Plugin Name catchall_file Host Name sanitized Platform Linux sanitized 2.6.18-53.1.21.el5 #1 SMP Tue May 20 09:35:07 EDT 2008 x86_64 x86_64 Alert Count 599 First Seen Wed Jul 2 08:27:15 2008 Last Seen Sun Aug 10 22:47:52 2008 Local ID c303a4ea-8e7a-4acc-9118-9cc61c6a2ec8 Line Numbers Raw Audit Messages host=sanitized type=AVC msg=audit(1218397672.372:352): avc: denied { getattr } for pid=4262 comm="postdrop" path="/var/log/httpd/error_log" dev=md2 ino=117005 scontext=system_u:system_r:postfix_postdrop_t:s0 tcontext=root:object_r:httpd_log_t:s0 tclass=file host=sanitized type=SYSCALL msg=audit(1218397672.372:352): arch=c000003e syscall=5 success=no exit=-13 a0=2 a1=7fffd6febca0 a2=7fffd6febca0 a3=0 items=0 ppid=4261 pid=4262 auid=4294967295 uid=48 gid=48 euid=48 suid=48 fsuid=48 egid=90 sgid=90 fsgid=90 tty=(none) comm="postdrop" exe="/usr/sbin/postdrop" subj=system_u:system_r:postfix_postdrop_t:s0 key=(null)

Running audit2allow on the above error, and reviewing the resultant postfixlocal.te policy file we see:

# grep postdrop /var/log/audit/audit.log | audit2allow -M postfixlocal # cat postfixlocal.te module postfixlocal 1.0; require { type httpd_log_t; type postfix_postdrop_t; class dir getattr; class file { read getattr }; } #============= postfix_postdrop_t ============== allow postfix_postdrop_t httpd_log_t:file getattr;

Hopefully the first thing to strike us here is why does postdrop needs access to /var/log/httpd/error_log? Presumably this isn't something we would expect so we have to assess if this is an action we wish to allow or not. We have a number of options. We could just ignore the error and allow SELinux to continue blocking and logging access attempts or we could allow the action by creating the custom policy module as suggested by audit2allow. Alternatively we can edit the custom policy module .te file to prevent auditing of this particular error whilst still allowing SELinux to continue preventing access. We do this by editing the allow line, changing it to dontaudit:

#============= postfix_postdrop_t ============== dontaudit postfix_postdrop_t httpd_log_t:file getattr;

Now we can manually compile and load the edited custom policy module:

# checkmodule -M -m -o postfixlocal.mod postfixlocal.te # semodule_package -o postfixlocal.pp -m postfixlocal.mod # semodule -i postfixlocal.pp

Access to /var/log/httpd/error_log by postdrop will still be prevented by SELinux but we won't receive constant alerts and error messages filling up our log files each time access is blocked.

8. Policy Documentation

A lot of SELinux policy is abstracted through GNU m4 macros, which is why the devel packages are required to build new modules that are dependant on the existing policy API. To see a listing of macros and modules available in the installed policy, you can install the selinux-policy-doc package that will install HTML documentation to /usr/share/doc/selinux-policy/html/. It also provides manual pages for each SELinux module that provides a brief overview of the booleans, file contexts, and types declared in the module. These are available as manual pages with names in the format of "<module>_selinux". For example, the documentation for the HTTPD module is available as httpd_selinux(8) and the documentation for the audit administrator role is contained in auditadm_selinux(8) .

9. Summary

This article is intended to give an overview of working with SELinux for users new to SELinux. SELinux is installed and enabled by default, and for most users it will function without issue affording an enhanced level of security. SELinux is suitable for all classes of installation including servers, workstations, desktops and laptops.

Although SELinux can appear quite daunting and complex to users not familiar with it, that is no reason to disable it at installation. If SELinux does present issues then it is easy to switch into Permissive mode at which point issues are only logged and not blocked. When issues do arise the techniques presented in this article can be used to troubleshoot and resolve them.

10. Additional Resources

https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/7/html/SELinux_Users_and_Administrators_Guide/

http://fedoraproject.org/wiki/SELinux

http://docs.fedoraproject.org/en-US/Fedora/13/html-single/Security-Enhanced_Linux/

http://danwalsh.livejournal.com/

11. User Notes and Gotchas

This section is provided by a user who learned most of what he knows of SELinux from this document. This document is a wonderful and detailed resource. However, it is somewhat dry. It misses a couple of practical points I found rather frustrating as I tried to actually get stuff done. Note this is all for CentOS 6.

semanage is found in the package policycoreutils-python which is not installed by default. Note there is a separate policycoreutils package. semanage appears to be installed by default in CentOS 7. It is still located in policycoreutils-python. Finding the right context to use as you manage a system is difficult. One place to start is ls -Z. Look at the directories and data pre-installed by a package and copy the contexts already used. The next tool is seinfo -t which lists all contexts currently in use on your system. grep for the name of your application. The use of the public_content_rw_t context is a fix for some situations. This user has directories that need to be shared by NFS, Samba and Apache. This context seems to allow this to happen. It's probably also a security hole so beware on security conscious systems. Don't forget about the -t parameter to chcon. It sets just the type context which is generally all you want to do and is easier than specifying the entire string as reported by ls -Z. audit2allow is actually easier to use than presented here. When you have a conflict between two contexts, find the error messages in audit.log and extract them to a separate text file. Feed the errors back to audit2allow like this:

audit2allow -M mynewpolicyname <errors.txt

What will be generated is mynewpolicyname.te and mynewpolicyname.pp along with helpful instructions on how to import the new policy. This new policy will enable whatever was previously conflicting. I discovered this process attempting to get a script running under postfix that was previously installed on a non-SELinux system. Under SELinux, the script needs to run under the postfix_pipe_exec_t context and it's spooling directory needs the postfix_pipe_tmp_t context. However the script also calls the spamc binary of spamassassin for processing. Alas, this binary runs under spamc_t and thus couldn't read or write into the spool directory. I found two error messages in audit.log: one for spamc_t trying to read in the spool directory and one for trying to write. After executing this process on the extracted error messages we get a .te file that looks like this:

module mynewpolicy 1.0; require { type spamc_t; type postfix_pipe_tmp_t; class file { read write }; } #============= spamc_t ============== allow spamc_t postfix_pipe_tmp_t:file { read write };