SELinux is an abbreviation for Security-Enhanced Linux. It is a rather complicated system that consists of a kernel module and a set of userspace tools for management and control.
According to my understanding, SELinux started its life as a research project called FLASK. From there, it was ported to Linux by the United States' National Security Agency (NSA). In the beginning with Linux, even many experts considered SELinux quite difficult to use and understand, but nowadays the SELinux userspace tools are more sophisticated and helpful to the system administrators. Using SELinux is no longer as hard as it perhaps used to be.
SELinux offers Mandatory Access Control (MAC) to the Linux operating system. What is MAC? Wikipedia defines it as follows:
With mandatory access control, [...] security policy is centrally controlled by a security policy administrator; users do not have the ability to override the policy and, for example, grant access to files that would otherwise be restricted. By contrast, discretionary access control (DAC), which also governs the ability of subjects to access objects, allows users the ability to make policy decisions and/or assign security attributes.
So MAC allows the system administrator to dictate security related rules (called policies in SELinux jargon) that users cannot bypass. With DAC, Discretionary Access Control, users can use commands such as
setfacl to modify access rights in the file system.
At some point Linus Torvalds decided that it is not good to have several different, incompatible patch sets that attempt to enhance Linux OS security. That is why Linux Security Module (LSM) framework was created. LSM is simply a pretty thin abstraction layer in the kernel that allows different security modules to serve as plugins that make security related access decisions. SELinux is implemented as an LSM module and it ships with Red Hat Enterprise Linux and Fedora Linux. AppArmor is also an LSM module, but it is very different from SELinux. So the idea of LSM framework is to allow different security module plugins to be used in the kernel. The LSM framework is quite light-weight, so I guess there is practically no observable performance penalty using LSM.
To give a concrete and practical example of what SELinux can do for us, I have written a very small backdoored demonstration daemon (
bddaemon). As the name suggests, this program is a Linux server program that has a nasty built-in backdoor that allows
/bin/bash root shell access to the attacker. However, my
bddaemon binds to localhost only (IPv4 127.0.0.1, TCP port 9999). So if you install this daemon for learning purposes, you need not worry: the port 9999 is accessible only from the same host as the daemon is running on.
The RPM package I provide for Fedora Linux 27 contains just
/usr/local/sbin/bddaemon and a systemd unit file
/usr/lib/systemd/system/bddaemon.service for starting and stopping the daemon. SELinux has a concept called domain transitions that applies between subjects becoming other subjects. Subjects in this context mean processes or threads. With SELinux, when a process does
exec() system calls to execute a new program, it must do so in a manner according to the security policy. For example, you must never start
/usr/local/sbin/bddaemon by executing the file directly as a superuser. Doing so could result in a wrong domain transition: the daemon might enter the unconfined domain and after that SELinux security checks would by bypassed. This could happen, for example, in the Fedora Linux 27 where targeted policy is the default. "Targeted policy" is a name invented by Red Hat Linux and it means that the whole system is not under SELinux control. The root user, for example, is pretty free to do whatever he or she wants on Fedora Linux. So the correct way to start the system daemons such as
bddaemon is to always do it via systemd:
It will do the right thing with domain transitions.
bddaemonwith a SELinux policy
I am by no means an expert, but many years ago I have spent some time studying the SELinux kernel module and the LSM framework that it uses. I plan to write a more detailed article about the SELinux workings later. It is now Sunday, March 18th, 2018 and I toiled the entire yesterday trying to figure out how to make a SELinux policy to restrict
bddaemon. I first tried a wrong approach and wanted to modify the entire
selinux-targeted-policy that is shipped with Fedora Linux 27. That turned out to be somewhat foolish and it was also very time consuming, since I had to rebuild the RPM with each new attempt at making a policy. Giving the command:
dnf download --source selinux-targeted-policy
and studying the contents was, however, very educational. That command actually downloads
selinux-policy source RPM! It is a certain kind of a meta RPM that contains subpackages such as the
selinux-targeted-policy. But there is an easier way to control SELinux policies and it is modular. We need not update the whole target policy in one go every time we want to alter the policy.
It is appropriate to give many thanks to Miroslav Grepl at Red Hat Linux. He has lots of SELinux expertise and he is willing to share it with us. I finally got on the right track after reading his article How to create a new initial policy using sepolicy-generate tool?.
As I said earlier, SELinux userspace tools have improved immensely during the past ten years or so, and now I could generate a policy template for
bddaemon with simply:
The output was five files named:
The first of these,
bddaemon.te, contains the Type Enforcement (TE) types and rules. There are
allow rules in it that specify what this subject (i.e. process) is allowed to do. By default, all actions not allowed are denied by SELinux. So you will understand that this file is extremely important!
Here lies both the beauty and the horror of SELinux. It can be difficult and time consuming to properly confine a complicated application or daemon, because:
allowrules in SELinux, it is necessary to allow everything that needs to allowed, or else the program might fail in mysterious ways with "Permission denied" messages in the system log. Fortunately SELinux on Fedora Linux also has a special log file
/var/log/audit/audit.logthat contains very informative messages concerning SELinux. But despite that, many system administrators can get frustrated and simply disable SELinux altogether. I know I have done that too.
The second file
bddaemon.if is partly mysterious to me. I copied some configuration settings from
ejabberd.if and possibly also from
apache.if. But I must confess I am not sure whether all those settings are needed. The file suffix "if" stands for "Interfaces".
The third file
bddaemon.fc is easy to understand. The suffix "fc" stands for "File Contexts". Here you will specify which labels files, directories, Unix domain sockets etc. should have. At the very heart of SELinux is the basic idea of attaching labels to almost everything there is. Using these labels, or security contexts if you want to call them that, SELinux can form rules and restrict access, because it knows the security "tag" or type of every subject (i.e. process) and object (e.g. file) that it sees. Inside the SELinux kernel module, the text labels are converted to unsigned 32-bit integers (SIDs, Security Identifiers) for efficiency. Those numbers waste less kernel memory and are faster to operate on than text strings.
The fourth file
bddaemon.sh is a shell script for automating the task of building
bddaemon.pp, i.e. the binary policy module that can be loaded into the SELinux enabled Linux kernel. Despite Miroslav Grepls suggestion, I did not execute the file, but I studied it. The script is very small and easy to understand, so I took the basic logic from it and managed to create
bddaemon.pp and RPM packages. Even though
bddaemon is quite a simple and artificial program, creating all the correct rules did not happen immediately. I had to inspect
/var/log/audit/audit.log and use
audit2allow to get audit rules. For instance, binding to the TCP port 9999 required a special
allow rule that mentions jboss. According to a friend, jboss is some kind of a Java program. Of course
bddaemon has nothing to do with it, but the port 9999 is normally associated with that program, and I had to create the following rule:
The fifth file
bddaemon_selinux.spec is a SPEC-file template for creating an RPM package for Fedora Linux 27. The resulting package
bddaemon_selinux will contain
bddaemon.if, but I am not sure why the latter is there.
bddaemon.pp makes perfect sense, since that is the binary format security module that gets loaded into the SELinux enabled Linux kernel.
So much for the background. Here are the concrete steps to help you with your SELinux testing:
The reply should be like the following:
bddaemonbinary RPM for Fedora Linux 27 bddaemon-0.4-1.x86_64.rpm.
bddaemon. Study this and you will understand how the daemon is backdoored. The backdoor is intentional, it is not hidden in any way!
bddaemon. It is essential to have!
bddaemon.pp. Study this and you will understand how SELinux will restrict
nc(netcat) to talk to
bddaemon. To connect, issue command:
Here is a screen capture of my sample session in Xfce desktop environment that I love. If the connection succeeds, you will see
bddaemon welcome banner saying "bddaemon ready". In this session I first wrote command "hi". The daemon replied politely "hello". After that I gave the malicious greeting "howdy" to enable the backdoor. Without SELinux protection, the
bddaemon would have executed
/bin/bash and attached its file descriptors to the network socket, giving the attacker root shell access to the host! But because we have told SElinux that
bddaemon is allowed only network access, writing to system logs and little else, SELinux denies everything else! So the
bddaemon malicious code trying to do
execle() fails. In this example, we got a GUI notification, but you should also check out
/var/log/audit/audit.log to see that SELinux indeed stopped the attack!
How do you search the audit log? Try the following
Your search will match both source and target contexts
system_u:system_r:bddaemon_t:s0. That is the SELinux security context for the running
/usr/local/sbin/bddaemon. My search found the result we wanted:
I hope this article showed you how useful SELinux can be. In this case I used an artificial example with a small
bddaemon that had an intentional backdoor that is easy to detect. There is not much C code to study and the backdoor is obvious. But in the real world, complicated programs do sometimes contain unintentional programming errors that could lead to attacks similar to the one seen with this tiny demonstration server.
Buffer overruns coupled with executable stacks are well-known examples. SELinux can protect from some userspace programming errors by enabling MAC, Mandatory Access Control. It is implemented in the operating system kernel and its security policy is defined by the system administrator.
In addition to the MAC protection, SELinux also contains MLS, Multi Level Security. But at this moment I am still unsure what it is and how to use it.
Kalevi Kolttonen <email@example.com>
UPDATE March 19th, 2018: Today I was very curious about how
sd_notify(3) call would work with systemd. It is used to inform systemd that a daemon is ready to serve clients. That requires
Type=notify in the systemd unit file for the service instead of
Type=forking. So I commented out
daemon(3) call from
bddaemon.c so that systemd would be able to track this daemon (
daemon(3) would do
This small change has absolutely nothing to do with the SELinux demonstration described above. As I said, I just wanted to know how this new model would work and the
bddaemon.c code was close at hand. Unfortunately I have now introduced a dependency on
systemd-devel on Fedora Linux. Sorry about that.