5.4. Privilege Awareness: The DetailsIn this section we present the precise semantics and describe the various features of our privilege model. 5.4.1. Per-Process StateWe extend the per-process state from Section 5.3.2 with the privilege awareness property, pas, and the semantically void[2] privilege debugging flag, db, and get
The notion of observed Effective and Permitted set is used only in those cases in which we need to distinguish between the implementation sets EI and PI and the observed sets EO and PO. When talking about the semantics and the user-visible behavior, we use E and P for EO and PO. These are also the values returned by getppriv(2) and as shown by ppriv(1). For reference, these are the observation rules:
The initial process init starts off with EI = PI = I = IO and L = P. IO is the set of basic privileges and can be empty. This default process credential behaves exactly like the superuser model: When the effective uid is non-zero, the process has the default privileges; when the effective uid is 0, the process has all privileges. 5.4.2. Privilege Awareness State TransitionsIn this section we describe the transitions between being privilege aware and not being privilege aware. This can only happen if the observed set invariance condition is met. That is, when a process transitions between PA and NPA, no changes to E and P are observed. 5.4.2.1. Becoming Privilege AwareThere are no restrictions on becoming privilege aware. Assuming pas = NPA, the following changes to the credential take place when transitioning to PA:
EO and PO are constant under this operation. This mechanism converts a uid 0 process that appears to follow the superuser model to a fully privileged process for which uid manipulations no longer have an effect on the privilege status. 5.4.2.2. Losing Privilege AwarenessThere are several times when a process may want to lose the privilege awareness, for example, on exec(2). Several restrictions apply since not all combinations of privilege sets can successfully and safely be converted to an equivalent NPA process. We derived the following rules from the observed set invariance property:
The last rule tells us that in a process without any 0 uids, pas can be changed at will. When the conditions are met and a process transitions to NPA, C.pas is set to NPA and the implementation sets are conditionally changed as follows:
Again note that EO and PO are unchanged under these operations. 5.4.3. Privilege State ManipulationThe per-process state changes at various times, either as a result of explicit actions or as a side effect of other actions. In some cases the changes are merely observed and not reflected in the underlying data structures. 5.4.3.1. What Happens When User IDs Change?In all circumstances, the actual implementation sets remain unchanged, but we can distinguish several cases. In privilege-aware processes, no changes are observed. In processes not having any uids set to 0, no changes are observed, even if privileges are used to obtain uid 0. In non-privilege-aware processes, EO alternates between L and EI depending on the value of the effective uid. PO appears as L until the last uid is set to non-zero; it then reverts to PI. 5.4.3.2. What Happens When a Process Is Created?When a process is created by any of fork(2), vfork(2), or fork1(2), the full privilege state is inherited by the child process. 5.4.3.3. What Happens When a Program Is Exec'd?When any of members of the exec(2) family is called, the following rules apply:
We include X.A and X.F in these equations both for use in future implementations and to keep open the option of per-mount point allowed sets and some form of forced privilege emulation. At one stage the prototype emulated a single forced privilege by using the setgid bit. A single privilege appears to be sufficient for many Solaris applications; for example, commands using rcmd(3socket), such as rsh(1) and rlogin(1) as well as applications using raw sockets, such as ping(1m) and TRaceroute(1m). The db property is inherited across exec(2); pas is adjusted according to the following rules:
The changes to the implementation sets EI and PI are defined in such a way that the observed sets EO and PO remain the same. As a consequence of these rules, it is not always possible for PA processes to gain privileges by executing a set-uid root executable. It is possible to gain privileges in that way for NPA processes. In addition to the current practice of marking a process NOCD|SUGID[3] if the real and effective IDs do not match, we add the marker if the permitted set of the process calling exec(2) is not a superset of the inheritable set. That is, when the child process runs with more privileges, it is awarded additional protection.
An implementation of Trusted Solaris might force most or all processes to PA. 5.4.3.4. Manipulating Privileges DirectlyThis project adds a system call family, setppriv(2), that allows a process to manipulate its privileges directly. The function setppriv() takes three arguments: the operation to perform, which is one of PRIV_OFF, PRIV_ON, or PRIV_SET; the privilege set name; and a privilege set with privileges to switch off or on, or which can completely replace the current set. The Effective set is most useful to perform privilege bracketing. In the superuser world, privilege bracketing looks like this: /* program start */ uid = getuid(); seteuid(uid); /* privilege bracketing */ seteuid(0); /* code requiring super-user privileges */ .... seteuid(uid); /* ordinary code */ .... /* Permanently giving up root */ setreuid(uid, uid); With process privileges, the code (simplified) becomes /* program start */ getppriv(PRIV_PERMITTED, pset); setppriv(PRIV_SET, PRIV_EFFECTIVE, emptyset); /* privilege bracketing */ setppriv(PRIV_SET, PRIV_EFFECTIVE, pset); /* code requiring privileges */ .... setppriv(PRIV_SET, PRIV_EFFECTIVE, emptyset); /* ordinary code */ .... /* Permanently giving up privileges */ setppriv(PRIV_SET, PRIV_PERMITTED, emptyset); The added advantage is that this manipulation can be done at the level of single privileges. For this purpose, a single convenience function, priv_set(3c), is provided. Removing privileges from E is not restricted; only privileges in P can be added to E. A process manipulating E needs to be PA; it will transition to PA following the rules in Section 5.4.2, if necessary. The permitted set can only be shrunk; a process should shrink its Permitted set if it does not have any future need for the privileges. Privileges removed from P are automatically removed from E, enforcing E P will automatically transition to PA. The Inheritable set can be added to and removed from; privileges from P can be added freely to I. Privileges can be removed from I without restriction. Because changing I does not influence EO or PO, a process does not need to transition to PA when manipulating I but might transition to PA on subsequent exec(2). I is allowed to be a superset of P, that is, removing privileges from P does not affect I. The Limit set cannot be added to by any mechanism. Privileges can be removed from the Limit set, but not without peril. Applications running under uid 0 usually assume full privileges on the OS; this often leads to no error checking for privileged operations or, worse, different behavior of certain operations whether you are privileged or not. For example, setuid(non-0) will reset all uids to non-0 if the effective uid is 0 but will only change the effective uid if a process is not privileged but can swap uids because of the value of the saved uid. It was discovered[4] that removing selected privileges from a further privileged process could render it insecure. To rectify the problem, we defined the set of unsafe privileges, the privileges without which it is too risky to allow privilege elevation. A process that does not have in L all privileges from the unsafe set, currently PRIV_PROC_SETID, PRIV_SYS_RESOURCE, and PRIV_PROC_AUDIT, will fail to execute set-uid 0 processes.
Changing the limit set would influence PO and EO, so a process needs to transition to PA when it changes L. A process that manipulates its privileges is marked NOCD, which awards the process additional protection. 5.4.3.5. Manipulating pas DirectlyUsing getpflags(2) and setpflags(2) processes can query and change pas. The latter function enforces the rules outlined in Section 5.4.2. 5.4.3.6. Manipulating Privileges through proc(4)The proc(4) interface is extended to allow inspecting and changing privileges through the /proc file system. The modifications through this interface are severely restricted:
In addition, if a process can modify the state of a process, it can always switch db on or off. The above rules help enforce the fact that the special privilege PRIV_PROC_OWNER is not sufficient to gain control over a process when such control can lead to escalation of privileges. In the special case in which any of the user IDs in the target process is zero, the full set of privileges is required. The privilege does give unrestricted read access. 5.4.4. Privilege Escalation PreventionThe privilege escalation is defined as the process whereby a subject can obtain one or more privileges by performing an action that does not require those privileges if that action is not specifically allowed by the security policy. One of the often recognized problems with privilege implementation is that some privileges provide back doors to others. We can think of many cases where this can happen:
While L could make certain that such privileges aren't accorded to ordinary users, such restrictions on L may preclude perfectly valid, controlled uses of such privileges by privileged programs, so we feel that we should put extra hurdles whenever we find a place where certain privileges can be leveraged to more privileges. Where we can identify such cases of privilege escalation, we require a process to either possess as many privileges as it would be able to obtain with the specific action or, in the specific instance of a process PS granting privileges to a process PO, we require that those privileges do not exceed PS.E 5.4.5. The Trouble with uid 0 The superuser's user ID is used not only for privilege checks but also for discretionary access control on files and devices, because the superuser owns most files and devices on the system. Even without any of the PRIV_FILE_DAC* discretionary access overrides, a process with uid 0 still has near complete reign of the system. It therefore stands to reason that we run as few system processes with uid 0 as possible. Rather, we run the daemons that need privileges as user daemon with some privileges rather than as root with most privileges removed. When we look at the privileges (for the complete set used in our implementation see privileges(5)), we find that many privileges are potentially too power-fulthey make no distinction between objects owned by root and objects owned by ordinary users, and without such distinction, they can switch to just any uid or to root. In that situation, MAC labeling would really help. In keeping with our Prevention of Privilege Escalation policy, we will require the full set of privileges if the effective uid of a process is not 0 when that process needs a privilege to modify an object owned by uid 0 or wants to control a process with any of its uids 0. If a process switches from any uid to 0 and none of its other uids is 0, it will also require all privileges. Ordinary file, directory, and other permissions still apply to root-owned objects; it is not until a privilege is needed that the additional requirement comes into force. For all other intents and purposes, root-owned objects behave exactly the same as ordinary objects. The more interesting applications of process privileges will be the use in processes not running with uid 0. Our implementation runs a few daemons with a very restricted set of privileges. We can run daemons under different uids, and we document which privileges we actually use at the same time. The daemons themselves have been changed to drop the excess privileges and run under a different uid. 5.4.6. Basic PrivilegesIn certain situations, you may think that the standard Solaris user has too many privileges enabled by default. Or you might want to enable unrestricted chown(2) for just a handful of users. Or make sure that a process never forks or execs, a feature that can be used to make any editor safe for use in a restricted menu environment. If you are using quotas, you may sometimes want to prevent users from creating hard links to files they do not own. To facilitate such features, we have implemented what we call basic privileges. Basic privileges are just like ordinary privileges except that the default I contains all basic privileges. Administrators can assign different privileges to users or withdraw the basic privileges away from specific users. The initial set of privileges, I0, is assigned the full set of basic privileges, B. This initial set is inherited by all processes and also determines the privileges associated with ordinary users authenticated to the kernel services using a form of RPC, such as NFS. The system(4) variable rstchown determines whether the privilege file_chown_self is present in I0; the ability to change ownership of files owned by the current effective uid is now per-process and can be awarded user by user. The set of basic privileges is part of the information the kernel supplies about its configuration; it is, therefore, easy to determine whether a process runs with more than the basic set of privileges. So that more standard functionality can migrate into the set of basic privileges, the functions that convert privilege sets to strings expand the basic privileges into the string "basic" by default. If one or more privileges from B are missing from a set, the expansion will read "basic,!basic missing1,!basic missing2", thus preserving the privileges the set conveys over future expansions of B. Options change the default behavior for those cases in which the literal privilege set is required or in which the shortest output form is wanted. To write a process or daemon that uses privileges that need to be ported forward, programmers must take care that the process or daemon requires B in addition to the other privileges they may need. Individual basic privileges that are known not to be required can be removed from P. For example, if we were to make open(..., O *WR*) a basic privilege, such a daemon would retain that privilege simply by retaining the basic privilege set at startup. In the OS release with the additional basic privilege, the process or daemon would automatically get that additional privilege. 5.4.7. Privileges and the Runtime EnvironmentThe Solaris runtime environment, consisting of the runtime linker and the runtime libraries, is user configurable to a considerable degree, mostly with environment variables. The runtime environment restricts the configurability for processes that are set-uid or set-gid. Applications that completely assume different identities in subprocesses, for example, su(1m) and sendmail(1m), clean the environment before executing subprocesses that cannot determine that they run under an assumed uid, for example, because the effective and real uid are now identical. The runtime environment does not impose restrictions on privileged processes merely because they are privileged; rather, it does so only when it knows that the privileged processes derive from unprivileged processes, for example, through set-uid. In Solaris, this mechanism remains largely unchanged; it remains the responsibility of applications to vet the environment if privileges are moved to I. The kernel will take care of those cases in which extra privileges are gained through setuid or, in the future, forced privileges. In Solaris, this responsibility is handled by extending issetugid(2). In the initial implementation, this solution is unnecessary, since the only way to gain privileges is through set-uid 0 executables and those are already caught by the current mechanism. The interface between the runtime linker and kernel is slightly changed; the kernel has better knowledge about how the process transitioned and how it was started; a new aux vector, AT_SUN_AUXFLAGS, is passed to the runtime linker. Its value is a bit mask for which we define a single bit, AF_SUN_SETUGID, which, when set, indicates that the runtime linker can trust only secure directories. 5.4.8. Privileges and NFSThe network file system, NFS, authorizes operations on files according to network credentials. In the insecure, default mode of operation, the NFS client sends the identity of the caller directly with each request, without giving the server any way to verify the credentials of the process on the NFS client. Proper authorizations schemes have been developed, but all of them focus on the "user" for granularity of access control. But because proper authorization is not widespread, NFS servers typically map requests by the superuser to a guest or nobody credential. Allowing privileged operations over NFS is atypical. There is one use of NFS for which this approach is not fine: the use for diskless clients, including the case of network installation. Diskless clients are slightly more complicated because root write access is also required. In both of the above cases, the NFS file systems are exported with full root access. We achieve compatibility by still having our system and install processes run as root; this gives those systems full file system access for those insecure exports. In those cases in which daemons are converted to run as a uid other than root, the appropriate ownership or permission changes are made. In the future we can think of client-verified (for example, digitally signed) additional credentials that hold the effective privileges of a process. However, we believe that allowing clients to wield privileges on NFS servers is generally ill-advised. It is NFS that prevents us from replacing the kernel tunable rstchown with the privilege PRIV_FILE_CHOWN_SELF. Several kernel RPC functions had their implementation changed: the NFS-specific privilege check was moved to the nfssys system call because it was out of place in the RPC layer. The NFS system generates kernel credentials from network credentials in several locations; we changed the code that generates credentials and the functions to generate full credentials. This changes the signatures of certain kernel rpc functions. The function authdes getucred is renamed as kauthdes_getucred because the former also exists in the rpc libraries. The signature of sec_svc_getcred just changes. /* Old signatures */ int sec_svc_getcred(struct svc_req *req, uid_t *uid, gid_t *gid, short *ngroups, gid_t *groups, caddr_t *principal, int *secmod) int authdes_getucred(const struct authdes_cred *, uid_t *, gid_t *, short *, gid_t *); /* New signatures */ int sec_svc_getcred(struct svc_req *req, cred_t *cr, caddr_t *principal, int *secmod); int kauthdes_getucred(const struct authdes_cred *adc, cred_t *cr); 5.4.9. Privileges and Third-Party File SystemsThere are no changes to the file system interfaces, so preexisting file systems will continue to work unless they reference the cr_groups field directly. But unmodified file systems will not honor privileges; they most likely will still require uid 0 for privileged operations until they are converted to using the Least Privilege file system policy routines. |