SELinux in a practical way

SELinux is often seen as an evil, complex, unnecessary and especially annoying security component which exists in a lot of Linux distributions. Often you can hear something like: “Disable SELinux and try again” or , “The first thing I do on a new server is to disable SELinux”. The problem with SELinux is that it looks very complex and that it looks like you need to spend ages to understand it. In this post, I’ll try to explain a few basic SELinux principles and especially focus on daily, practical problems related to SELinux and their solutions. Don’t forget that there’s a very good reason for SELinux and it would be a shame to not use it.

SELinux stands for Security Enhanced Linux. It’s a kernel security module that is responsible for mandatory access control. This means that something won’t work unless it’s explicitly allowed by SELinux. The big advantage of SELinux is that it makes your system much more secure because it provides a more granular approach to security. It’s way more flexible that standard permissions.

When does SELinux usually get in your way?

Most distributions that come with SELinux already have standard set of rules, called a policy. This policy allows you to do most, unharmful, things on you system without you really noticing that it’s running SELinux. Usually SELinux comes in the picture as soon as you start to try non-standard stuff. For example trying to run Apache on a port different than 80 or 443, have your webroot in another, non-standard location or trying to communicate between different kinds of services.

When a new packages get’s in the repository, and it requires special permissions to work with SELinux, the policy usually get’s an update. Of course if you use software that’s coming from another source, there’s a big chance that your policy doesn’t contain all required SELinux entries and won’t run as out of the box as you expected.

How to see if it’s really SELinux that’s in the way?

The best way to check if a problem is caused by SELinux is to tail the audit log in /var/log/audit/audit.log and look for entries of type AVC.

Some examples:

For example, we changed the config of Apache to let it listen on port 90 instead of port 80.

When trying to start the Apache, we get an error message:

[jensd@cen ~]$ sudo systemctl restart httpd
Job for httpd.service failed. See 'systemctl status httpd.service' and 'journalctl -xn' for details.

The following appears in the syslog/journal:

Jun 23 19:47:52 cen httpd[13190]: AH00557: httpd: apr_sockaddr_info_get() failed for cen

As you see, there isn’t anything that tells us that the above error is SELinux related and this is where SELinux usually tends to be annoying. In most situations, you will start to check various settings, permissions,… and you lose a lot of time to double check and see that actually everything should be correct.

A good thing for such situations is to check the audit log immediately. This way, you can be sure that the problem is or isn’t SELinux related:

[jensd@cen ~]$ sudo tail -n100 /var/log/audit/audit.log |grep AVC
type=AVC msg=audit(1435063672.779:9069): avc:  denied  { name_bind } for  pid=13190 comm="httpd" src=90 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:reserved_port_t:s0 tclass=tcp_socket

The above line in the audit log tells us that it was SELinux that blocked httpd to bind on TCP port 90.

Another example is when you try to access a file which is copied from somewhere else or which has been extracted from an archive and doesn’t have the correct SELinux security context.

Imagine that I created a nice html-file in my home directory and I decide to make it accessible via my webserver. To be sure, I even change the owner of the file to apache and give it all possible permissions (not a good idea):

[jensd@cen ~]$ sudo mv test.html /var/www/html/
[jensd@cen ~]$ sudo chown apache:apache /var/www/html/test.html
[jensd@cen ~]$ sudo chmod 777 /var/www/html/test.html

You would expect the above to work fine but when I try to access the file, I get a 403 Forbidden in my browser and the following is in the Apache error log:

[jensd@cen ~]$ sudo tail /var/log/httpd/error_log|grep deny
[Tue Jun 23 19:55:20.411240 2015] [core:error] [pid 13257] (13)Permission denied: [client 192.168.202.1:50837] AH00132: file permissions deny server access: /var/www/html/test.html

As with the previous example, you can really get frustrated with such issue since there isn’t any hint or clue related to SELinux.

When checking the audit log, we can see that indeed SELinux blocked access to the file for Apache:

[jensd@cen ~]$ sudo tail /var/log/audit/audit.log|grep AVC
type=AVC msg=audit(1435065017.349:9215): avc:  denied  { read } for  pid=13259 comm="httpd" name="test.html" dev="dm-2" ino=30 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file

The above line in the logfile tells us that httpd can’t access a file with a security contect of user_home_t.

How to solve SELinux-related issues

In the above examples, I showed how to know that SELinux is blocking something. Now I’ll try to explain what to do in such case to solve the problem and allow the action.

I’m completely aware that there are SELinux contexts, labels, classes, ports,… but for me, there are generally four scenarios to solve SELinux issues:

  • SELinux is blocking because of file-context or labels
  • SELinux is blocking “normal” functionality because it could be dangerous
  • SELinux is blocking because the use of non-standard locations/ports
  • SELinux custom issues

Each of them needs to be solved in a different way to do things correctly. Using a custom module to solve a context-related problem would solve the issue but potentially creates a security hole. The order which I listed here is the order which you should follow for most cases.

Solving SELinux problems related to file-context or labels

The first thing you should try in order to solve your SELinux issue, is to check the file lables. You can usually see that a problem is related to the file-context when the log line has tclass=file in it. This means that a file (or directory) has the wrong “label”. Every file on a SELinux system get’s such label and this greatly influences how SELinux treats every file.

In the above example, I moved a file from my homedirectory to the apache webroot. When moving files, permissions aren’t touched so the initial security context (or label) stayed on the file.

Before the move:

[jensd@cen ~]$ ls -Z test.html
-rw-rw-r--. jensd jensd unconfined_u:object_r:user_home_t:s0 test.html

After the move (and even change of permissions):

[jensd@cen ~]$ sudo mv test.html /var/www/html/
[jensd@cen ~]$ sudo chown apache:apache /var/www/html/test.html
[jensd@cen ~]$ sudo chmod 777 /var/www/html/test.html
[jensd@cen ~]$ ls -Z /var/www/html/test.html
-rwxrwxrwx. apache apache unconfined_u:object_r:user_home_t:s0 /var/www/html/test.html

As you can see, the file in the webroot has a context of user_home_t. SELinux doesn’t allow Apache to access files with such label.

To solve this, we need to set the correct label on the file. The easiest way is to do relabel the file according to the policy:

[jensd@cen ~]$ sudo restorecon /var/www/html/test.html
[jensd@cen ~]$ ls -Z /var/www/html/test.html
-rwxrwxrwx. apache apache unconfined_u:object_r:httpd_sys_content_t:s0 /var/www/html/test.html

The label changed from user_home_t to httpd_sys_content_t and this is a context that allows httpd to access the file.

In this case, we could easily solve the problem by relabeling the file. SELinux knows which context is needed for files in /var/www/html to make things work.

In some cases, for example when you would want your homedir to be the webroot for Apache, the policy will not set the correct context for your file when doing a relabel. In such case you’ll need to set the context yourself but it’s important to know that in some cases, your system might relabel all files on the filesystem (for example after a policy upgrade or disk restore).

To prevent problems with a filesystem relabel, you can change the policy regarding labels yourself in /etc/selinux/targeted/contexts/files/.

Allowing “normal” functionality that could be dangerous

Setting the correct SELinux label on files can’t solve every SELinux problem. The next thing to try is setting one of the SELinux booleans. SELinux has a whole list of booleans to set that influence the behavior of SELinux.

To get a complete list of booleans an their value:

[jensd@cen ~]$ sudo getsebool -a
abrt_anon_write --> off
abrt_handle_event --> off
abrt_upload_watch_anon_write --> on
antivirus_can_scan_system --> off
...
zabbix_can_network --> off
zarafa_setrlimit --> off
zebra_write_config --> off
zoneminder_anon_write --> off
zoneminder_run_sudo --> off

You could browse the list of booleans and guess which one to set to solve your problem. A better way is to use audit2allow.

Audit2allow analyses a line in the audit log and will propose you with a possible solution. For example when we want Apache to send emails with PHP directly to an SMTP server, we would get something like this in the audit log:

[jensd@cen ~]$ sudo tail /var/log/audit/audit.log|grep AVC
type=AVC msg=audit(1435067483.758:9419): avc: denied { name_connect } for pid=13440 comm="httpd" dest=465 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:smtp_port_t:s0 tclass=tcp_socket

To find out if the we can solve this issue by setting an SELinux boolean, we can pipe the line in the logfile to audit2allow:

[jensd@cen ~]$ sudo tail -n20 /var/log/audit/audit.log|grep AVC|audit2allow


#============= httpd_t ==============

#!!!! This avc can be allowed using one of the these booleans:
#     nis_enabled, httpd_can_sendmail, httpd_can_network_connect
allow httpd_t smtp_port_t:tcp_socket name_connect;

As you can see, audit2allow proposes us multiple solutions. A good match in this case would be to set the boolean httpd_can_sendmail to 1. To do so, use setsebool:

[jensd@cen ~]$ sudo setsebool -P httpd_can_sendmail=1

The -P option makes this change permanent or persistent over a reboot.

After setting the boolean to true, Apache is allowed to connect to an SMTP-server without SELinux getting in the way.

Solving SELinux issues related to the use of non-standard locations/ports

Next in row are issues that can be solved by setting certain values for SELinux with semanage. The concept is almost the same as with the booleans, only now you can set specific values and not allow or disbale something. In 99% of the cases, this method is what you would use to allow daemons to listen on non-standard ports.

When we use the example where we were trying to let httpd bind to port 90, we got the following message in the audit log:

[jensd@cen ~]$ sudo tail -n20 /var/log/audit/audit.log|grep AVC
type=AVC msg=audit(1435068000.221:9460): avc:  denied  { name_bind } for  pid=13539 comm="httpd" src=90 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:reserved_port_t:s0 tclass=tcp_socket

As with the previous problem, we can pipe this logline to audit2allow in order to see how we can solve this problem:

[jensd@cen ~]$ sudo tail -n60 /var/log/audit/audit.log|grep AVC|audit2allow


#============= httpd_t ==============
allow httpd_t reserved_port_t:tcp_socket name_bind;

Unlike the previous use of audit2allow, we do not get suggestions related to sebooleans. We could build a module that allows the above suggestion by audit2allow but that would cause httpd to be able to listen on any reserved port.

Better in this case is to use semanage to set the allowed ports for httpd.

To get a list of allowed ports for most daemons:

[jensd@cen ~]$ sudo semanage port -l
SELinux Port Type              Proto    Port Number
afs3_callback_port_t           tcp      7001
afs3_callback_port_t           udp      7001
afs_bos_port_t                 udp      7007
afs_fs_port_t                  tcp      2040
...
zookeeper_leader_port_t        tcp      2888
zope_port_t                    tcp      8021

To allow Apache to bind on port 90:

[jensd@cen ~]$ sudo semanage port -a -t http_port_t -p tcp 90
[jensd@cen ~]$ sudo semanage port -l|grep http
http_cache_port_t              tcp      8080, 8118, 8123, 10001-10010
http_cache_port_t              udp      3130
http_port_t                    tcp      90, 80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t            tcp      5988
pegasus_https_port_t           tcp      5989

Directly after changing the allowed ports with semanage, we see it appear in the list.

Now let’s restart Apache to test if this change in SELinux worked:

[jensd@cen ~]$ sudo systemctl restart httpd
[jensd@cen ~]$ sudo netstat -tlpn|grep 90
tcp        0      0 0.0.0.0:90              0.0.0.0:*               LISTEN      13613/httpd

Solve SELinux issues that can be resolved by loading a custom module

If none of the above methods work to solve your issue, you can build a custom SELinux module. This can be done manually or by using audit2allow.

An example where you use such type of solution would be a situation where multiple processes need to access a file and each of them requires a specific SELinux security context (or label). To solve this issue you can’t change the label since that would cause the other process to be denied and vice versa.

Imagine a situation where you want FTP-access to the webroot of your server. This would require a file label that allows httpd to access files and would require another label that allows proftpd to allow the same file. A file can only have one label. Since the files for this example are located in /var/www/html, it’s a good idea to keep their default labels defined in the policy.

One solution to this problem could be to set a boolean that allows the FTP-server to have full access to all files on the system but probably that’s not what we want.

Besides allowing access to all files, we can create a custom module that will allow the FTP-server to access files that have the correct label for http.

Without any action, a line similar like this one would appear in the audit log after trying to access a file with a httpd_sys_content_t context via FTP:

[jensd@cen ~]$ sudo tail -n60 /var/log/audit/audit.log|grep AVC
type=AVC msg=audit(1435070359.365:9800): avc:  denied  { read } for  pid=14113 comm="proftpd" name="html" dev="dm-2" ino=27 scontext=system_u:system_r:ftpd_t:s0-s0:c0.c1023 tcontext=system_u:object_r:httpd_sys_content_t:s0 tclass=dir

Audit2allow tells us indeed that we can give access to all files for FTP:

[jensd@cen ~]$ sudo tail -n60 /var/log/audit/audit.log|grep AVC|audit2allow


#============= ftpd_t ==============

#!!!! This avc can be allowed using the boolean 'ftpd_full_access'
allow ftpd_t httpd_sys_content_t:dir read;

For our solution, we need the last line in the audit2allow output. We can ask audit2allow to translate this to an SELinux module:

[jensd@cen ~]$ sudo tail -n60 /var/log/audit/audit.log|grep AVC|audit2allow -m ftphttp

module ftphttp 1.0;

require {
        type ftpd_t;
        type httpd_sys_content_t;
        class dir read;
}
allow ftpd_t httpd_sys_content_t:dir read;

Option -m will display the source for a custom module that allows the specific action which was denied. The syntax isn’t very hard and it’s quit easy to read that a service with type ftpd_t will get read access to directories with label httpd_sys_content_t.

To build and activate the module as it was proposed by audit2allow:

[jensd@cen ~]$ sudo tail -n60 /var/log/audit/audit.log|grep AVC|audit2allow -M ftphttp
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i ftphttp.pp

[jensd@cen ~]$ sudo semodule -i ftphttp.pp
[jensd@cen ~]$ sudo semodule -l|grep ftphttp
ftphttp 1.0

The -M option will build the source as it was displayed and generates a .pp file (which is the actual module) and a .te file (which is the source).

After building the module, it needs to be installed with semodule.

The above actions will allow ftpd to list a directory that has a httpd_sys_content_t label but won’t allow read or write of the files in those directories. We could repeat all actions multiple times for each action we need but that would take a lot of time and generate a lot of different modules.

A more clean way is to edit the source of the custom module yourself and allow all actions that you need in one module:

The previous command (audit2allow -M) create the source of the module in a .te file which we can use as a base for our custom module.

Edit the file to look like this:

module ftphttp 1.3;

require {
        type ftpd_t;
        type httpd_sys_content_t;
        class dir read;
        class dir write;
        class dir add_name;
        class dir create;
        class file getattr;
        class file read;
        class file open;
        class file write;
        class file create;
}

#============= ftpd_t ==============
allow ftpd_t httpd_sys_content_t:dir read;
allow ftpd_t httpd_sys_content_t:dir write;
allow ftpd_t httpd_sys_content_t:dir add_name;
allow ftpd_t httpd_sys_content_t:dir create;
allow ftpd_t httpd_sys_content_t:file getattr;
allow ftpd_t httpd_sys_content_t:file read;
allow ftpd_t httpd_sys_content_t:file open;
allow ftpd_t httpd_sys_content_t:file write;
allow ftpd_t httpd_sys_content_t:file create;

After changing the source, we need to create/build the actual SELinux module ourselves and activate it:

[jensd@cen ~]$ sudo checkmodule -M -m -o ftphttp.mod ftphttp.te
checkmodule:  loading policy configuration from ftphttp.te
checkmodule:  policy configuration loaded
checkmodule:  writing binary representation (version 17) to ftphttp.mod
[jensd@cen ~]$ sudo semodule_package -o ftphttp.pp -m ftphttp.mod
[jensd@cen ~]$ ll
total 12
-rw-r--r--. 1 root  root  1910 Jun 23 17:01 ftphttp.mod
-rw-rw-r--. 1 jensd jensd 1926 Jun 23 17:01 ftphttp.pp
-rw-rw-r--. 1 jensd jensd  689 Jun 23 16:59 ftphttp.te
[jensd@cen ~]$ sudo semodule -i ftphttp.pp
[jensd@cen ~]$ sudo semodule -l|grep ftphttp
ftphttp 1.3

After these steps, we have a persistent SELinux module that allows proftpd to read and write files on our webroot.

As you can see, SELinux isn’t that hard but you need to invest some time to get trough the basics. Once you did, you will feel satisfied for running machines that are more secure and have all functionality enabled.

4 thoughts on “SELinux in a practical way

  1. very good article. selinux is not hard but problem with material or proper documentation to explain in a simple and better way. I have seen most of the selinux documentation or article fail to explain properly or take complex approach, which adds further complexity to already complex subject.

    Your article start with simple examples and covers most the stuff in a single page. I would suggest to write few more article on SELINUX if you have some time.

    Srinivas Kotaru

  2. Pingback: SELinux in a practical way  - Charming Cloud Blog

Leave a Reply

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