< Previous PageNext Page > Hide TOC

Elevating Privileges Safely

Running code with root or administrative privileges can intensify the dangers posed by security vulnerabilities. This article explains why that is so, suggests techniques you can use to avoid elevating privileges, and describes the safest techniques for elevating privileges when it can't be avoided.

Contents:

Circumstances Requiring Elevated Privileges
The Hostile Environment and the Principle of Least Privilege
Avoiding Elevated Privileges
Running With Elevated Privileges
Calls to Change Privilege Level
Avoiding Forking Off a Privileged Process
Factoring Applications
Authorization and Trust Policies
Security in a KEXT


Circumstances Requiring Elevated Privileges

Regardless of whether a user is logged in as an administrator, a program might have to obtain administrative or root privileges in order to accomplish a task. Examples of tasks that require elevated privileges include:

If you have to perform a task that requires elevated privileges, you must be aware of the fact that running with elevated privileges means that if there are any security vulnerabilities in your program, an attacker can obtain elevated privileges as well, and would then be able to perform any of the operations listed above.

The Hostile Environment and the Principle of Least Privilege

As discussed in “The Security Landscape,” any program can come under attack, and probably will. By default, every process runs with the privileges of the user or process that started it. Therefore, if a user has logged on with restricted privileges, your program should run with those restricted privileges. This effectively limits the amount of damage an attacker can do, even if he successfully hijacks your program into running malicious code. Do not assume that the user is logged in with administrator privileges; you should be prepared to run a helper application with elevated privileges if you need them to accomplish a task. However, keep in mind that, if you elevate your process' privileges to run as root, an attacker can gain those elevated privileges and potentially take over control of the whole system.

Note: Although in certain circumstances it’s possible to mount a remote attack over a network, for the most part the vulnerabilities discussed here involve malicious code running locally on the target computer.

If an attacker uses a buffer overflow or other security vulnerability (see “Types of Security Vulnerabilities”) to execute code on someone else's computer, they can generally run their code with whatever privileges the logged-in user has. If an attacker can gain administrator privileges, they can elevate to root privileges and gain access to any data on the user's computer. Therefore, it is good security practice to log in as an administrator only when performing the rare tasks that require admin privileges. Because the default setting for Mac OS X is to make the computer's owner an administrator, you should encourage your users to create a separate non-admin login and to use that for their everyday work. In addition, if possible, you should not require admin privileges to install your software.

The idea of limiting risk by limiting access goes back to the "need to know" policy followed by government security agencies (no matter what your security clearance, you are not given access to information unless you have a specific need to know that information). In software security, this policy is often termed "the principle of least privilege," first formally stated in 1975: “Every program and every user of the system should operate using the least set of privileges necessary to complete the job.” (Saltzer, J.H. AND Schroeder, M.D., "The Protection of Information in Computer Systems," Proceedings of the IEEE, vol. 63, no. 9, Sept 1975.)

In practical terms, the principle of least privilege means you should avoid running as root, or—if you absolutely must run as root to perform some task—you should run a separate helper application to perform the privileged task (see “Factoring Applications”). By running with the least privilege possible, you:

Keep in mind that, even if your code is free of errors, vulnerabilities in any libraries your code links in can be used to attack your program. For example, no program with a graphical user interface should run with privileges because the large number of libraries used in any GUI application makes it virtually impossible to guarantee that the application has no security vulnerabilities.

There are a number of ways an attacker can take advantage of your program if you run as root. Some possible approaches are described in the following sections.

Launching a New Process

Because any new process runs with the privileges of the process that launched it, if an attacker can trick your process into launching his code, the malicious code runs with the privileges of your process. Therefore, if your process is running with root privileges and is vulnerable to attack, the attacker can gain control of the system. There are many ways an attacker can trick your code into launching malicious code, including buffer overflows, race conditions, and social engineering attacks (see “Types of Security Vulnerabilities”).

Executing Command-Line Arguments

Because all command-line arguments, including the program name (argv(0)), are under the control of the user, you should not use the command line to execute any program without validating every parameter, including the name. If you use the command line to re-execute your own code or execute a helper program, for example, a malicious user might have substituted his own code with that program name, which you are now executing with your privileges.

Inheriting File Descriptors

When you create a new process, the child process inherits its own copy of the parent process' file descriptors (see the manual page for fork(2)). Therefore, if you have a handle on a file, network socket, shared memory, or other resource that's pointed to by a file descriptor and you fork off a child process, you must be careful to either close the file descriptor or you must make sure that the child process cannot be tampered with. Otherwise, a malicious user can use the subprocess to tamper with the resources referenced by the file descriptors.

For example, if you open a password file and don’t close it before forking a process, the new subprocess has access to the password file.

To set a file descriptor so that it closes automatically when you execute a new process (such as by using the execve system call), use the fcntl(2) command to set the close-on-exec flag. You must set this flag individually for each file descriptor; there’s no way to set it for all.

Abusing Environmental Variables

Most libraries and utilities use environmental variables. Sometimes environmental variables can be attacked with buffer overflows or by inserting inappropriate values. If your program links in any libraries or calls any utilities, your program is vulnerable to attacks through any such problematic environmental variables. If your program is running as root, the attacker might be able to bring down or gain control of the whole system in this way. Examples of environmental variables in utilities and libraries that have been attacked in the past include:

  1. The dynamic loader: LD_LIBRARY_PATH, DYLD_LIBRARY_PATH are often misused, causing unwanted side effects.

  2. libc: MallocLogFile );

  3. Core Foundation: CF_CHARSET_PATH

  4. perl: PERLLIB,PERL5LIB, PERL5OPT

    [2CVE-2005-2748 (corrected in Apple Security Update 2005-008) 3CVE-2005-0716 (corrected in Apple Security Update 2005-003) 4CVE-2005-4158]

Environmental variables are also inherited by child processes. If you fork off a child process, your parent process should validate the values of all environmental variables before it uses them in case they were altered by the child process (whether inadvertently or through an attack by a malicious user).

Modifying Process Limits

You can use the setrlimit call to limit the consumption of system resources by a process. For example, you can set the largest size of file the process can create, the maximum amount of CPU time the process can consume, and the maximum amount of physical memory a process may use. These process limits are inherited by child processes.

In order to prevent an attacker from taking advantage of open file descriptors, programs that run with elevated privileges often close all open file descriptors when they start up. However, if an attacker can use setrlimit to alter the file descriptor limit, he can fool the program into leaving some of the files open. Those files are then vulnerable.

Similarly, a vulnerability was reported for a version of Linux that made it possible for an attacker, by decreasing the maximum file size, to limit the size of the /etc/passwd and /etc/shadow files. Then, the next time a utility accessed one of these files, it truncated the file, resulting in a loss of data and denial of service. [CVE-2002-0762]

File Operation Interference

If you’re running with elevated privileges in order to write or read files in a world-writable directory or a user’s directory, you must be aware of time-of-check–time-of-use problems; see “Time Of Check–Time Of Use.”

Avoiding Elevated Privileges

In many cases, you can accomplish your task without needing elevated privileges. For example, suppose you need to configure the environment (add a configuration file to the user's home directory or modify a configuration file in the user's home directory) for your application. You can do this from an installer running as root (the installer command requires administrative privileges; see the manual page for installer(8)). However, if you have the application configure itself, or check whether configuration is needed when it starts up, then you don't need to run as root at all.

An example of using an alternate design in order to avoid running with elevated privileges is given by the BSD ps command, which displays information about processes that have controlling terminals. Originally, BSD used the setgid bit to run the ps command with a group ID of kmem, which gave it privileges to read kernel memory. More recent implementations of the ps command use the sysctl utility to read the information it needs, removing the requirement that ps run with any special privileges.

Running With Elevated Privileges

If you do need to run code with elevated privileges, there are several approaches you can take:

Calls to Change Privilege Level

There are several commands you can use to change the privilege level of a program. The semantics of these commands are tricky, and vary depending on the operating system on which they're used.

Important: If you are running with both a group ID (GID) and user ID (UID) that are different from those of the user, you have to drop the GID before dropping the UID. Once you've changed the UID, you may no longer have sufficient privileges to change the GID.

Important: As with every security-related operation, you must check the return values of your calls to setuid, setgid, and related routines to make sure they succeeded. Otherwise you might still be running with elevated privileges when you think you have dropped privileges.

For more information on permissions, see the "Permissions" section in the Security Concepts chapter in Security Overview. For information on setuid and related commands, see Setuid Demystified by Chen, Wagner, and Dean (Proceedings of the 11th USENIX Security Symposium, 2002), available at http://www.usenix.org/publications/library/proceedings/sec02/full_papers/chen/chen.pdf and the manual pages for setuid(2), setreuid(2), setregid(2), and setgroups(2). (The setuid(2) manual page includes information about seteuid, setgid, and setegid as well.

Here are some notes on the most commonly used system calls for changing privilege level:

Avoiding Forking Off a Privileged Process

There are a couple of functions you might be able to use to avoid forking off a privileged helper application: The authopen function lets you obtain temporary rights to create, read, or update a file. You can call the launchd command to start a process with specified privileges and a known environment.

authopen

When you call the authopen function, you provide the pathname of the file that you want to access. There are options for reading the file, writing to the file, and creating a new file. Before carrying out any of these operations, the authopen function requests authorization from the system security daemon, which authenticates the user (through a password dialog or other means) and determines whether the user has sufficient rights to carry out the operation. See the manual page for authopen(1) for the syntax of this command.

launchd

Starting with Mac OS X v10.4, the launchd daemon is used to launch daemons and other programs automatically, without user intervention. (For systems running versions of the OS earlier than Mac OS X v10.4, you can use the standard BSD routine mach_init for this purpose.) The launchd daemon launches daemons on a per-user basis and can restart daemons after they quit if they are needed. You provide a configuration file that tells launchd the level of privilege with which to launch your routine. You can use launchd to launch a privileged helper daemon rather than factoring your application into privileged and unprivileged processes. Be sure that you do not request higher privilege than you actually need, and to drop privilege or quit execution as soon as possible.

There are several reasons to use launchd in preference to writing a factored application that forks off a privileged process:

For more information on launchd, see the manual pages for launchd(8), launchctl(1), and launchd.plist(5), and Getting Started with launchd in http://developer.apple.com/macosx/. For more information about mach_init, see The Boot Process in System Startup Programming Topics and Root and Login Sessions in Multiple User Environments.

Factoring Applications

If you've read this far and you're still convinced you need to factor your application into privileged and nonprivileged processes, this section provides some tips and sample code. In addition, see Authorization Services Programming Guide for more advice on the use of Authorization Services and the proper way to factor an application.

As explained in the Authorization Services documentation, it is very important that you check the user's rights to perform the privileged operation, both before and after launching your privileged helper tool. Your helper tool, owned by root and with the setuid bit set, has sufficient privileges to perform whatever task it has to do. However, if the user doesn't have the rights to perform this task, you shouldn't launch the tool and—if the tool gets launched anyway—the tool should quit without performing the task. Your nonprivileged process should first use Authorization Services to determine whether the user is authorized and to authenticate the user if necessary (this is called preauthorizing; see Listing 1). Then launch your privileged process. The privileged process then should authorize the user again, before performing the task that requires elevated privileges; see Listing 2. As soon as the task is complete, the privileged process should terminate.

In determining whether a user has sufficient privileges to perform a task, you should use rights that you have defined and put into the policy database yourself. If you use a right provided by the system or by some other developer, the user might be granted authorization for that right by some other process, thus gaining privileges to your application or access to data that you did not authorize or intend. For more information about policies and the policy database, (see the section "The Policy Database" in the Authorization Concepts chapter of Authorization Services Programming Guide).

In the code samples shown here, the task that requires privilege is killing a process that the user does not own.

Example: Preauthorizing

If user tries to kill a process that he doesn’t own, the application has to make sure user is authorized to do so. The following numbered items correspond to comments in the code sample:

  1. If the process is owned by the user, and the process is not the window server or the login window, go ahead and kill it.

  2. Call the permitWithRight method to determine whether the user has the right to kill the process. The application must have previously added this right—in this example, called com.apple.processkiller.kill—to the policy database. The permitWithRight method handles the interaction with the user (such as an authentication dialog). If this method returns 0, it completed without an error and the user is considered preauthorized.

  3. Obtain the authorization reference.

  4. Create an external form of the authorization reference.

  5. Create a data object containing the external authorization reference.

  6. Pass this serialized authorization reference to the setuid tool that will kill the process (Listing 2).

Listing 1  Nonprivileged process

if (ownerUID == _my_uid && ![[contextInfo processName]
        isEqualToString:@"WindowServer"] && ![[contextInfo processName]
        isEqualToString:@"loginwindow"]) {
[self killPid:pid withSignal:signal];                                      // 1
}
else
{
SFAuthorization *auth = [SFAuthorization authorization];
if (![auth permitWithRight:"com.apple.proccesskiller.kill" flags:
        kAuthorizationFlagDefaults|kAuthorizationFlagInteractionAllowed|
        kAuthorizationFlagExtendRights|kAuthorizationFlagPreAuthorize])    // 2
{
AuthorizationRef authRef = [auth authorizationRef];                        // 3
AuthorizationExternalForm authExtForm;
OSStatus status = AuthorizationMakeExternalForm(authRef, &authExtForm);    // 4
if (errAuthorizationSuccess == status) {
NSData *authData = [NSData dataWithBytes: authExtForm.bytes
                             length: kAuthorizationExternalFormLength];    // 5
[_agent killProcess:pid signal:signal authData: authData];                 // 6
}
}
}

The external tool is owned by root and has its setuid bit set so that it runs with root privileges. It imports the externalized authorization rights and checks the user's authorization rights again. If the user has the rights, the tool kills the process and quits. The following numbered items correspond to comments in the code sample:

  1. Convert the external authorization reference to an authorization reference.

  2. Create an authorization item array.

  3. Create an authorization rights set.

  4. Call the AuthorizationCopyRights function to determine whether the user has the right to kill the process. You pass this function the authorization reference. If the credentials issued by the Security Server when it authenticated the user have not yet expired, this function can determine whether the user is authorized to kill the process without reauthentication. If the credentials have expired, the Security Server handles the authentication (for example, by displaying a password dialog). (You specify the expiration period for the credentials when you add the authorization right to the policy database.)

  5. If the user is authorized to do so, kill the process.

  6. If the user is not authorized to kill the process, log the unsuccessful attempt.

  7. Release the authorization reference.

Listing 2  Privileged process

AuthorizationRef authRef = NULL;
OSStatus status = AuthorizationCreateFromExternalForm(
  (AuthorizationExternalForm *)[authData bytes], &authRef);              // 1
if ((errAuthorizationSuccess == status) && (NULL != authRef)) {
AuthorizationItem right = {"com.apple.proccesskiller.kill",
                                                0L, NULL, 0L};           // 2
AuthorizationItemSet rights = {1, &right};                               // 3
status = AuthorizationCopyRights(authRef, &rights, NULL,
        kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed |
        kAuthorizationFlagExtendRights,    NULL);                        // 4
if (errAuthorizationSuccess == status)
kill(pid, signal);                                                       // 5
else
NSLog(@"Unauthorized attempt to signal process %d with %d",
            pid, signal);                                                // 6
AuthorizationFree(authRef, kAuthorizationFlagDefaults);                  // 7
}

Helper Tool Cautions

If you write a privileged helper tool, you need to be very careful to examine your assumptions. For example, you should always check the results of function calls; it is dangerous to assume they succeeded and to proceed on that assumption. You must be careful to avoid any of the pitfalls discussed in this document, such as buffer overflows and race conditions.

If possible, avoid linking in any extra libraries. If you do have to link in a library, you must not only be sure that the library has no security vulnerabilities, but also that it doesn't link in any other libraries. Any dependencies on other code potentially open your code to attack.

In order to make your helper tool as secure as possible, you should make it as short as possible—have it do only the very minimum necessary and then quit. Keeping it short makes it less likely that you made mistakes, and makes it easier for others to audit your code. Be sure to get a security review from someone who did not help write the tool originally. An independent reviewer is less likely to share your assumptions and more likely to spot vulnerabilities that you missed.

Authorization and Trust Policies

In addition to the basic permissions provided by BSD, the Mac OS X Authorization Services API enables you to use the policy database to determine whether an entity should have access to specific features or data within your application. Authorization Services includes functions to read, add, edit, and delete policy database items.

You should define your own trust policies and put them in the policy database. If you use a policy provided by the system or by some other developer, the user might be granted authorization for a right by some other process, thus gaining privileges to your application or access to data that you did not authorize or intend. Define a different policy for each operation to avoid having to give broad permissions to users who need only narrow privileges. For more information about policies and the policy database, (see the section "The Policy Database" in the Authorization Concepts chapter of Authorization Services Programming Guide).

Authorization Services does not enforce access controls; rather, it authenticates users and lets you know whether they have permission to carry out the action they wish to perform. It is up to your program to either deny the action or carry it out.

Security in a KEXT

Because kernel extensions have no user interface, you cannot call Authorization Services to obtain permissions that you do not already have. However, you can determine what permissions you have and evaluate access control lists (ACLs; see the section "ACLs" in the Security Concepts section of Security Overview). Starting in Mac OS X v10.4, you can use the Kernel Authorization (Kauth) subsystem to manage authorization. For more information on Kauth, see Technical Note TN2127, Kernel Authorization (http://developer.apple.com/technotes/tn2005/tn2127.html).



< Previous PageNext Page > Hide TOC


© 2008 Apple Inc. All Rights Reserved. (Last updated: 2008-05-23)


Did this document help you?
Yes: Tell us what works for you.
It’s good, but: Report typos, inaccuracies, and so forth.
It wasn’t helpful: Tell us what would have helped.