This chapter describes how to develop privileged applications.
The chapter covers the following topics:
A privileged application is an application that can override system controls and check for specific user IDs (UIDs), group IDs (GIDs), authorizations, or privileges. These access control elements are assigned by system administrators. For a general discussion of how administrators use these access control elements, see Chapter 8, Using Roles and Privileges (Overview), in System Administration Guide: Security Services.
The Oracle Solaris operating system provides developers with two elements that enable a finer-grained delegation of privileges:
Privileges - A privilege is a discrete right that can be granted to an application. With a privilege, a process can perform an operation that would otherwise be prohibited by the Oracle Solaris OS. For example, processes cannot normally open data files without the proper file permission. The file_dac_read privilege provides a process with the ability to override the UNIX file permissions for reading a file. Privileges are enforced at the kernel level.
Authorizations - An authorization is a permission for performing a class of actions that are otherwise prohibited by security policy. An authorization can be assigned to a role or user. Authorizations are enforced at the user level.
The difference between authorizations and privileges has to do with the level at which the policy of who can do what is enforced. Privileges are enforced at the kernel level. Without the proper privilege, a process cannot perform specific operations in a privileged application. Authorizations enforce policy at the user application level. An authorization might be required for access to a privileged application or for specific operations within a privileged application.
A privilege is a discrete right that is granted to a process to perform an operation that would otherwise be prohibited by the Oracle Solaris operating system. Most programs do not use privileges, because a program typically operates within the bounds of the system security policy.
Privileges are assigned by an administrator. Privileges are enabled according to the design of the program. At login or when a profile shell is entered, the administrator's privilege assignments apply to any commands that are executed in the shell. When an application is run, privileges are turned on or turned off programmatically. If a new program is started by using the exec(1) command, that program can potentially use all of the parent process's inheritable privileges. However, that program cannot add any new privileges.
System administrators are responsible for assigning privileges to commands. For more information on privilege assignment, see Privileges (Overview) in System Administration Guide: Security Services.
Every process has four sets of privileges that determine whether a process can use a particular privilege:
Permitted privilege set
Inheritable privilege set
Limit privilege set
Effective privilege set
All privileges that a process can ever potentially use must be included in the permitted set. Conversely, any privilege that is never to be used should be excluded from the permitted set for that program.
When a process is started, that process inherits the permitted privilege set from the parent process. Typically at login or from a new profile shell, all privileges are included in the initial set of permitted privileges. The privileges in this set are specified by the administrator. Each child process can remove privileges from the permitted set, but the child cannot add other privileges to the permitted set. As a security precaution, you should remove those privileges from the permitted set that the program never uses. In this way, a program can be protected from using an incorrectly assigned or inherited privilege.
Privileges that are removed from the permitted set are automatically removed from the effective set.
At login or from a new profile shell, the inheritable set contains the privileges that have been specified by the administrator. These inheritable privileges can potentially be passed on to child processes after an exec(1) call. A process should remove any unnecessary privileges to prevent these privileges from passing on to a child process. Often the permitted and inheritable sets are the same. However, there can be cases where a privilege is taken out of the inheritable set, but that privilege remains in the permitted set.
The limit set enables a developer to control which privileges a process can exercise or pass on to child processes. A child process and the descendant processes can only obtain privileges that are in the limit set. When a setuid(0) function is executed, the limit set determines the privileges that the application is permitted to use. The limit set is enforced at exec(1) time. Removal of privileges from the limit set does not affect any other sets until the exec(1) is performed.
The privileges that a process can actually use are in the process's effective set. At the start of a program, the effective set is equal to the permitted set. Afterwards, the effective set is either a subset of or is equal to the permitted set.
A good practice is to reduce the effective set to the set of basic privileges. The basic privilege set, which contains the core privileges, is described in Privilege Categories. Remove completely any privileges that are not needed in the program. Toggle off any basic privileges until that privilege is needed. For example, the file_dac_read privilege, enables all files to be read. A program can have multiple routines for reading files. The program turns off all privileges initially and turns on file_dac_read, for appropriate reading routines. The developer thus ensures that the program cannot exercise the file_dac_read privilege for the wrong reading routines. This practice is called privilege bracketing. Privilege bracketing is demonstrated in Privilege Coding Example.
To accommodate legacy applications, the implementation of privileges works with both the superuser and privilege models. This accommodation is achieved through use of the PRIV_AWARE flag, which indicates that a program works with privileges. The PRIV_AWARE flag is handled automatically by the operating system.
Consider a child process that is not aware of privileges. The PRIV_AWARE flag for that process would be false. Any privileges that have been inherited from the parent process are available in the permitted and effective sets. If the child sets a UID to 0, the process's effective and permitted sets are restricted to those privileges in the limit set. The child process does not gain full superuser powers. Thus, the limit set of a privilege-aware process restricts the superuser privileges of any non-privilege-aware child processes. If the child process modifies any privilege set, then the PRIV_AWARE flag is set to true.
Privileges are logically grouped on the basis of the scope of the privilege, as follows:
Basic privileges – The core privileges that are needed for minimal operation.
PRIV_FILE_LINK_ANY – Allows a process to create hard links to files that are owned by a UID other than the process's effective UID.
PRIV_PROC_FORK – Allows a process to call fork(), fork1(), or vfork().
PRIV_PROC_SESSION – Allows a process to send signals or trace processes outside its session.
PRIV_PROC_INFO – Allows a process to examine the status of processes outside of those processes to which the inquiring process can send signals. Without this privilege, processes that cannot be seen in /proc cannot be examined.
In general, the basic privileges should be assigned as a set rather than individually. This approach ensures that any basic privileges that are released in an update to the Oracle Solaris OS will be included in the assignment. On the other hand, a privilege that is known not to be needed by a program should be explicitly turned off. For example, the proc_exec privilege should be turned off if the program is not intended to exec(1) sub-processes.
File system privileges.
See the privileges(5) man page for a complete list of the Oracle Solaris privileges with descriptions.
Oracle Solaris provides the zones facility, which lets an administrator set up isolated environments for running applications. See zones(5). Since a process in a zone is prevented from monitoring or interfering with other activity in the system outside of that zone, any privileges on that process are limited to the zone as well. However, if needed, the PRIV_PROC_ZONE privilege can be applied to processes in the global zone that need privileges to operate in non–global zones.
This section discusses the interfaces for working with privileges. To use the privilege programming interfaces, you need the following header file.
#include <priv.h>
An example demonstrating how privilege interfaces are used in a privileged application is also provided.
The major data types that are used by the privilege interfaces are:
Privilege type – An individual privilege is represented by the priv_t type definition. You initialize a variable of type priv_t with a privilege ID string, as follows:
priv_t priv_id = PRIV_FILE_DAC_WRITE;
Privilege set type – Privilege sets are represented by the priv_set_t data structure. Use one of the privilege manipulation functions shown in Table 2–1 to initialize variables of type priv_set_t.
Privilege operation type – The type of operation to be performed on a file or process privilege set is represented by the priv_op_t type definition. Not all operations are valid for every type of privilege set. Read the privilege set descriptions in Programming with Privileges for details.
Privilege operations can have the following values:
PRIV_ON – Turn the privileges that have been asserted in the priv_set_t structure on in the specified file or process privilege set.
PRIV_OFF – Turn the privileges asserted in the priv_set_t structure off in the specified file or process privilege set.
PRIV_SET – Set the privileges in the specified file or process privilege set to the privileges asserted in the priv_set_t structure. If the structure is initialized to empty, PRIV_SET sets the privilege set to none.
The following table lists the interfaces for using privileges. Descriptions of some major privilege interfaces are provided after the table.
Table 2–1 Interfaces for Using Privileges
Purpose |
Functions |
Additional Comments |
---|---|---|
Getting and setting privilege sets |
setppriv() and getppriv() are system calls. priv_ineffect() and priv_set() are wrappers for convenience. |
|
Identifying and translating privileges |
priv_str_to_set(3C), priv_set_to_str(3C), priv_getbyname(3C), priv_getbynum(3C), priv_getsetbyname(3C), priv_getsetbynum(3C) |
These functions map the specified privilege or privilege set to a name or a number. |
Manipulating privilege sets |
priv_allocset(3C), priv_freeset(3C), priv_emptyset(3C), priv_fillset(3C), priv_isemptyset(3C), priv_isfullset(3C), priv_isequalset(3C), priv_issubset(3C), priv_intersect(3C), priv_union(3C), priv_inverse(3C), priv_addset(3C), priv_copyset(3C), priv_delset(3C), priv_ismember(3C) |
These functions are concerned with privilege memory allocation, testing, and various set operations. |
Getting and setting process flags |
The PRIV_AWARE process flag indicates whether the process understands privileges or runs under the superuser model. PRIV_DEBUG is used for privilege debugging. |
|
Low-level credential manipulation |
These routines are used for debugging, low-level system calls, and kernel calls. |
The main function for setting privileges is setppriv(), which has the following syntax:
int setppriv(priv_op_t op, priv_ptype_t which, \ const priv_set_t *set);
op represents the privilege operation that is to be performed. The op parameter has one of three possible values:
PRIV_ON – Adds the privileges that are specified by the set variable to the set type that is specified by which
PRIV_OFF – Removes the privileges that are specified by the set variable from the set type that is specified by which
PRIV_SET – Uses the privileges that are specified by the set variable to replace privileges in the set type that is specified by which
which specifies the type of privilege set to be changed, as follows:
PRIV_PERMITTED
PRIV_EFFECTIVE
PRIV_INHERITABLE
PRIV_LIMIT
set specifies the privileges to be used in the change operation.
In addition, a convenience function is provided: priv_set().
These functions are convenient for mapping privilege names with their numeric values. priv_str_to_set() is a typical function in this family. priv_str_to_set() has the following syntax:
priv_set_t *priv_str_to_set(const char *buf, const char *set, \ const char **endptr);
priv_str_to_set() takes a string of privilege names that are specified in buf. priv_str_to_set() returns a set of privilege values that can be combined with one of the four privilege sets. **endptr can be used to debug parsing errors.
Note that the following keywords can be included in buf:
“all” indicates all defined privileges. “all,!priv_name,...” enables you to specify all privileges except the indicated privileges.
Constructions that use “priv_set, “!priv_name,...” subtract the specified privilege from the specified set of privileges. Do not use “!priv_name,...” without first specifying a set because with no privilege set to subtract from, the construction subtracts the specified privileges from an empty set of privileges and effectively indicates no privileges.
“none” indicates no privileges.
“basic” indicates the set of privileges that are required to perform operations that are traditionally granted to all users on login to a standard UNIX operating system.
This section compares how privileges are bracketed using the superuser model and the least privilege model.
The following example demonstrates how privileged operations are bracketed in the superuser model.
/* Program start */ uid = getuid(); seteuid(uid); /* Privilege bracketing */ seteuid(0); /* Code requiring superuser capability */ ... /* End of code requiring superuser capability */ seteuid(uid); ... /* Give up superuser ability permanently */ setreuid(uid,uid);
This example demonstrates how privileged operations are bracketed in the least privilege model. The example uses the following assumptions:
The program is setuid 0.
The permitted and effective sets are initially set to all privileges as a result of setuid 0.
The inheritable set is initially set to the basic privileges.
The limit set is initially set to all privileges.
An explanation of the example follows the code listing.
The source code for this example is also available through the Sun download center. See http://www.sun.com/download/products.xml?id=41912db5.
1 #include <priv.h> 2 /* Always use the basic set. The Basic set might grow in future 3 * releases and potentially retrict actions that are currently 4 * unrestricted */ 5 priv_set_t *temp = priv_str_to_set("basic", ",", NULL); 6 /* PRIV_FILE_DAC_READ is needed in this example */ 7 (void) priv_addset(temp, PRIV_FILE_DAC_READ); 8 /* PRIV_PROC_EXEC is no longer needed after program starts */ 9 (void) priv_delset(temp, PRIV_PROC_EXEC); 10 /* Compute the set of privileges that are never needed */ 11 priv_inverse(temp); 12 /* Remove the set of unneeded privs from Permitted (and by 13 * implication from Effective) */ 14 (void) setppriv(PRIV_OFF, PRIV_PERMITTED, temp); 15 /* Remove unneeded priv set from Limit to be safe */ 16 (void) setppriv(PRIV_OFF, PRIV_LIMIT, temp); 17 /* Done with temp */ 18 priv_freeset(temp); 19 /* Now get rid of the euid that brought us extra privs */ 20 (void) seteuid(getuid()); 21 /* Toggle PRIV_FILE_DAC_READ off while it is unneeded */ 22 priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL); 23 /* Toggle PRIV_FILE_DAC_READ on when special privilege is needed*/ 24 priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL); 25 fd = open("/some/retricted/file", O_RDONLY); 26 /* Toggle PRIV_FILE_DAC_READ off after it has been used */ 27 priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL); 28 /* Remove PRIV_FILE_DAC_READ when it is no longer needed */ 29 priv_set(PRIV_OFF, PRIV_ALLSETS, PRIV_FILE_DAC_READ, NULL);
The program defines a variable that is named temp. The temp variable determines the set of privileges that are not needed by this program. Initially in line 5, temp is defined to contain the set of basic privileges. In line 7, the file_dac_read privilege is added to temp. The proc_exec privilege is necessary to exec(1) new processes, which is not permitted in this program. Therefore, proc_exec is removed from temp in line 9 so that the exec(1) command cannot execute new processes.
At this point, temp contains only those privileges that are needed by the program, that is, the basic set plus file_dac_read minus proc_exec. In line 11, the priv_inverse() function computes the inverse of temp and resets the value of temp to the inverse. The inverse is the result of subtracting the specified set, temp in this case, from the set of all possible privileges. As a result of line 11, temp now contains those privileges that are never needed by the program. In line 14, the unneeded privileges that are defined by temp are subtracted from the permitted set. This removal effectively removes the privileges from the effective set as well. In line 16, the unneeded privileges are removed from the limit set. In line 18, the temp variable is freed, since temp is no longer needed.
This program is aware of privileges. Accordingly, the program does not use setuid and can reset the effective UID to the user's real UID in line 20.
The file_dac_read privilege is turned off in line 22 through removal from the effective set. In a real program, other activities would take place before file_dac_read is needed. In this sample program, file_dac_read is needed for to read a file in line 25. Accordingly, file_dac_read is turned on in line 24. Immediately after the file is read, file_dac_read is again removed from the effective set. When all files have been read, file_dac_read is removed for good by turning off file_dac_read in all privilege sets.
The following table shows the transition of the privilege sets as the program progresses. The line numbers are indicated.
Table 2–2 Privilege Set Transition
Step |
temp Set |
Permitted Privilege Set |
Effective Privilege Set |
Limit Privilege Set |
---|---|---|---|---|
Initially |
– |
all |
all |
all |
Line 5 – temp is set to basic privileges |
basic |
all |
all |
all |
Line 7 – file_dac_read is added to temp. |
basic + file_dac_read |
all |
all |
all |
Line 9 – proc_exec is removed from temp. |
basic + file_dac_read – proc_exec |
all |
all |
all |
Line 11 – temp is reset to the inverse. |
all – (basic + file_dac_read – proc_exec) |
all |
all |
all |
Line 14 – The unneeded privileges are turned off in the permitted set. |
all – (basic + file_dac_read – proc_exec) |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
all |
Line 16 – The unneeded privileges are turned off in the limit set. |
all – (basic + file_dac_read – proc_exec) |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
Line 18 – The temp file is freed. |
– |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
Line 22 – Turn off file_dac_read until needed. |
– |
basic – proc_exec |
basic – proc_exec |
basic + file_dac_read – proc_exec |
Line 24 – Turn on file_dac_read when needed. |
– |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
basic + file_dac_read – proc_exec |
Line 27 – Turn off file_dac_read after read() operation. |
– |
basic – proc_exec |
basic – proc_exec |
basic + file_dac_read – proc_exec |
Line 29 – Removefile_dac_read from all sets when no longer needed. |
– |
basic – proc_exec |
basic – proc_exec |
basic – proc_exec |
This section provides the following suggestions for developing privileged applications:
Use an isolated system. You should never debug privileged applications on a production system, as an incomplete privileged application can compromise security.
Set IDs properly. The calling process needs the proc_setid privilege in its effective set to change its user ID, group ID, or supplemental group ID.
Use privilege bracketing. When an application uses privilege, system security policy is being overridden. Privileged tasks should be bracketed and carefully controlled to ensure that sensitive information is not compromised. See Privilege Coding Example for information on how to bracket privileges.
Start with the basic privileges. The basic privileges are necessary for minimal operation. A privileged application should start with the basic set. The application should then subtract and add privileges appropriately.
A typical start-up scenario follows.
The daemon starts up as root.
The daemon turns on the basic privilege set.
The daemon turns off any basic privileges that are unnecessary, for example, PRIV_FILE_LINK_ANY.
The daemon adds any other privileges that are needed, for example, PRIV_FILE_DAC_READ.
The daemon switches to the daemon UID.
Avoid shell escapes. The new process in a shell escape can use any of the privileges in the parent process's inheritable set. An end user can therefore potentially violate trust through a shell escape. For example, some mail applications might interpret the !command line as a command and would execute that line. An end user could thus create a script to take advantage of any mail application privileges. The removal of unnecessary shell escapes is a good practice.
Authorizations are stored in the /etc/security/auth_attr file. To create an application that uses authorizations, take the following steps:
Scan the /etc/security/auth_attr for one or more appropriate authorizations.
Check for the required authorization at the beginning of the program using the chkauthattr(3SECDB) function.
The chkauthattr() function searches for the authorization in order in the following locations:
AUTHS_GRANTED key in the policy.conf(4) database – AUTHS_GRANTED indicates authorizations that have been assigned by default.
PROFS_GRANTED key in the policy.conf(4) database – PROFS_GRANTED indicates rights profiles that have been assigned by default. chkauthattr() checks these rights profiles for the specified authorization.
The user_attr(4) database – This database stores security attributes that have been assigned to users.
The prof_attr(4) database – This database stores rights profiles that have been assigned to users.
If chkauthattr() cannot find the right authorization in any of these places, then the user is denied access to the program.
Let the administrator know which authorizations are required for this application. You can inform the administrators through man pages or other documentation.
The following code snippet demonstrates how the chkauthattr() function can be used to check a user's authorization. In this case, the program checks for the solaris.job.admin authorization. If the user has this authorization, the user is able to read or write to other users' files. Without the authorization, the user can operate on owned files only.
/* Define override privileges */ priv_set_t *override_privs = priv_allocset(); /* Clear privilege set before adding privileges. */ priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, priv_FILE_DAC_WRITE, NULL); priv_addset(override_privs, PRIV_FILE_DAC_READ); priv_addset(override_privs, PRIV_FILE_DAC_WRITE); if (!chkauthattr("solaris.jobs.admin", username)) { /* turn off privileges */ setppriv(PRIV_OFF, PRIV_EFFECTIVE, override_privs); } /* Authorized users continue to run with privileges */ /* Other users can read or write to their own files only */