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.
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.
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:
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:
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?
Every individual subject and object controlled by SELinux is governed by a security context, each consisting of a user, a role and a domain (also called a type).
A user is what you'd expect: an individual user, whether human or dæmon. However, SELinux maintains its own list of users separate from the Linux DAC system. In security contexts for subjects, the user label indicates which SELinux user account's privileges the subject (which, again, must be a process) is running. In security contexts for objects, the user label indicates which SELinux user account owns the object.
A role is sort of like a group in the Linux DAC system, in that a role may be assumed by any of a number of pre-authorized users, each of whom may be authorized to assume different roles at different times. The difference is that in SELinux, a user may assume only one role at a time, and may switch roles only if and when authorized to do so. The role specified in a security context indicates which role the specified user is operating within for that particular context.
Finally, a domain is sort of like a sandbox: a combination of subjects and objects that may interact with each other. Domains are also called types, and although domains and types are two different things in the Flask security model (on which the NSA based SELinux), in SELinux domain and type are synonymous.
This model, in which each process (subject) is assigned to a domain, wherein only certain operations are permitted, is called Type Enforcement (TE), and it's the heart of SELinux. Type Enforcement also constitutes the bulk of the SELinux implementation in Fedora and Red Hat Enterprise Linux.
There's a bit more to it than that, but before I go any further, I want to use an example scenario to illustrate security contexts.
Suppose we're securing my LED-blinking dæmon, blinkend, with SELinux. As you'll recall, it's run with the privileges of the account someguy, and it reads the messages it blinks from a text file, which we'll call /home/someguy/messages.txt.
Under SELinux, we'll need an SELinux user called someguy (remember, this is in addition to the underlying Linux DAC's someguy account—that is, the one in /etc/passwd). We'll also need a role for someguy to assume in this context; we could call it blink_r (by convention, SELinux role names end with _r).
The heart of blinkend's security context will be its domain, which we'll call blinkend_t (by convention, SELinux domain names end with _t—t is short for type). blinkend_t will specify rules that allow the blinkend process to read the file /home/someguy/messages.txt and then write data to, say, /dev/numlockled.
The file /home/someguy/messages.txt and the special file /dev/numlockled will need security contexts of their own. Both of these contexts can probably use the blinkend_t domain, but because they describe objects, not subjects, they'll specify the catch-all role object_r. Objects, which by definition are passive in nature (stuff gets done to them, not the other way around), generally don't assume meaningful roles, but every security context must include a role.
There are two types of decisions SELinux must make concerning subjects, domains and objects: access decisions and transition decisions. Access decisions involve subjects doing things to objects that already exist or creating new things that remain in the expected domain. Access decisions are easy to understand. In our example, “can blinkend read /home/someguy/messages.txt?” is just such a decision.
Transition decisions, however, are a bit more subtle. They involve the invocation of processes in different domains than the one in which the subject process is running or the creation of objects in different types than their parent directories. (Note: even though domain and type are synonymous in SELinux, by convention we usually use domain when talking about processes and type when discussing files.)
That is to say, normally, if one process executes another, the second process will, by default, run within the same SELinux domain. If, for example, blinkend spawns a child process, the child process will run in the blinkend_t domain, the same as its parent. If, however, blinkend tries to spawn a process into some other domain, SELinux will need to make a domain transition decision to determine whether to allow this. Like everything else, transitions must be authorized explicitly in the SELinux policy. This is an important check against privilege-escalation attacks.
File transitions work in a similar way. If a subject creates a file in some directory (and if this file creation is allowed in the subject's domain), the new file normally will inherit the security context (user, role and domain) of the parent directory. For example, if blinkend's security context allows it to write a new file in /home/someguy/, say, /home/someguy/error.log, then error.log will inherit the security context (user, role and type) of /home/someguy/. If, for some reason, blinkend tries to label error.log with a different security context, SELinux will need to make a type transition decision.
Get the picture? Transition decisions are necessary because the same file or resource may be used in multiple domains/types; process and file transitions are a normal part of system operation. But, if domains can be changed arbitrarily, attackers will have a much easier time doing mischief.
Besides Type Enforcement, SELinux includes a second model, called Role-Based Access Control (RBAC). Although I'm out of space for now, RBAC builds on the concepts we've already discussed, providing controls especially useful when real human users, as opposed to dæmons and other automated processes, are concerned.
Next time, I'll describe RBAC at length and begin going into greater depth on how actually to use SELinux, beginning with Fedora and Red Hat's “targeted” policy. Until then, be safe!
Resources for this article: /article/9510.