Paranoid Penguin - Introduction to SELinux

Invest some time into SELinux and worry less about zero-day attacks.

SELinux, the NSA's powerful implementation of mandatory access controls for Linux, can seem like a daunting technology. It's got a lot of moving parts that are labeled (pun intended) with arcane, acronym-intensive terminology, adding some very dense layers of abstraction over Linux's already-abstract architecture. To compound the problem, much of SELinux's documentation seems to have been written by security geeks for security geeks.

Well, people say all that and worse about LDAP too, but as with LDAP (which we covered in this column in the July, August and September 2003 issues of LJ), you can make SELinux do what you need it to do if you learn some basic concepts, become familiar with a modestly sized list of terms and study some representative policy files.

In this month's column, we discuss SELinux basics. We begin with SELinux's general design goal; introduce the concepts of SELinux subjects, permissions and objects, and how they fit into security contexts; and tie those ideas together in a discussion of Type Enforcement.

Believe me, that's plenty to start off with! We'll save actual SELinux configuration for subsequent columns. But, if you have an urgent need to get something working on an SELinux-enabled system, see the on-line Resources for this article.

The Problem

So, precisely what problem are we trying to solve with SELinux? Nothing less than the entire security-patch rat race!

As I've said previously in this space, Linux security often seems to boil down to a cycle of researchers and attackers discovering new security vulnerabilities in Linux applications and kernels; vendors and developers scrambling to release patches, with attackers wreaking havoc against unpatched systems in the meantime; and hapless system administrators finally applying that week's or month's patches, only to repeat the entire trail of tears soon afterward. This is the security-patch rat race, and it's unwinnable. There will always be zero-day (as-yet-unpatched) vulnerabilities.

That's why I've spent so much ink over the years extolling techniques such as virtualizing servers, creating chroot jails, running processes as unprivileged users and using mandatory access controls, all of which limit the effects of zero-day vulnerabilities. SELinux, like Novell AppArmor, is a mandatory access control implementation that doesn't prevent zero-day attacks, but it's specifically designed to contain their effects.

Why is the patch rat race unwinnable? Because in Linux's default Discretionary Access Control (DAC) model, each process runs with the privileges of whichever user starts (or, sometimes, owns) it—that is, all of that user's privileges. If an attacker compromises any process running as root, or escalates a compromised process to root privileges, the attacker can do anything root can do, even when that action has nothing whatsoever to do with the process' intended function.

For example, suppose I have a dæmon called blinkend that is running as the user someguy, and this dæmon is hijacked by an attacker. blinkend's sole function is to make a keyboard LED blink out jokes in Morse code, so you might think, well, the worst the attacker can do is blink some sort of insult, right? Wrong. The attacker can do anything the someguy account can do, which might include everything from executing the Bash shell to mounting CD-ROMs.

Under SELinux, however, the blinkend process would run in a narrowly defined domain of activity that would allow it to do its job (blinking the LED, possibly reading jokes from a particular text file, and so forth). In other words, blinkend's privileges would not be determined based on its user/owner; rather, they would be determined by much more narrow criteria. Provided blinkend's domain was sufficiently strictly defined, even a successful attack against the blinkend process would, at worst, result in naughty Morse-code blinking.

That, in a nutshell, is the problem SELinux was designed to solve.

What SELinux Does

I'm going to assume you understand how Discretionary Access Controls, aka plain-old filesystem permissions, work in Linux. If you don't, I covered this topic in the October and November 2004 issues of Linux Journal, in the two-part series “Linux Filesystem Basics” (see Resources).

Suffice it to say that even under SELinux, the Linux DACs still apply. If the ordinary Linux permissions on a given file block a particular action (for example, user A attempting to write file B), that action still will be blocked, and SELinux won't bother evaluating that action. But, if the ordinary Linux permissions allow the action, SELinux will evaluate the action against its own security policies before allowing it to occur.

So, how does SELinux do this? The starting point for SELinux seems similar to the DAC paradigm: it evaluates actions attempted by subjects against objects.

In SELinux, subjects are always processes. This may seem counterintuitive. Aren't subjects sometimes end users? Not exactly—users execute commands (processes). SELinux naturally pays close attention to who or what executes a given process, but the process itself, not the human being who executed it, is considered to be the subject.

In SELinux, we call actions permissions, just like we do in the Linux DAC. The objects that get acted on, however, are different. Whereas in the Linux DAC model, objects always are files or directories, in SELinux, objects include not only files and directories but also other processes and various system resources in both kernel space and user land.

SELinux differentiates between a wide variety of object classes (categories)—dozens, in fact. You can read the complete list on the Web site “An Overview of Object Classes and Permissions” (see Resources). Not surprisingly, file is the most commonly used object class. Other important object classes include the following:

  • dir

  • socket

  • tcp_socket

  • unix_stream_socket

  • filesystem

  • node

  • xserver

  • cursor

Each object class has a particular set of possible permissions (actions). This makes sense. There are things you can do to directories, for example, that simply don't apply to, say, X servers. Each object class may have both inherited permissions that are common to other classes (for example, read), plus unique permissions that apply only to it. Just a few of the unique permissions associated with the dir class are as follows:

  • search

  • rmdir

  • getattr

  • remove_name

  • reparent

Don't be frustrated by my not explaining these class names or actions; at this point you don't need to understand them for their own sake. I'm simply illustrating that SELinux goes much, much further than Linux DAC's simple model of users, groups, files, directories and read/write/execute permissions.

As you might guess, SELinux would be impossible to use if you had to create an individual rule for every possible action by every possible subject against every possible object. SELinux gets around this in two ways: 1) by taking the stance “that which is not expressly permitted, is denied” and 2) by grouping subjects, permissions and objects in various ways. Both of these points have positive and negative ramifications.

The “default deny” stance allows you to have to create rules/policies that describe only the behaviors you expect and want, instead of all possible behaviors. It's also, by far, the most secure design principle any access control technology can have. However, it also requires you to anticipate all possible allowable behavior by (and interaction between) every dæmon and command on your system.

This is why the “targeted” SELinux policy in Red Hat Enterprise Linux 4 and Fedora Core 3 actually implements what amounts to a “restrict only these particular services” policy, giving free rein to all processes not explictly covered in the policy. No, this is not the most secure way to use SELinux, and it's not even the way SELinux was originally designed to be used. But as we'll see, it's a justifiable compromise on general-purpose systems.

The upside of SELinux's various groupings (roles, types/domains, contexts and so on) is, obviously, improved efficiency over always having to specify individual subjects, permissions and objects. The downside is still more terminology and layers of abstraction. Alas, with power comes complexity.

So, how does SELinux group subjects, permissions and objects?