SELinux for beginners

SELinux is a major enhancement to Linux and can seem arcane, complex and very daunting to those who are new to it. In this document we outline some of the basic ways to begin to harness this system for your advantage without delving into the minute details.

What is SELinux?

For over 30 years, Linux (and Unix before it) relied on a very simple system of filesystem-based permissions to control access to various features where each access was allowed or denied on a user, group or global basis. The very simplicity of this system was its great success and is the reason why it still survives today. However, in the times of increasing security threats, particularly from internet sources, more fine-grained control could sometimes be useful.

In 2004, SELinux began to be introduced into mainstream Linux distributions to meet this need. In essence it gives users, processes and files contexts and through a system of policies decides whether or not to allow access between objects dependent upon their contexts.

Enabling SELinux

We can consider SELinux to have three distinct modes of operation which are:

Disabled
The SELinux system is not in use at all.
Permissive
The SELinux system is in use, but only in an advisory capacity. It will still check whether access should be allowed or not and report this, but will not actually prevent the access happening even if the policy dictates that it should be denied.
Enforcing
The SELinux system is in use and will prevent accesses which are denied by the policy.

Only the Enforcing mode actually provides any real-time security and it is therefore strongly recommended that this be used on production machines - particularly those which are connected to the internet.

The default mode will vary between different Linux distributions. You can find out at any time which mode is in operation by running the command

getenforce

which prints one of the three modes listed above. The Disabled mode is special because it means that SELinux is not running at all and since SELinux also impinges upon kernel processes, it can only be changed to or from Disabled mode by rebooting. To do this, you will need to edit the control file in /etc/sysconfig/selinux (this is the file on RedHat and compatible distributions - check the documentation for your specific distro for alternative locations). In there you should change the value of the SELINUX variable to the desired mode at boot, eg.

SELINUX=enforcing

and then reboot for it to take effect. Depending on your situation this may require a full filesystem relabel at boot time to assign or reassign correct contexts to all your files and that may take some time to complete.

Switching between Enforcing and Permissive modes is thankfully much less unwieldy. There is a command setenforce which can take the argument 0 for permissive mode or 1 for enforcing mode, eg.

# getenforce
Permissive
# setenforce 1
# getenforce
Enforcing
# setenforce 0
# getenforce
Permissive

Using setenforce in this way is very useful, especially for debugging purposes, but the new setting will only persist until the next boot at which point the mode will take from the entry in the file in sysconfig as outlined above.

Disabling SELinux

Don't.

No, seriously, don't. Unfortunately there are many examples on the web of well-meaning people who advise this action in order to allow some other application to "work properly". But it's a bad idea because what you would be doing is removing an entire layer of security from the whole operating system just to get one application to "work". There are better ways of achieving this aim as we shall shortly see.

Monitoring SELinux

There are two main reasons to monitor what SELinux is doing: you may want to find out why a certain action which you want to happen is being denied or you want to keep an eye on the ways in which the system is being probed by outside agents. In either case your first port of call is the audit log.

The audit log is /var/log/audit/audit.log (again, this is the RedHat location) and the entries within which are of type=AVC are generated by the SELinux system. You can parse this file by eye, but of much more use are the stock tools audit2why and audit2allow. The former is a reporting tool which lists the actions which SELinux has disallowed (or reported as disallowable in Permissive mode) together with explanations and suggested actions which could be taken to allow them in future. It is most commonly called like this.

audit2why < /var/log/audit/audit.log

This may generate a lot of output depending on the number of denials in the audit log. Note that in RHEL/CentOS/Fedora installations, these handy commands are provided by the policycoreutils-python RPM.

Configuring SELinux

In addition to the gross three-way condition of Disabled/Permissive/Enforcing, there are X other more fine-grained methods which can be used to configure SELinux.

Overall Policy

You can choose between a number of distinct policies which may be provided by your distribution. The Targeted policy is usually the default and allows separation into confined domains for targeted processes and unconfined domains for others. There is the MLS policy which adds security levels and associated sensitivities and capabilities. There's a minimum policy too for starting with the least restrictive set-up and adding in your custom restrictions as you go. Older systems may use a strict policy by default and this may be worth considering for servers with particular requirements still.

Typically these are chosen at boot time and specified in the /etc/sysconfig/selinux file. In the general case, you'd need a good reason to switch away from targeted.

Policy amendments

You can make your own policy amendments to extend access to particular source/action/target combinations. You could do this by hand but that isn't really a topic for beginners. Instead here is the simpler (although less elegant) approach.

The first task is to generate a list of all those operations which are currently disallowed but which you want to allow. The easy method is to temporarily switch into permissive mode, perform the tasks required and then switch back into enforcing mode, noting the timestamps before and after. This results in all of the disallowed actions being recorded in the audit log. If you don't switch the modes then it is only the first failure which will be recorded because enforcing mode would stop your action from proceeding any further.

Having done that, you can pipe the relevant section of the audit log into audit2allow in order to generate a policy amendment.

# grep bar /var/log/audit/audit.log | audit2allow -M foo
Generating type enforcment file: foo.te
Compiling policy: checkmodule -M -m -o foo.mod foo.te
Building package: semodule_package -o foo.pp -m foo.mod
# semodule -i foo.pp

The two commands shown above will firstly extract all the audit log lines matching 'bar' which have been denied and create a policy called 'foo' based upon them. This policy is automatically compiled and built before the second command loads it into the running SELinux system. It is prudent to examine the module before issuing the second command in order to ensure that no undesirable accesses are being inadvertently allowed. Do this by examining the type enforcement file which in this case is called foo.te in the working directory.

Booleans

By far the easiest mid-range configuration of SELinux is by the use of boolean variables. These allow the administrator to enable or disable whole chunks of functionality simply and atomically. These variables can be queried with getsebool and set with setsebool which is quite flexible with its arguments such that either "true" or 1 or "on" will enable the feature whereas "false" or 0 or "off" will disable it. You are encouraged to be consistent nonetheless.

# getsebool httpd_enable_cgi
httpd_enable_cgi --> off
# setsebool httpd_enable_cgi on
# getsebool httpd_enable_cgi
httpd_enable_cgi --> on

The above commands show a query of the httpd_enable_cgi boolean (which permits the web server to execute CGI scripts) showing it to be off initially. The root user then changes this to enable such functionality. Changes made in this way will not persist through a reboot, so be sure to use the -P flag for any changes which you wish to keep.

For a useful (if somewhat terse) description of each boolean as well as both its current and default value, we can use:

# semanage boolean -l

This will generate quite a bit of output, so probably best to filter or save it to a file.

Context changes

Finally, you can change the contexts of users, processes and files.

File contexts

Each file has a context in SELinux. Sometimes the contexts are wrong because of the way the files are created and if the administrator is adding SELinux freshly to a machine none of the files will have any initial context at all.

Fixing the contexts is known as "relabeling" and it is usually recommended that this is done at boot. SELinux looks for the special indicator file at the root level and if found, will initiate a complete relabel of the filesystem. eg.

# touch /.autorelabel
# shutdown -r now

Of course, this would be overkill if you only want to relabel a single directory and its contents, for example. To avoid this, you can use the handy restorecon command. If the directory to be changed is called "foo" then all that is needed is

$ restorecon -r foo

There are plenty of other options to restorecon and you are urged to familiarise yourself with them if you plan on using it more than very occasionally.

But, what if the context being restored is not what you require? Firstly, think hard about why you want to change the context from the default. The defaults are very carefully chosen and subtle use of the other tools at our displosal (such as the booleans or policy changes for instance) can often countermand the need to change them. If that still won't do the trick then you can use chcon to change the context of a file explicity.

$ chcon system_u:object_r:usr_t:s0 file1.txt
$ chcon -t user_tmp_t ~/tmp/foo
$ chcon --reference=filea fileb

These three lines will set an explicit context on file1.txt, just set the type context only on ~/tmp/foo and use the context of filea as the new context for fileb respectively. There is quite fine-grained control available here for those with the inclination to exert it.

Making context changes in this way is only temporary and they will not persist through a relabel (or higher-level restorecon). To make the changes permanent we can again make use of semanage like this:

$ semanage fcontext -a system_u:object_r:usr_t:s0 '/path/regex/to/files\.txt'

User contexts

More to follow here in due course.

Further Reading