You are on page 1of 10

SELinux in a practical way

Posted on 23/06/2015
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, Ill try to explain a few basic SELinux principles
and especially focus on daily, practical problems related to SELinux and their solutions. Dont
forget that theres a very good reason for SELinux and it would be a shame to not use it.
SELinux stands for Security Enhanced Linux. Its a kernel security module that is responsible for
mandatory access control. This means that something wont work unless its 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. Its 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 its running SELinux. Usually SELinux comes in the picture as soon as you start to try nonstandard 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 gets in the repository, and it requires special permissions to work with
SELinux, the policy usually gets an update. Of course if you use software thats coming from
another source, theres a big chance that your policy doesnt contain all required SELinux entries
and wont run as out of the box as you expected.

How to see if its really SELinux thats 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:
1 [jensd@cen ~]$ sudo systemctl restart httpd

2 Job for httpd.service failed. See 'systemctl status httpd.service' and 'journalctl -xn' for details.
The following appears in the syslog/journal:
1 Jun 23 19:47:52 cen httpd[13190]: AH00557: httpd: apr_sockaddr_info_get() failed for cen
As you see, there isnt 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 isnt SELinux related:
[jensd@cen ~]$ sudo tail -n100 /var/log/audit/audit.log |grep AVC
1 type=AVC msg=audit(1435063672.779:9069): avc: denied { name_bind } for pid=13190
2 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 doesnt 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):
1 [jensd@cen ~]$ sudo mv test.html /var/www/html/
2 [jensd@cen ~]$ sudo chown apache:apache /var/www/html/test.html
3 [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
1
[Tue Jun 23 19:55:20.411240 2015] [core:error] [pid 13257] (13)Permission denied: [client
2
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 isnt 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


1 type=AVC msg=audit(1435065017.349:9215): avc: denied { read } for pid=13259
2 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 cant 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 Ill try
to explain what to do in such case to solve the problem and allow the action.
Im 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
gets 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 arent touched so the initial security context (or label) stayed on the
file.
Before the move:
1 [jensd@cen ~]$ ls -Z test.html
2 -rw-rw-r--. jensd jensd unconfined_u:object_r:user_home_t:s0 test.html

After the move (and even change of permissions):


1
2
3
4
5

[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 doesnt 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


1
[jensd@cen ~]$ ls -Z /var/www/html/test.html
2
-rwxrwxrwx. apache apache unconfined_u:object_r:httpd_sys_content_t:s0
3
/var/www/html/test.html
4
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 youll
need to set the context yourself but its 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 cant 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:
1
2
3

[jensd@cen ~]$ sudo getsebool -a


abrt_anon_write --> off
abrt_handle_event --> off

4
5
6
7
8
9
10
11
12

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


1 type=AVC msg=audit(1435067483.758:9419): avc: denied { name_connect } for pid=13440
2 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:
1
2
3
4
5
6
7
8

[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:

1 [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
1 type=AVC msg=audit(1435068000.221:9460): avc: denied { name_bind } for pid=13539
2 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:
1 [jensd@cen ~]$ sudo tail -n60 /var/log/audit/audit.log|grep AVC|audit2allow
2
3
4 #============= httpd_t ==============
5 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:
1
2
3
4
5
6
7
8
9

[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:

1
2
3
4
5
6
7

[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 lets restart Apache to test if this change in SELinux worked:

1 [jensd@cen ~]$ sudo systemctl restart httpd


2 [jensd@cen ~]$ sudo netstat -tlpn|grep 90
3 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 cant 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, its 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 thats not what we want.
Besides allowing access to all files, we can create a custom module that will allow the FTPserver 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
1 type=AVC msg=audit(1435070359.365:9800): avc: denied { read } for pid=14113
2 comm="proftpd" name="html" dev="dm-2" ino=27 scontext=system_u:system_r:ftpd_t:s0s0: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:
1
2
3
4
5
6
7

[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:

1
2
3
4
5
6
7
8
9
10

[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 isnt very hard and its 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:

1
2
3
4
5
6
7
8
9

[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
wont 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:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

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:
1
2
3

[jensd@cen ~]$ sudo checkmodule -M -m -o ftphttp.mod ftphttp.te


checkmodule: loading policy configuration from ftphttp.te
checkmodule: policy configuration loaded

4
5
6
7
8
9
10
11
12
13

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 isnt 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.

You might also like