An Introduction to SELinux on CentOS 7

An Introduction to SELinux on CentOS 7

Introduction to SELinux concepts and management

Operating System and Software Versions

  • Operating System: – Linux distribution agnostic

Requirements

  • Root access on a working Linux installation with a valid SElinux policy
  • policycoreutils package: it provides getsebool, setsebool, restorecon utilities
  • coreutils package: provides chcon utility
  • policycoreutils-python package: provides semanage command
  • policycoreutils-newrole: provides the newrole program
  • setools-console: provides seinfo command

Difficulty

MEDIUM

Conventions

  • # – requires given command to be executed with root privileges either directly as a root user or by use of sudo command
  • $ – given command to be executed as a regular non-privileged user

Introduction

SELinux (Security Enhanced Linux) is an implementation of a Mandatory Access Control permission system (MAC) in the Linux kernel. This type of access control differs from Discretionary Access Control systems (DAC) like ACLs and standard unix ugo/rwx permissions, in how the access to a resource is provided. In the case of MAC is not the owner of a resource the one who decides who and how can access it: this access is based on the relationships between domains and labels, dictated by a policy and enforced at the kernel level. Its important to say that SELinux enforced rules and standard system permissions are not mutually exclusive, and the former are implemented after the latter.

Possible SELinux status

There are three possible status of SELinux: disabled, permissive and enforcing. In the first case SELinux is completely off: it doesnt have any effect on the running system. When in permissive mode SELinux is active: it does log the policy violations, but it does nothing to block them. Finally, when in enforcing mode, SELinux actually enforces its policy.

There are many ways you can check SELinux status on your system. The first one is using the command called getenforce. This command just reports in what of the three status mentioned above SELinux is. To have a more verbose output you can use the sestatus utility. This is the output of the command on my system (CentOS 7):

SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

Some useful information are provided: first of all the SELinuxfs mountpoint, in this case /sys/fs/selinux. SELinuxfs is a pseudo filesystem, just like /proc: it is populated at runtime by the Linux kernel and contains files useful to document SELinux status. The SELinux root directory is, instead, the path used to keep SELinux configuration files, the main one being /etc/selinux/config (a symbolic link to this file is present also at /etc/sysconfig/selinux). Changing this file directly is the most straightforward way to change selinux status and mode. Lets take a brief look at its content:

$ cat /etc/selinux/config

	# This file controls the state of SELinux on the system.
	# SELINUX= can take one of these three values:
	#     enforcing - SELinux security policy is enforced.
	#     permissive - SELinux prints warnings instead of enforcing.
	#     disabled - No SELinux policy is loaded.
	SELINUX=enforcing
	# SELINUXTYPE= can take one of three two values:
	#     targeted - Targeted processes are protected,
	#     minimum - Modification of targeted policy. Only selected processes are protected.
	#     mls - Multi Level Security protection.
	SELINUXTYPE=targeted

The file is very well commented: by changing the values of SELINUX and SELINUXTYPE variables, we can set respectively the SELinux status and the SELinux mode. The possible modes are: targeted (the default), minimum and mls. The targeted mode is the default: when this mode is active all targeted processes are protected. The minimum mode is a subset of the first one, in which only specific processes are protected. Finally the mls policy its the most sophisticated one, based on the concept of security classification: from unclassified to top secret: it uses the Bell-La Padula model, developed for the US Department of Defense.

Changing SELinux status

To change the SELinux status at runtime you can use the setenforce command. Its syntax is really simple: you specify the status you want to put SELinux in, choosing between Enforcing or Permissive or providing a boolean value referred to the enforcing status. What you cannot do with this command is to disable SELinux completely. To accomplish this (not recommended) and make other persistent changes, you must edit the main configuration file, as seen above. Changes made to this file are applied after a reboot.

How does SELInux work?

Basically SELinux works on the concept of entities: subjects, objects and actions. A subject is an application or a process (an http server for example), an object is a resource on the system, like a file, a socket, or a port. Finally an action is what that specific subject can perform on the object. A subject runs under a certain domain, which, for example, in the case of the httpd daemon is httpd_t. This is easy verifiable by checking a running process with the ps command: all we need to do is to add the -Z switch (-Z switch is often associated with SELinux on the commands that support it, like ls for example):

$ ps -auxZ | grep httpd

The above command gives the following result (output truncated):

system_u:system_r:httpd_t:s0    apache    2340  0.0  0.2 221940  2956 ?        S    14:20   0:00 /usr/sbin/httpd -DFOREGROUND

Running under the httpd_t domain, the httpd service (subject) can only access (action) resources (objects) within the associated SELinux types. A very simple way to verify this is by checking the /var/www directory. The httpd daemon must be able to access it, so lets check what type this directory has. We can do it by using the ls command with the -Z switch:

$ ls -dZ /var/www

The commands gives us this result:

system_u:object_r:httpd_sys_content_t:s0 /var/www

The output shows us the complete SELinux context, and the /var/www directory being labeled with the httpd_sys_content_t type. This makes perfectly sense: the targeted SELinux policy allows a process running under the httpd_t domain to access (in read only mode) all the files labeled with the httpd_sys_content_t type, no matter what DAC permissions are set on the file. If the process will attempt any action not expected by the policy, SELinux will log the error, and, if in enforcing mode, block the action itself.

SELinux Users

We saw above how a representation of a complete SELinux context appears to be structured:

system_u:object_r:httpd_sys_content_t:s0

Lets analyze this structure by taking in consideration the first three parts (the fourth is referred to the MLS mode). The first section is about the SELinux users: every SELinux user has a different set of restrictions and is authorized to play only a specific set of SELinux roles which give access to specific SELinux domains, which, in turn, are able to access only relate SELinux types.

Selinux users
  can play  
selinux roles
  can go to  
SELinux domains
  have access to  
SELinux
 types

To have a clear idea of the available SELinux users, we can run:

# semanage user -l

This command gives us a clear overall view of the users – roles relationships:

SELinux User    Prefix     MCS Level  MCS Range                      SELinux Roles

guest_u         user       s0         s0                             guest_r
root            user       s0         s0-s0:c0.c1023                 staff_r sysadm_r system_r unconfined_r
staff_u         user       s0         s0-s0:c0.c1023                 staff_r sysadm_r system_r unconfined_r
sysadm_u        user       s0         s0-s0:c0.c1023                 sysadm_r
system_u        user       s0         s0-s0:c0.c1023                 system_r unconfined_r
unconfined_u    user       s0         s0-s0:c0.c1023                 system_r unconfined_r
user_u          user       s0         s0                             user_r
xguest_u        user       s0         s0                             xguest_r

Lets briefly see what some of the described SELinux users are authorized to do:

  • guest_u: This type of user has no access to networking, no script execution privileges in /home, nor can make use of sudo or su commands to gain higher privileges. It can only use the guest_r role
  • staff_u: The system users mapped to this SELinux user have access to GUI, to networking, and to the use of the sudo command to gain privileges. It can switch between the stuff_r, sysadm_r , system_r and unconfined_r roles
  • sysadmin_u: Same as above, plus can use also the su command. It can only play the sysadm_r role
  • system_u: This is the user assigned to system services, no system users should be mapped to it
  • unconfined_u: This type of user has no restrictions. It has both unconfined_r and system_r roles associated with it
  • xguest_u: This SELinux user has access to GUI and to the network, but only via the Firefox browser. It has not execution rights for files under /home and has only the xguest_r role associated with it

As you can see, SELinux user are identifiable, in the context, having the _u suffix. It should be clear that they are a totally different thing from system users. There exists a map between the two, and its possible to see it by running semanage login -l command:

# semanage -l login

Which gives us the following output:

Login Name           SELinux User         MLS/MCS Range        Service

__default__          unconfined_u         s0-s0:c0.c1023       *
root                 unconfined_u         s0-s0:c0.c1023       *

The system user root is mapped to the unconfined_u SELinux user, therefore has no restrictions. No other users are explicitly mapped, so they are, by default, associated to the unconfined_u SELinux user.

Changing SELinux User

At this point you may ask how its possible to set a map between a system user and a SELinux one. We accomplish this task by using semanage login command. In the following example I change the default mapping, associating the dummy user on my system to the guest_u SELinux user:

# semanage login -a -s guest_u dummy

The -a switch is short for –add and its used to add a record, while the -s one (short for –seuser) specifies the SELinux user the system user should be mapped to. Lets now run again semanage login -l to see if something changed:

Login Name           SELinux User         MLS/MCS Range        Service

__default__          unconfined_u         s0-s0:c0.c1023       *
dummy                guest_u              s0                   *
root                 unconfined_u         s0-s0:c0.c1023       *
system_u             system_u             s0-s0:c0.c1023       *

As expected the system dummy user is now associated with the guest_u SELinux user which, as said before, has no access to the network. Lets verify it in the most simple way: we try to ping google and see what the result is:

[dummy@linuxmasterswiki ~]$ ping google.com
ping: socket: Permission denied

As expected, the dummy user is not allowed to use the network, so the ping command fails. To delete the mapping we use the -d switch (short for –delete):

# semanage login -d -s guest_u dummy

Not having a specific mapping, the dummy user will fallback to the unconfined_u SELinux user. Since the latter has no restrictions, if we try again the above command, it should now be successful:

[dummy@linuxmasterswiki ~]$ ping google.com
PING google.com (216.58.205.206) 56(84) bytes of data.
64 bytes from mil04s29-in-f14.1e100.net (216.58.205.206): icmp_seq=1 ttl=52 	time=29.2 ms
[]

Keep in mind that changes in the mapping between users and SELinux users will be effective only after a new login.

SELinux Roles

The second part in a SELinux context is about roles. As you can see from the output of semanage user -l above, each SELinux user can play a specified set of SELinux roles: when there are multiple roles for a SELinux user, the user can also switch between them using the newrole command, using the following syntax:

$ newrole -r newrole

To check what domains a specific role can access, you should run the seinfo command. This is provided by the setools-console package. For example to check what domains are accessible from the stuff_r role, we run:

# seinfo -rstuff_r -x
$ seinfo -rstaff_r -x (output truncated)
   staff_r
      Dominated Roles:
         staff_r
      Types:
         abrt_helper_t
         alsa_home_t
         antivirus_home_t
         httpd_user_content_t
         httpd_user_htaccess_t
	   [...]

Domains and types

The third part of a SELinux context is about domains and types, and is identifiable by having the _t suffix in the context representation. We refer to it as type if we are talking about an object, or as domain if we are talking about a process. Lets take a look.

I have created a simple .html file inside the default apache VirtualHost on my CentOS 7 machine: as you can see the file inherited the SELinux context of the directory it was created in:

-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 test.html

With the httpd_sys_content_t, the file can be read by the httpd process, as confirmed by navigating to it in the browser:

Now lets try to change the file type and see the effect this change has. To manipulate the SELinux context we use the chcon command:

# chcon -t user_home_t /var/www/html/test.html

We changed the SELinux type of the file to user_home_t: this is the type used by the files located in the users home directories by default. Running ls -Z on the file gives us the confirmation:

unconfined_u:object_r:user_home_t:s0 /var/www/html/test.html

If we now try to reach the file from the browser, as expected, we have this, negative, result:

The chcon command can be used not only to change the type of the file, but also the user and the role part of the selinux context. When using it to change a directory context it can also run recursively with the -R switch, and can assign a context also by reference: in this case we dont specify the parts of the context to be changed directly, but we provide the reference to the file or directory the context should conform to. For example, lets make the test.html file above, acquire the context of the /var/www/html directory:

# chcon --reference /var/www/html /var/www/html/test.html && ls -Z /var/www/html/test.html

We can see from the output of the commands above, that now the context of the file has changed again, and its now the same as the one of the /var/www/html directory:

system_u:object_r:httpd_sys_content_t:s0 /var/www/html/test.html

Notice that the changes made with chcon command, will survive a reboot but not a relabeling of the files: in that case the files will be set in accordance to the SELinux original policy, and the changes will be lost. So how can we make the change persistent? We must add new rule to the SELinux policy using semanage command.

Lets say we want to add a rule dictating that all the files created in the directory /home/egdoc/test should have, by default the httpd_sys_content_t type. Here is the command we should run:

semanage fcontext -a -t httpd_sys_content_t /home/egdoc/test(/.*)?

First we invoke the semanage command specifying fcontext for modifying file contexts, then we add the -a switch to add a record and the -t one, to specify we want to change the type part of the context to the one immediately following. Finally, we provide the directory path together with a regular expression that means: /home/egdoc/test path followed by the / character, followed by any number of any character, the entire expression being match 0 or 1 time. This regular expression will match all the file names.

We now run the restorecon command with the -R (recursive) option on the directory, to apply the policy. Since now the rule we added above is part of the policy itself, all the files contained in the directory, and also the newly created ones, will have the context we specified in the rule.

SELinux boolean settings

Selinux booleans settings can change SELinux behavior, and are managed by the use of boolean values. We can interact with them by the use of two commands: getsebool and setsebool, the first one being used to query the state of an option and second one to change it.

If we pass the option we want to check to getsebool, it will give us just the state of that option, if we provide it with the -a switch it will instead show us all available settings and their respective boolean state. For example if we want to check the status of options related to httpd we could run:

$ getsebool -a | grep httpd

Here is a very short excerpt of the output:

[egdoc@linuxmasterswiki.org ~]$ getsebool -a | grep httpd
httpd_anon_write --> off
httpd_builtin_scripting --> on
[...]

Lets now try to change the state of the httpd_anon_write option, and activate it. As mentioned above we use setsebool for the task:

# setsebool httpd_anon_write 1

If we now check the value of the option, it should have been activated:

[egdoc@linuxmasterswiki.org ~]$ getsebool -a | grep httpd_anon_write
httpd_anon_write --> on

All went as expected. However, the changes made this way will not survive a reboot. To accomplish this task we must use the same command, but adding the -P switch: when using it, the changes will be written to the policy and they will persist.

There are many things one should consider when using SELinux, and fine-tuning it to obtain a specific behavior, while maintaining the less possible permissions can be a time-consuming task. Nonetheless is not a good idea, in my opinion, to turn it completely off. Keep experimenting until you are satisfied with the results and you reach the wanted setup: you will gain both in security and knowledge.

 
Enjoyed this video?
An Introduction to SELinux on CentOS 7
"No Thanks. Please Close This Box!"