Solaris Privileges

So what makes Solaris Privileges different? Why didn't we copy something else like Trusted Solaris Privileges or "POSIX" capabilities?

Let's start from what we formulated as our requirements near the beginning of our project.

One of the important features of Solaris is complete binary backward compatibility; in order to offer that we needed to design the privilege subsystem in such a manner that current practices, binaries and products would continue to work. Of course, some have solved this issue by providing a system wide knob to turn: root / root + privileges / just privileges. We don't like knobs in our OS; specifically not ones which drastically alter the behaviour of a system. It makes it harder to develop software; it needs to work for all settings. Certain products may require conflicting settings, and so on. So we decided on a "per-process" knob which is largely automatic

With backward compatibility comes the onus on the software developer to develop future proof interfaces; that ruled out all other interfaces as they all have fixed bitmaps and fixed privilege/capability numbers, fixed structure sizes in the programmer visible parts of the system. Solaris Privileges have none of that. And while we could savely reuse the names of the Trusted Solaris interfaces we can not redefine interfaces even from a defunct standard. So we have interfaces which smell like Trusted Solaris but with a completely new userland representation of privileges and privilege sets. We can never have more signals; but we can have more privileges and more privilege sets!

The privileges and privilege sets in Solaris 10 are represented to userland processes and non-core kernel modules as strings; privilege sets are bitmasks of undetermined size; they can only be allocated through the C library routines. Privilege set names are also strings and not plain integer indices; this gives us even more flexibility. A Solaris binary compiled for 4 privilege sets of each 32 privileges will continue to work on a Solaris system with 5 privilege sets each of which can contain 64 privileges and with all the privileges having their internal representation renumbered.

Evolving from the Super user model

The traditional super user model is fairly straighforward; a process has three uids associated with it the effective uid, the real uid and the saved uid. A privileged process is a process which runs with an effective uid 0. A process can temporarily relinquish privileges by setting the effective uid back to the real uid; the saved uid remains 0 or can be set to the real user id as well; in the latter case the process has permanently relinquished its privileged status. But if the saved uid is 0 the process can swap the effective uid back and forth, implementing some form of privilege bracketing. Of course, once such a process is compromised, an exploit can also swap the effective uid back to 0. And the only choice is to have all privileges available or none.

In your typical Unix privilege model the powers formaly associated with uid 0 (PFAWU0) are split into a number of privileges; each process has three different privilege sets; the Effective, Permitted and Inheritable sets, or E, P and I, for short. E closely models the effective uid and P is very much like the saved/real uids: the Effective set determines which privileges a process has active; this is the set of privileges the kernel verifies its privilege checks against. The Permitted set is very much like the saved set; it contains privileges a process is allowed to use. So a process is free to remove any privilege from E and a process is free to add whatever privilege he wants to E as long as it carries that privilege in P. The Inheritable set allows a process to pass privileges on to sub processes; e.g., in case you want to run a webserver with a particular uid but with the additional privilege allowing it to bind to port 80. You put the privilege in the inheritable set (if it is in your permitted or effective set) and the executable will run with that privilege. Privilege bracketing is then performed by adding privileges to E and removing them from E; when a process is done and wishes to relinquish all privileges forever, it removes them from P (which automatically causes removal from E). The fourth privilege set we use in Solaris is the "Limit" set. The privilege set is the upper limit of privilege a process and its off-spring can ever obtain. Solaris uses the limit set for a number of additional things; it is used to restrict the power of the super user in non-global zones and it is used as a mechanism to determine with what privileges a backward compatible uid 0 process runs with.

So how is this compatibility achieved? Well, after a long debate the answer we came up with was really simple: if we want an implementation to be backward compatible for applications which don't know better, what's simpler than a single per-process knob? This know we've called "Privilege Awareness" (PA) and in order to explain this we introduce the notion of Observability; and we make the kernel operate on the observed effective (EO) and observed permitted (PO) set; these contrast with the implementation sets, the actual bits in the kernel credential for a process. The kernel then sees the privilege sets as follows:

EO = euid == 0 && !PA ? L : EI
PO = any uid == 0 && !PA ? L : PI

The observed set closely follows the effective uid and the permitted set models the fact that if any of your uids is 0 you can recover an effective uid of 0. A process becomes privilege aware if it modifies its E or P set or when it requests to become privilege aware. A PA process can also request to become non-PA but such a transition is only possible if the observed sets can be made to remain constant on such a transition. The kernel will try to drop PAness on exec().

The privilege sets can be modified by the process itself but the kernel modifies them also at exec(2) time using the following rules:
            I' = L & I               (L intersected with I)
            E' = P' = I'
            L remains unchanged

For your typical process this is a noop, as the typical process has the following privileges:
            I = P = E = { basic }
            L = { all zone privileges }

As seen here, the system defines a set of privileges known as the "basic" set; it is a set of privileges requires for operations which traditionally weren't privileged in Unix; the design of the basic privilege set and the specific rules about its use make sure that it too can be extended in future with new privileges, without requiring applications which use the feature now privilege to be modified. The current set of basic privileges in Solaris are the privilege needed to fork(), a privilege needed to exec(), a privilege needed to create hardlinks to files not owned by the current effective uid, a privilege needed to send signal to processes outside of your current session and the privilege needed to see other user's processes.
In order to properly work in an environment with a changed basic set, a process would specify the privileges it needs as the basic set + non basic privileges minus the basic privileges it knows it doesn't need. When the basic set is then extended in a later Solaris release the process is guaranteed to continue to work.

What I've outlined up to this point is mostly an enabling technology; in and by itself it does not make the system more secure; but it allows us to harden the system and reduce the risk.

Why privileges should not be orthogonal

My background is very much a hardening background and not a Trusted Computing background; so I have always felt that the privilege model as employed in a number of operating systems has one serious weakness: most of these operating systems define privileges which allow you to acquire more privileges. Typical for such privileges are single privileges which allow you to write directly to disks or kernel memory. What is the point of such privileges? You can just as well give process requiring such privileges all privileges. In Solaris we have defined a very simple rule which I've dubbed The principle of privilege escalation prevention; the basic rule is this: "an operation needs at least as many privileges to be performed as can be gained by executing it". Simply put: if you want to write to /dev/kmem, you will need all privileges! If you want to control a process, you will need at least as many privilege as a process has. If you want to assign privileges to another process, you must have those privileges. If you want to mount on top of something, you must own that something.

And when we find more of such holes we will plug them. Of course, we still have a little bit of a problem with the user with uid 0: he still own all the files and we have not restricted him in writing to his files. So in order to practice safe computing, run with a different uid and perhaps a few extra privileges.

Any, that's all for today of to the beach and see you in two weeks or so. Or see you at Usenix security in San Diego! In the next episode we'l shed some light on the repercussions of changing the credential and the visible changes obviated in userland; we'll answer such important questions as "will door_cred()" survive privileges?

Hi, I'm running S10 build 60 and I'm interested in seeing how "solaris privileges" work. Can you give some examples that I can try out? BTW, tcp wrappers on rpc is great:) I'm very happy to see rpc, sendmail, ssh, etc integrated with tcp_wrappers. Octave

Posted by Octave Orgeron on July 27, 2004 at 04:26 PM MEST #

Post a Comment:
Comments are closed for this entry.



« February 2015