Process privileges

Updated: April 19, 2023

In systems where security is important, applications should run with the fewest privileges possible. This practice helps reduce the impact of possible compromises and can also help lower the privilege escalation attack surface of the device.

The more difficult it is for attackers to elevate an application's privileges, the better; forcing attackers to chain multiple attacks against various applications that each have minimal sets of permissions is ideal.

Services usually require a larger set of privileges when they start up. Ideally, they are launched in this more privileged state and then the process itself performs some operation to reduce its privileges once it has completed initialization. There are two ways this usually happens, depending on whether security policies are in use or not.

Dropping privileges with security policies

When the system uses security policies, instead of having a user ID that is root, services can use a security type to give them the additional privileges they intially need. After they have completed startup, they should use secpol_transition_type() to switch to a lower-privilege security type (see secpol_transition_type() in the System Security Guide).

If the service does not call secpol_transition_type() but supports the -U option (see “Dropping privileges without security policies” for a description), you can use a hybrid approach: the service starts as root, with a security type, and with -U. Changing the user ID drops the privileges that are not needed after startup, with the security type controlling the specific privileges granted both before and after the change.

For more information on using security policies, including a tutorial for building a system that uses them, see the Security Policies chapter in the System Security Guide.

Dropping privileges without security policies

When the system does not use security policies, services and other system processes usually have to be started as root so that they can perform privileged tasks. To improve security, some of these services and processes implement a command-line option (usually -U) that specifies the user and group IDs to run as.

This option takes one of these forms:

For example, -U99:98 specifies that the process is to run as user ID 99 and group ID 98. An integration team can assign the appropriate permissions for each user and group.

After the process starts up and carries out any privileged functionality it requires, and possibly obtains capabilities to retain some privileged permissions, it's expected to switch to the user and group IDs specified by -U.

The liblogin library includes a standard helper function called set_ids_from_arg() that you can use to do this. To use set_ids_from_arg(), you have to include <login.h> and build with the -llogin linker option. The following example shows how to handle the -U argument simply:

case 'U':
    if( set_ids_from_arg( optarg ) != 0 ) {
        // insert appropriate logging and error handling
        log("Invalid user/group specified [%s]", optarg);
        return EXIT_FAILURE;
    }
    break;

Often you can't call set_ids_from_arg() immediately because the process needs enhanced privileges to start up. In addition, there are often a number of privileges the process needs on an ongoing basis even after it is switched to non-root. In most cases, it is necessary to make one or more procmgr_ability() calls to grant non-root certain privileged abilities and then call set_ids_from_arg() with the value passed as the -U option.