Introduction
One of the guiding principles, if not the guiding principle, of writing secure programs is the principle of least privilege. The principle of least privilege states that any operation carried out by a program should be done so using the least amount of privilege required to successfully complete the task. Traditional flavors of UNIX, including Solaris releases prior to Solaris 10 (the various releases of Trusted Solaris notwithstanding), have very coarse granularity when it comes to the notion of "least" privilege. Historically, the Solaris OS has supported only two levels of privilege: the regular user and the so-called superuser, or root (the latter of which is identified by having a user ID of 0). In other words, prior to the Solaris 10 release, the Solaris OS had an "all or nothing" approach to privileges: a process with an effective user ID of 0 essentially had no restrictions, and all other processes had many restrictions. (We should point out that most other flavors of UNIX also adopt this approach; our focus on the Solaris OS is merely a byproduct of this article's subject. We should also add that the Solaris 10 OS is not the only OS to adopt this privilege separation model: VMS, for example, has had it since its inception.) For a limited subset of uses, another mechanism can be used to restrict the amount of privilege a program uses: set-user-ID or set-group-ID. This is when a process runs with its user and/or group ID set to those of the program's owner rather than those of the invoking user. Suppose we have a program that stores data in a certain file, and that the program has a need to restrict how the file is updated. We could make the program set-user-ID (SUID) root, but a less drastic approach would be to assign the program its own user (and/or group), and have it (the program) become that user when necessary. The Problems With "All or Nothing"
There are many privileged operations a program might want to perform. (When we talk of privileged operations, we mean those operations that cannot or should not be performed by just any process.) As we alluded to previously, the Solaris OS "all or nothing" model means that a program that needs to perform a privileged operation can theoretically perform all privileged operations. For example, a program that needs to bind to a reserved port can also, by design or otherwise, shut down the system. This unsatisfactory state of affairs can be mitigated somewhat by using privilege bracketing. Privilege Bracketing
With privilege bracketing, we enable privileges only when they are actually required, turning them off when they are no longer required. The undesirable opposite is when privileges are always enabled, whether or not they are required. The name comes from the fact that the enabling and disabling of privileges "bracket" the privileged operation, typically around system calls. We can represent this in the following pseudo code:
The idea of privilege bracketing is to narrow the window of a program's vulnerability so that it is as small as possible, reducing the damage any exploit can do. For example, in order for a process to be able to write to a file, it is only necessary for that file to be opened for write access. In other words, we would assert privileges only for the Without exception, all programs that perform privileged operations should use privilege bracketing, even if they use discrete process privileges. (Prior to the Solaris 10 OS, privilege bracketing can be implemented using the Process Privileges
The Solaris 10 OS introduced process privileges, where the god-like abilities available to processes whose effective UID is 0 are broken down into numerous discrete privileges. Following our previous example, now a process that needs to bind to a reserved port can do so by asserting the appropriate privilege (that is, The Solaris implementation of privileges uses four sets: the Permitted set (P), the Inheritable set (I), the Limit set (L), and the Effective set (E). P defines the set of privileges a process can ever potentially use. Privileges not in P cannot be asserted by a program. P is initialized when a process is created by being inherited from the parent. A process can remove privileges from P, but cannot add them. Privileges removed from P are automatically removed from E. I defines the set of privileges that may be passed onto child processes after a call to L defines the upper bound of the set of privileges that a process may assert or pass onto its children. Unlike the other privilege sets, changes to L do not take effect until the next E defines the set of privileges that are currently in use. When a process starts, E and P are equal. Subsequently, E is either a subset of, or is equal to, P. Each process has a flag associated with it that advertises its privilege awareness state; a process is either privilege-aware or it is not-privilege-aware. By default, processes are not-privilege-aware; they become privilege-aware by doing either of the following:
Observed vs. Implementation Privilege Sets
To discuss how privileges are handled with both types of process, let's introduce two types of E and P privilege sets: the observed set, which we'll indicate by prefixing the set name with an "o", and the implementation set. We'll indicate this by prefixing the set name with an "i". When a process becomes privilege-aware, the following assignments take place: iE = oE The oE and oP sets are invariant when a privilege-aware process changes UID. However, if the process is not-privilege-aware, oE and oP are assigned as follows: oE = (euid == 0) ? L : iE In other words, if the euid is equal to 0, oE is set to L, otherwise it is set to iE (which is likely to be a subset of L). Similarly, if the euid, ruid, or suid is equal to 0, oP is set to L, otherwise it is set to iP. A side effect of all this is that it is possible for a non-privilege-aware SUID root process to not have all privileges at its disposal (it is this property that ensures that even UID 0 processes in a local zone cannot affect processes in other zones). We can demonstrate some of this by using the
Now we'll become root and run
As we would expect, a root shell has all privileges available to it. This means we can become another user without typing in that user's password:
Again, as we would expect, the regular user does not have any privileges other than the basic set. After exiting the regular user's shell, so that we are root once again, let's run a shell (as root) with a reduced L, and try to become another user:
This time, despite our user ID being 0, we can't change our user ID because we have insufficient privilege to do so. To investigate this a bit further and determine why we couldn't join the default project or set credentials, we can run
Here we can see that the Process Privileges API
Now that we have some background information about privileges, and have seen an example of their use, let's take a look at some of the functions and data types we use in our privilege-aware programs. These are declared in the Individual privileges are represented by a data type called a
Similarly, a set of privileges is represented by an opaque data type called a Let's now take a look at some of the process privilege functions. Examples showing how we can use these functions appear later in this article.
The
setppriv FunctionThe
The op argument specifies the operation to be performed, and may be one of the following values:
The which argument specifies which privilege set is to be changed, and must be one of the following:
The
priv_set FunctionThe
The op and which arguments have the same meaning as for
The
priv_str_to_set FunctionThe
The first argument, buf, points to a privilege specification that consists of one or more privilege names that are separated by characters in the string pointed to by sep. If endptr is not NULL, then if an error occurs when parsing the string, a pointer to the rest of the string is stored in it. Upon successful completion, a pointer to a The privilege specification may contain one or more of the following strings: "none" for the empty set, "all" for all privileges, "zone" for all privileges available within a zone, and "basic," which is the set of privileges traditionally granted to all users on login (except for root). Several other related functions exist, which we don't describe here (for example,
The
priv_addset FunctionThis function adds a privilege to a privilege set, and has the following prototype:
The privilege named by priv is added to the set pointed to by sp. Conversely, a function called
The
priv_inverse FunctionThe
The privilege set pointed to by sp is inverted and stored in sp.
The
priv_freeset FunctionWhen we're finished using a privilege set, we must free it using
The storage for the privilege set pointed to by sp is freed. Examples
All this theory is great, but how do we use these functions in practice? Let's assume that we're writing an application that needs to be able to read a file or files it wouldn't normally be able to. A quick read of the We'll write three functions:
Example: The
priv_init functionThe Calling The following listing shows the code for
Let's take a look at
Example: The
priv_on functionWe call the
Let's see how this seven-line function works.
Example: The
priv_off functionThis function is the converse of
Example: Privilege Bracketing Using
priv_on and priv_offThe following are a few lines that show how we use privilege bracketing around a library call (in this case,
Although these examples help us understand the privilege API, we ideally need to examine a complete application to see how to use them for real. Space limitations prevent us from doing so in this article, but interested readers are referred to the source code for the author's lock program, from which these examples were taken. Inspired by its BSD namesake, lock is a CDDL-licensed open source utility. It can be found on the Solaris lock home page at http://www.rite-group.com/rich/sw/lock.html. Summary
This article has described Solaris privileges, and their reason for being. It briefly compared the privilege model with the traditional UNIX "all or nothing" model, and discussed privilege bracketing. We then described the Solaris implementation of privileges, and a few of the functions that make up the Solaris privileges API. Finally, we showed three examples illustrating the use of this API: Acknowledgments
Many thanks to Glenn Brunette for reviewing this article.
Recommended Reading
The author's book, Solaris Systems Programming, is essential for readers developing Solaris applications. The OpenSolaris community blogs (especially that of Casper Dik) are another great source of information. Other useful resources are the Solaris Security for Developers Guide (on docs.sun.com) and two Sun BluePrints documents: Privilege Debugging in the Solaris 10 Operating System and Limiting Service Privileges in the Solaris 10 Operating System. Here is more information on these resources:
Rich Teer is an independent consultant who has been an active member of the Solaris community for more than 10 years. He is the author of the best-selling Sun Microsystems Press book, Solaris Systems Programming, and several Solaris OS-related articles. He was a member of the OpenSolaris pilot program, and currently serves on the OpenSolaris Community Advisory Board (CAB). Rich lives in Kelowna, British Columbia, Canada, with his wife, Jenny, and their canine child, Judge. His web site can be found at www.rite-group.com/rich.
| |||||||||||||||||||||||||||||||||||
Oracle is reviewing the Sun product roadmap and will provide guidance to customers in accordance with Oracle's standard product communication policies. Any resulting features and timing of release of such features as determined by Oracle's review of roadmaps, are at the sole discretion of Oracle. All product roadmap information, whether communicated by Sun Microsystems or by Oracle, does not represent a commitment to deliver any material, code, or functionality, and should not be relied upon in making purchasing decisions. It is intended for information purposes only, and may not be incorporated into any contract.
|
| ||||||||||||