- Security Contexts for Type Enforcement
- Type Enforcement Access Control
- The Role of Roles
- Multilevel Security in SELinux
- SELinux Features Familiarization
- Summary
- Exercises
2.2 Type Enforcement Access Control
In SELinux, all access must be explicitly granted. SELinux allows no access by default, regardless of the Linux user/group IDs. Yes, this means that there is no default superuser in SELinux, unlike root in standard Linux. The way access is granted is by specifying access from a subject type (that is, a domain) and an object type using an allow rule. An allow rule has four elements:
- Source type(s) Usually the domain type of a process attempting access
- Target type(s) The type of an object being accessed by the process
- Object class(es) The class of object that the specified access is permitted
- Permission(s) The kind of access that the source type is allowed to the target type for the indicated object classes
As an example, take the following rule:
allow user_t bin_t : file {read execute getattr};
This example shows the basic syntax of a TE allow rule. This rule has two type identifiers: the source (or subject or domain) type, user_t; and the target (or object) type, bin_t. The identifier file is the name of an object class defined in the policy (in this case, representing an ordinary file). The permissions contained within the braces are a subset of the permissions valid for an instance of the file object class. The translation of this rule would be as follows:
A process with a domain type of user_t can read, execute, or get attributes for a file object with a type of bin_t.
As we discuss later, permissions in SELinux are substantially more granular than in standard Linux, where there are only three (rwx). In this case, read and execute are fairly conventional; getattr is less obvious. Essentially, getattr permission to a file allows a caller to view (not change) attributes such as date, time, and discretionary access control (DAC) access modes. In a standard Linux system, a caller may view such information on a file with only search permission to the file's directory even if the caller does not have read access to the file.
Assuming that user_t is the domain type of an ordinary, unprivileged user process such as a login shell process, and bin_t is the type associated with executable files that users run with the typical security privileges (for example, /bin/bash), the rule might be in a policy to allow users to execute shell programs such as the bash shell.
Throughout this chapter, we often depict allowed access using symbols: circles for processes, boxes for objects, and arrows representing allowed access. For example, Figure 2-1 depicts the access allowed by the previous allow rule.
Figure 2-1 A depiction of an allow rule
2.2.1 Type Enforcement by Example
SELinux allow rules such as the preceding example are really all there is to granting access in SELinux. The challenge is determining the many thousands of accesses one must create to permit the system to work while ensuring that only the necessary permissions are granted, to make it as secure as possible.
To further explore type enforcement, let's use the example of the password management program (that is, passwd). In Linux, the password program is trusted to read and modify the shadow password file (/etc/shadow) where encrypted passwords are stored. The password program implements its own internal security policy that allows ordinary users to change only their own password while allowing root to change any password. To perform this trusted job, the password program needs the ability to move and re-create the shadow file. In standard Linux, it has this privilege because the password program executable file has the setuid bit set so that when it is executed by anyone, it runs as root user (which has all access to all files). However, many, many programs can run as root (in reality, all programs can potentially run as root). This means, any program (when running as root) has the potential to modify the shadow password file. What type enforcement enables us to do is to ensure that only the password program (or similar trusted programs) can access the shadow file, regardless of the user running the program.
Figure 2-2 depicts how the password program might work in an SELinux system using type enforcement.
Figure 2-2 Type enforcement example: passwd program
In this example, we defined two types. The passwd_t type is a domain type intended for use by the password program. The shadow_t type is the type for the shadow password file. If we examine such a file on disk, we would see something like this:
# ls -Z /etc/shadow -r---- root root system_u:object_r:shadow_t shadow
Likewise, examining a process running the password program under this policy would yield this:
# ps -aZ joe:user_r:passwd_t 16532 pts/0 00:00:00 passwd
For now, you can ignore the user and role elements of the security context and just note the types.
Examine the allow rule in Figure 2-2 The purpose of this rule is to give the passwd process' domain type (passwd_t) the access to the shadow's file type (shadow_t) needed to allow the process to move and create a new shadow password file. So, in reexamining Figure 2-2, we see that the depicted process running the password program (passwd) can successfully manage the shadow password file because it has an effective user ID of root (standard Linux access control) and because a TE allow rule permits it adequate access to the shadow password file's type (SELinux access control). Both are necessary, neither is sufficient.
2.2.2 The Problem of Domain Transitions
If all we had to do was provide allowed access for processes to objects such as files, writing a TE policy would be straightforward. However, we have to figure out a way to securely run the right programs in a process with the right domain type. For example, we do not want programs not trusted to access the shadow file to somehow execute in a process with the passwd_t domain type. This could be disastrous. This problem brings us to the issue of domain transitions.
To illustrate, examine Figure 2-3, in which we expand upon the previous password program example. In a typical system, a user (say Joe) logs in, and through the magic of the login process, a shell process is created (for example, running bash). In standard Linux security, the real and effective user IDs (that is, joe) are the same. [1] In our example SELinux policy, we see that the process type is user_t, which is intended to be the domain type of ordinary, untrusted user processes. As Joe's shell runs other programs, the type of the new processes created on Joe's behalf will keep the user_t domain type unless some other action is taken. So how does Joe change passwords?
Figure 2-3 The problem of domain transitions
We would not want Joe's untrusted domain type user_t to have the capability to read and write the shadow password file directly because this would allow any program (including Joe's shell) to see and change the contents of this critical file. As discussed previously, we want only the password program to have this access, and then only when running with the passwd_t domain type. So, the question is how to provide a safe, secure, and unobtrusive method for transitioning from Joe's shell running with the user_t type to a process running the password program with the passwd_t type.
2.2.3 Review of SetUID Programs in Standard Linux Security
Before we discuss how to deal with the problem of domain transitions, let's first review how a similar problem is handled in standard Linux where the same problem of providing Joe a means to securely change his password exists. The way Linux solves this problem is by making passwd a setuid to the root program. If you list the password program file on a typical Linux system, you see something like this:
# ls -l /usr/bin/passwd -r-s—x—x 1 root root 19336 Sep 7 04:11 /usr/bin/passwd
Notice two things about this listing. First the s in the x spot for the owner permission. This is the so-called setuid bit and means that for any process that executes this file, its effective UID (that is, the user ID used for access control decisions) will be changed to that of the file owner. In this case, root is the file owner, and therefore when executed the password program will always run with the effective user ID of root. Figure 2-4 shows these steps.
Figure 2-4 Password program security in standard Linux (setuid)
What actually happens when Joe runs the password program is that his shell will make a fork() system call to create a near duplicate of itself. This duplicate process still has the same real and effective user IDs (joe) and is still running the shell program (bash). However, immediately after forking, the new process will make an execve() system call to execute the password program. Standard Linux security requires that the calling user ID (still joe) have x access, which in this case is true because of the x access to everyone. Two key things happen as a result of the successful execve() call. First, the shell program running in the new process is replaced by the password program (passwd). Second, because the setuid bit is set for owner, the effective user ID is changed from the process' original ID to the file owner ID (root in this case). Because root can access all files, the password program can now access the shadow password file and handle the request from Joe to change his password.
Use of the setuid bit is well established in UNIX-like operating systems and is a simple and powerful feature. However, it also illustrates the primary weakness of standard Linux security. The password program needs to run as root to access the shadow file. However, when running as root, the password program can effectively access any system resource. This is a violation of the central security engineering principal of least privilege. As a result, we must trust the password program to be benign with respect to all other possible actions on the system. For truly secure applications, the password program requires an extensive code audit to ensure it does not abuse its extra privilege. Further, when the inevitable unforeseen error makes its way into the password program, it presents a possible opportunity to introduce vulnerabilities beyond accessing the shadow password file. Although the password program is fairly simple and highly trusted, think of the other programs (including login shells) that may and do run as root with that power.
What we would really like is a way to ensure least privilege for the password program and any other program that must have some privilege. In simple terms, we want the password program to be able to access only the shadow and other password-related files plus those bare-minimum system resources necessary to run; and we would like to ensure that no other program but the password (and similar) programs can access the shadow password file. In this way, we need only evaluate the password (and similar) programs with respect to its role in managing user accounts and need not concern ourselves with other programs when evaluating security concerns for user account management.
This is where type enforcement comes in.
2.2.4 Domain Transitions
As previously shown in Figure 2-2, the allow rule that would ensure that passwd process domain type (passwd_t) can access the shadow password file. However, we still have the problem of domain transitions described earlier. Providing for secure domain transition is analogous to the concept of setuid programs, but with the strength of type enforcement. To illustrate, let's take the setuid example and add type enforcement (see Figure 2-5).
Figure 2-5 Passwd program security in SELinux (domain transitions)
Now our example is more complicated. Let's examine this figure in detail. First notice that we have added the three types we showed previously, namely Joe's shell domain (user_t), the password program's domain type (passwd_t), and the shadow password file type (shadow_t). In addition, we have added the file type for the passwd executable file (passwd_exec_t). For example, listing the security context for the password program on-disk executable would yield a result something like this:
# ls -Z /usr/bin/passwd -r-s—x—x root root system_u:object_r:passwd_exec_t /usr/bin/passwd
Now we have enough information to create the TE policy rules that allow the password program (and presumably only the password program) to run with the passwd_t domain type. Let's look at the rules from Figure 2-5. The first rule is as follows:
allow user_t passwd_exec_t : file {getattr execute};
What this rule does is allow Joe's shell (user_t) to initiate an execve() system call on the passwd executable file (passwd_exec_t). The SELinux execute file permission is essentially the same permission as x access for files in standard Linux. (The shell "stats" the file before trying to execute, hence the need for getattr permission, too.) Recall our description of how a shell program actually works. First it forks a copy of itself, including identical security attributes. This copy still retains Joe's shell original domain type (user_t). Therefore, the execute permission must be for the original domain (that is, the shell's domain type). That is why user_t is the source type for this rule.
Let's now look at the next allow rules from Figure 2-5:
allow passwd_t passwd_exec_t : file entrypoint;
This rule provides entrypoint access to the passwd_t domain. The entrypoint permission is a rather valuable permission in SELinux. What this permission does is define which executable files (and therefore which programs) may "enter" a domain. For a domain transition, the new or "to-be-entered" domain (in this case, passwd_t) must have entrypoint access to the executable file used to transition to the new domain type. In this case, assuming that only the passwd executable file is labeled with passwd_exec_t, and that only type passwd_t has entrypoint permission to passwd_exec_t, we have the situation that only the password program can run in the passwd_t domain type. This is a powerful security control.
Let's now look at the final rule:
allow user_t passwd_t : process transition;
This is the first allow rule we have seen that did not provide access to file objects. In this case, the object class is process, meaning the object class representing processes. Recall that all system resources are encapsulated in an object class. This concept holds for processes, too. In this final rule, the permission is transition access. This permission is needed to allow the type of a process' security context to change. The original type (user_t) must have transition permission to the new type (passwd_t) for the domain transition to be allowed.
These three rules together provide the necessary access for a domain transition to occur. For a domain transition to succeed, all three rules are necessary; alone, none is sufficient. Therefore, a domain transition is allowed only when the following three conditions are true:
- 1. The process' new domain type has entrypoint access to an executable file type.
- 2. The process' current (or old) domain type has execute access to the entry point file type.
- 3. The process' current domain type has transition access to the new domain type.
When all three of these permissions are permitted in a TE policy, a domain transition may occur. Further, with the use of the entrypoint permission on executable files, we have the power to strictly control which programs can run with a given domain type. The execve() system call is the only way to change a domain type, [2] giving the policy writer great control over an individual program's access to privilege, regardless of the user who may be invoking the program.
Now the issue is how does Joe indicate that he wants a domain transition to occur. The above rules allow only the domain transition; they do not require it. There are ways that a programmer or user can explicitly request a domain transition (if allowed), but in general we do not want users to have to make these requests explicitly. All Joe wants to do is run the password program, and he expects the system to ensure that he can. We need a way to have the system initiate a domain transition by default.
2.2.5 Default Domain Transitions: type_transition Statement
To support domain transitions occurring by default (as we want in the case of the password program), we need to introduce a new rule, the type transition rule (type_transition). This rule provides a means for the SELinux policy to specify default transitions that should be attempted if an explicit transition was not requested. Let's add the following type transition rule to the allow rules:
type_transition user_t passwd_exec_t : process passwd_t;
The syntax of this rule differs from the allow rule. There are still source and target types (user_t and passwd_exec_t, respectively) and an object class (process). However, instead of permissions, we have a third type, the default type (passwd_t).
Type_transition rules are used for multiple different purposes relating to default type changes. For now, we are concerned with a type_transition rule that has process as its object class. Such rules cause a default domain transition to be attempted. The type_transition rule indicates that, by default on an execve() system call, if the calling process' domain type is user_t and the executable file's type is passwd_exec_t (as is the case in our example in Figure 2-5), a domain transition to a new domain type (passwd_t) will be attempted.
The type_transition rule allows the policy writer to cause default domain transitions to be initiated without explicit user input. This makes type enforcement less obtrusive to the user. In our example, Joe does not want to know anything about access control or types; he wants only to change his password. The system and policy designer can use type_transition rules to make these transitions transparent to the user.