Hackers often rely on lazy system admins and unpatched vulnerabilities to get access to a host. Keeping intruders off of our machines requires us to update daily, only run the services we need, and read the code, among other things, but we can still make mistakes. Luckily for us, we can limit the damage caused by those mistakes by running SELinux.
SELinux is a mandatory access control (MAC) system implemented via the Linux kernel and within extended attributes on the file system. SELinux was originally developed by the NSA with the help of the Secure Computing Corporation (now defunct), MITRE, and numerous research labs. SELinux was released under the GPL (GNU General Public License) in 2000.
Mandatory access control is a system-wide policy which determines who and what is allowed to have access to files, sockets, ports, etc. In contrast, a Linux system running without some form of MAC uses discretionary access control (DAC) which relies on the owner of an object to determine the permissions, groups, etc. With discretionary access control, the user has the final say. On a system running MAC, the policy in place has the final say, even over the root user.
Giving the kernel the final say over access keeps users and processes from interacting with other objects they shouldn't be interacting with. SELinux policy can be configured in a very granular manner for use in a a multi-level security environment. This can be time-consuming and challenging. Luckily, for an average user, major distributions often include some default SELinux policies which come preconfigured for common use-cases.
Modern Fedora, RHEL, and CentOS distributions all come with SELinux out of the box. Ubuntu and Debian require the administrator to install additional packages and configure SELinux. Most popular distributions include support for SELinux, so there's really not an excuse to rely solely on DAC for your hosts.
With the introduction out of the way, we can start interacting with SELinux. I will be using a Fedora system throughout this article, but the commands and interactions should remain the same no matter which distribution you have selected. However, other distributions may have varying policies.
Even though Fedora supports MLS (multi-level security), we won't be using MLS since it's outside the scope of this article. We will instead be focusing on the Fedora Linux targeted policy. Even though the targeted policy isn't as fine-grained as MLS, it is still an effective security tool.
Targeted policy is the default SELinux policy in Fedora Linux. When using targeted policy, processes that are defined in the policy run within the confines of SELinux. Processes that are not included in this policy run as they would run in a standard Linux environment.
For example, httpd is a targeted process, and SELinux policy applies to it. If an attacker were to abuse a file include, SELinux would limit the attackers access to the system via the policy implemented on the httpd service.
There are a few ways to determine your current SELinux policy. You can use the cat command in your terminal to read the SELinux config file.
We can also use the sestatus command to check the current status of SELinux.
sestatus [-v] [-b]
The -v option is for verbosity. The -b option shows the configuration of SELinux boolean values. We'll cover boolean values in a little bit.
The output of this command gives us a bit of information about the current SELinux status.
- We see that SELinux is enabled.
- We see the mount point of the SELinux temporary file system.
- We see this file system is used internally by SELinux.s
- We see the root directory, which tells us where SELinux config files are located. Unlike some configuration files in SELinux, the files contained in the root directory can be modified by the user.
- We see that the loaded policy name tells us what SELinux policy is currently loaded.
- We see that the current mode tells us whether or not SELinux is currently enforcing the loaded policy.
- We won't worry about the MLS status.
- We see that the policy deny unknown status controls whether or not the kernel will handle permissions defined within the kernel, but missing from the targeted policy.
- We see that the max kernel policy version is the default set by the operating system.
Another command that can be used to check the status of SELinux is the getenforce command. This command can be used in shell scripts to determine if SELinux is currently enforcing its policy.
SELinux is a complicated tool, but we can still address a bit of the basics here. SELinux works by labeling processes and files with an SELinux context. In the case of files and directories, these labels are stored as extended attributes on the file system. When a user creates a file or directory, that file or directory is created within the context of the directory it was created in.
For example, if I create a webpage in my home directory, it inherits the labeling from that directory. If I move that page to the /var/www/html folder, it retains the inherited labeling. This can lead to problems where Apache is unable to read the file, where it would need to be re-labeled in the httpd context. Even though this creates a little bit of work on the system administrators end, it adds security.
Now that we know a little about labels, let's have a look at one and see some type enforcement at work!
In this example, I've executed ls -Z on /etc/resolv.conf. The -Z argument is the context argument and can be used with many different command line utilities. Let's break this information down.
This output can be interpreted as user:role:type:level, with level being optional. For our purposes, we're only concerned with the type field. The rest of these fields, of course, can be configured, but it starts to get complex quickly.
From the ls -Z command, we see that resolv.conf is net_conf_t, or net conf type. Processes labeled as net type can interact with this file, but they will not interact with files that are not within their type. So what happens if I create a new resolv.conf file in my home directory and then move it to replace the existing resolv.conf?
The first thing to notice here is that this file is user_home_t, which means that objects of the net type will be restricted from accessing it. When I replace the existing /etc/resolv.conf with my new file, we run into some problems.
This alert pops up almost immediately warning me that there are some problems on my system. These alerts are part of the setroubleshoot package. If you are running a server, you will want the setroubleshoot-server package, which will parse SELinux error messages in /var/log/audit/audit.log into a human-readable format. The difference is really night and day.
One thing to note is that the resolution of the problem is at the top of the message! This is really helpful.
This particular message is suggesting that we fix our resolv.conf issue by creating a custom policy for cupsd. Of course, if we read through it, we can see that there is a label issue with resolv.conf. It's important to always read the full message. In the case of our resolv.conf issue, we don't actually have to modify anything. This issue can be resolved simply by removing resolv.conf and restarting networking. The network manager will make a new resolv.conf in the /etc directory.
Readers who have been following closely are probably asking themselves if the type is inherited from the directory where the file is created, wouldn't the newly created /etc/resolv.conf file be labeled as etc_t? The answer is yes, if the process creating the type wasn't part of the targeted policy. In order to address this issue, SELinux includes something called a transition.
File transitions are defined by SELinux policy, in this case, the targeted policy. If I were to create a file in the /etc directory as the root user, it would inherit the etc_t label. In order to get around this, SELinux allows you to create transitions. Transitions can be configured to allow services like the network manager to create a resolv.conf file in the /etc directory without inheriting the labeling of the /etc directory.
Beyond the labeling system, SELinux targeted policy contains hundreds of boolean (true/false) values which configure some of the behavior of SELinux. In order to view all of the boolean values on your system, use the getsebool command.
I went ahead and piped the command output into nl just to give an idea of the number of boolean values that can be modified. In targeted policy, these boolean values come preconfigured to work with most configurations. If you're having an issue with SELinux, this is one of the places to check. If you're unsure of what a boolean does, you can get a list with the command semanage boolean -l. SELinux booleans can be managed with the setsebool command.
setsebool smbd_anon_write 1
This command would set smbd_anon_write to true, which would allow SMB to write files in directories labeled public content. This change is not persistent, which means that it's a great way to troubleshoot. In order to make the change permanent, you will need to add the -P argument.
setsebool -P smbd_anon_write 1
This command would have the same effect, except it would enable the change permanently.
The SELinux targeted policy is a collection of reasonable defaults, and it's improving all the time. But what happens when you want to deviate from that policy? Things will break. You will experience unexpected behavior and need to troubleshoot the problem.
Troubleshooting isn't that bad for most issues, but SELinux has a bad rap. I've seen tutorials for services that start out with "1. Disable SELinux." That's just lazy. It's especially bad since by disabling SELinux, you'll be disabling security across the entire operating system just so you can run one service or process. It's much better to figure out what the problem is and resolve it.
In our first example, I'm going to create a Perl script and place it in the /var/www/cgi-bin directory. I create my script, change the permissions, and enable CGI in Apache. I then move my script over to the cgi-bin directory.
That's not good! My cutting edge "hello world" script isn't executing! When I check the Apache error log, there's no information. Next, I have a look in /var/messages, since I've installed the setroubleshoot packages.
Alright, now we're getting somewhere! At the top of the log entry, I'm given the command to run for a complete error message. These IDs for sealert are system specific, so running this command on your system will not produce a message.
sealert -l 81dd8f63-f56a-4666-9348-3bd1ef4ea534
The -l argument tells sealert to look up alerts by listing number. When I execute this command, I get a nice clean human-readable alert.
At the top of this alert, I see the source context, which is httpd type, and the target context, which is user_home type. There is also information for configuring a policy module to allow this type of access.
I don't really want to enact a policy that allows Apache to read user files. The issue is obviously due to labeling. Labeling issues are the most common issues you will encounter with SELinux.
There are a few ways to modify a file's label. We could look for a known good file in the directory and change the labeling to match. I do have a properly labeled copy of hello.cgi in my /var/www/cgi-bin directory.
We can see that hello2.cgi is labeled user_home_t, and hello.cgi is labeled httpd_sys_script_exec_t. We can change the labeling and context in a few ways.
The long typing intensive way:
chcon -u unconfied_u -r object_r -t httpd_sys_script_exec_t /var/www/cgi-bin/hello2.cgi
This will use the chcon (change context) command to relabel our broken CGI to the proper label, but that's pretty typing intensive.
The short way:
chcon —reference /var/www/cgi-bin/hello.cgi /var/www/cgi-bin/hello2.cgi
This will use the properly labeled hello.cgi script as a reference for the context to set hello2.cgi to. This is a lot faster than typing everything out. But what if we don't know how it's supposed to be labeled? What do we do when there's no reference? We use the restorecon command.
restorecon -vR /var/www/cgi-bin/
This will restore the proper directory context to all contents of the directory. The -v argument tells restorecon to be verbose, and the R argument is for recursive.
Great, so we should be back in business. I fire up my browser and access the page, only to get an error again. Since I know my labels are correct and SELinux isn't throwing any errors into /var/log/messages, it's time to check if there are any boolean values blocking access. We can do this with the getsebool command.
getsebool -a | grep httpd
Well, it looks like our problem might be related to the boolean httpd_enable_cgi, which is currently set to off. We'll use the setsebool command to set it to on.
setsebool -P httpd_enable_cgi 1
Looks like we sorted out our first problem. This wasn't a very complex example, but these types of issues are what you can expect to crop up when using SELinux. In this case, we knew enough about SELinux to determine the cause of the issue without making any policy changes. If we can't figure out what's wrong, it's wise to review the suggestion that SELinux makes about modifying policy make sure we understand the effects and follow the commands given by SEAlert to create a custom policy.
This type of troubleshooting works with smaller problems, but it would get frustrating very quickly if there is more than one issue. In that case, we might want to set SELinux to permissive mode for a bit.
If we anticipate that a particular piece of software will cause problems in SELinux, we can change the SELinux mode to permissive. In permissive mode, SELinux doesn't enforce its policy, instead, it logs violations. Once we've worked with our new configuration for a bit, collected the list of violations, and fixed them, we should be able to set SELinux back to enforcing and the configuration should work. To make these changes, we use the setenforce command.
The 1 argument sets SELinux to enforcing, and the 0 argument sets SELinux to permissive.
SELinux can be disabled and enabled via its configuration file /etc/selinux/config.
If you choose to disable SELinux for some reason, you will need to take some extra steps before reenabling it. Switching SELinux directly back into enforcing mode can cause issues with your system. Create a file with touch in / to tell SELinux to relabel the system.
Next, set SELinux to permissive, and reboot your machine. SELinux will relabel the file system. The last step is to work with the machine for a little bit, then check your log files for errors. You will need to fix these errors before moving SELinux to enforcing mode.
SELinux is a powerful security tool, and when feasible, should be run on all public facing hosts. This article barely scratches the surface of what you can do with SELinux. For more information, you can always consult the man pages on your system. You can also check the blog of Dan Walsh (the writer of the man pages) for current events.
As always, feel free to comment! If you've got something you'd like to talk about, you can find me on Twitter @0xBarrow.