Changing a Program’s Identity

If you've worked with Linux for some time, you've probably used a set-user ID (or setuid) program to temporarily gain permissions different from your normal access rights. Unlike typical programs that run with your permissions, a setuid program runs with the permissions of that program's owner. For example, if you launch a program that's setuid and owned by root, that program runs as though root had executed it, temporarily granting you the same (full) access privileges as the superuser.

If you’ve worked with Linux for some time, you’ve probably used a set-user ID (or setuid) program to temporarily gain permissions different from your normal access rights. Unlike typical programs that run with your permissions, a setuid program runs with the permissions of that program’s owner. For example, if you launch a program that’s setuid and owned by root, that program runs as though root had executed it, temporarily granting you the same (full) access privileges as the superuser.

sudo is one such setuid program, and many other useful examples can be found on a common Linux system. Every use of the passwd command invokes some setuid magic, because it makes changes to the /etc/shadow file, a file that’s otherwise off-limits to non-root users. Likewise, if the user flag is set in /etc/fstab, mount‘s setuid status allows non-root users to mount filesystems. Another novel use of setuid is Apache HTTPD’s suexec utility, which executes CGI programs as those files’ owners instead of the actual httpd process owner.

This month, let’s look at how to change user ids in a running application, understand why setuid root is special, and see some of the pitfalls that leave setuid programs vulnerable to exploits. Setuid programs can be very effective — no matter their reputation — if written correctly.

What’s In a … Number?

A login name (like strike or jpeek) is really just an alias for a numeric user identifier, or uid. Every user on a Linux (or Unix) system has a unique uid, and each user (as represented by a uid) also has a set of privileges or permissions that dictate what resources he or she can access and affect. Uids are central to Linux security, as every Linux process and file is associated with, or owned by a specific uid.

More precisely, Linux processes are associated with a real uid and a saved set-user uid (sometimes called the saved uid, for short). Saved uid is so named because the kernel “saves” its value for a reason that’ll be apparent momentarily. The real uid represents the user that ran the command. For non-setuid binaries. the saved uid is the same as the real uid. However, for setuid binaries, the saved uid is the owner of the executable file.

While the real uid and saved uid of setuid processes differ, setuid processes don’t run with the combined privileges of the real and saved uids. A process can only be owned by one uid at a time, known as the effective uid. (Mnemonic: the program is effectively running as that user.)

By default, a setuid program’s effective uid is its saved uid, that is, the uid of the file’s owner. But — and this is what’s make setuid programs special — a setuid program may change its effective uid at will. A setuid process could open files as one user, then send mail as another.

Of course, setting the effective uid at will is quite dangerous, so the kernel enforces a simple rule: a process may change its effective uid to either its real or saved uid, but nothing else. This is why the kernel saves the value of the setuid program’s owner, and hence the name saved set-user ID.

The Ego of IDs

Detecting a uid is straightforward: the (portable) system calls getuid() and geteuid() return a process’s real and effective uid, respectively. The Linux-specific system call getresuid() fetches the real, effective, and saved uids simultaneously.

Several functions exist to change a program’s uid and which one you choose to use depends on your specific needs.

* setuid(uid) (and synonym seteuid(uid)) changes the process’s effective uid to uid.

* setreuid(ruid, euid) changes the real and effective uids at the same time. Pass a 1 for either parameter to leave that uid unchanged. For example, setreuid ((uid_t) 1 , 400) leaves the real uid unchanged and sets the effective uid to 400. (The uid_t type is an unsigned number, so the 1 is explicitly cast to avoid a compiler error.)

Lastly, the function setresuid(ruid, euid, suid) lets you change the real, effective, and saved uids in one fell swoop. (As with setreuid(), pass a 1 to leave a value unchanged.) As a process may change its effective uid only to one of the real or saved set-user IDs, changing the saved set-user ID with setresuid() prevents a program from regaining old privileges.

While all three functions are similar, setresuid() is not as common as seteuid() or setuid(), so avoid this function if you strive for portability. And in all cases, only the real or saved set-user IDs are valid arguments. You’ll see why shortly. Listing One, sample_1.cc, demonstrates all of these functions.

Listing One: Demonstration of setting real, effective, and saved uids

extern “C” {
#include<pwd.h> // getpwuid()


void whois(const char* prefix , struct passwd* pwd) {
std::cout << ‘\t’ << prefix << pwd->pw_name << “(”
<< pwd->pw_uid << “)” << std::endl;

void printInfo() {
whois(“real: ” , getpwuid(getuid()));
whois(“effective: ” , getpwuid(geteuid()));
std::cout << ‘\n’;

int main(int argc , char** argv) {
// we don’t have to save this; getuid() will return
it for us.
// but it’s nice to have in a variable that we can
use similar
// to savedUid
const uid_t realUid = getuid();
const uid_t savedUid = geteuid();
std::cout << “[start of program]” << std::endl;

NOTE: if the effective uid is root, this call to
setuid() here will change the effective uid for
the rest of the program. This is why one must
use setreuid() if you want a setuid root
program to flip back and forth between root and
the real UID.
std::cout << “[call to setuid(" << realUid << ")]”
<< std::endl;

if (0 == savedUid) {
std::cout << “(saved UID is root: there’s no
going back from here!)”
<< std::endl;


std::cout << “[reset to original state using
setreuid(" << realUid
<< " , " << savedUid << ")]” << std::endl;
setreuid(realUid , savedUid);

std::cout << “[call to seteuid(" << realUid << ")]”
<< std::endl;

std::cout << “[reset to original state using
setreuid(" << realUid
<< " , " << savedUid << ")]” << std::endl;
setreuid(realUid , savedUid);

std::cout << “[swapping real/effective uid's using
setreuid()]” << std::endl;
setreuid(savedUid , realUid);

std::cout << “[reset to original state using
setreuid(" << realUid
<< " , " << savedUid << ")]” << std::endl;
setreuid(realUid , savedUid);

std::cout << “[changing all id's using setresuid("
<< realUid << " , "
<< realUid << " , " << realUid << ")]” <<
setresuid(realUid , realUid , realUid);

std::cout << “[reset to original state using
setreuid(" << realUid
<< " , " << savedUid << ")]” << std::endl;
std::cout << ‘\n’ << “(this will fail: we just
overwrote the” << ‘\n’
<< ” saved set-user ID with setresuid())” <<
setreuid(realUid , savedUid);


In the code, the real and effective uids remain the same if the program does not have the setuid bit enabled. Use chmod u+s filename to enable the setuid bit on the executable, then run the program as another user to see the real effect.

The program should not be owned by root. Setuid root programs are special — and you’ll see why below.

How Root Breaks the Rules

It wouldn’t be *nix if superuser didn’t break all the rules. When root is the effective uid — that is, either root runs a program or a program is setuid root — things get a little more complicated and it becomes clear why some of the functions mentioned in the previous section exist. Here’s what’s different about root:

A root or setuid root process may change uid to any valid user on the system. It’s not limited to the real and saved uids, as with nonroot users.

Root calls to setuid() are one-way trips: all of the real, saved, and effective uids are set to the specified value. If this value is not 0 (root), the process loses its root privileges. (You can explicitly do this by calling setresuid() with all non-root uids.)

To demonstrate, change the owner of the sample_1 executable to root (such that it is setuid root) and run it as a non-root user.

Notice that the program cannot change back to root after setuid(), in spite of the series of calls to setreuid() to return to the initial program state.

Sometimes you want to return to being root, say, to cleanup some root-created files. setuid() is an unwelcome surprise here, because your cleanup routine fails, and unless you explicitly check the return value from setuid(), you’ll have no indication why.

Now it should make more sense why seteuid() and setreuid() exist: they don’t touch the saved set-user ID. Setuid root programs can call these functions and still return the effective uid to root when needed. (The sample_2.cc file, which you can download, amends sample_1.cc to not call setuid(). To test it, make the program setuid root and run it as a non-root user.)


What’s been explained so far for user ids holds true for group ids, or gids, as well. A process has a real, effective, and saved group ID (gid). The functions to query and change gids are named similarly to their uid-related counterparts: simply change uid to gid in the name. (The sample code in sample_3.cc does just this.)

There’s one caveat: functions setgid(), setegid(), setregid(), and setresgid() affect only the user’s primary gid. Even if a user belongs to many groups, supplemental groups are unaffected by the functions. Supplemental groups can only be changed by calling setgroups(), which, in turn, may only be called when root is the effective uid.

Taking Care

No discussion of setuid binaries would be complete without a discussion of their limitations.

For one, the kernel ignores the special setuid bit on executables in filesystems mounted with the nosuid option. Also, setuid scripts have been such a security problem in the past that many operating systems (some versions of Linux included) disable the functionality at the kernel level.

Third, dynamic linking is limited in setuid programs.

The runtime linker ld.so searches the paths advertised in /etc/ld.so.conf to satisfy a program’s runtime library dependencies. Users may supplement the search path by setting the environment variable LD_LIBRARY_PATH.

Setuid programs, however, ignore the user-supplied path for security reasons. If a setuid executable relies on a shared library in a nonstandard path, the program won’t run.

Setuid programs are so subtle that you can’t tell when you’re using one. The ability to change a process’s runtime user identity through setuid programs is a powerful tool, and as such, using it requires special care.

RAII is one of many techniques available to prevent “leaks” of privilege in your code.

For more information on RAII, see the sidebar “Least Privilege, Best Policy.”

Least Privilege, Best Policy

The ability to switch effective uid midstream can be helpful. Instead of a program doing everything as the saved uid, it can switch the effective uid to create a special file as the program starts and then remove that file as the program exits. Everything else the program does need not happen with special privileges.

Taken a step further, the practice yields the notion of least privilege, which states that an action is performed with the absolute minimum authority required to do so. Least privilege provides appreciable damage control: programs written according to this idiom may only be exploited to the extent of the power they are using at that particular moment — that is, the privileges of the effective user — regardless of the program’s overall power.

For example, only root may bind to a low-numbered port (under 1024). You could write such a program and run it as root — but then an exploit in that program would yield root privileges to an attacker. Per least privilege, the program should instead use its root powers only to bind to the port, then change to an unprivileged user to do real work. An exploit here would still be a problem, but not to the degree of a root compromise. (While making a program setuid root may not be the safest or cleanest way to bind to a privileged port, it does demonstrate that having an occasional piece of another uid can be helpful.)

Listings Two through Five demonstrate how to apply the idiom of least privilege to changing user identity at runtime. A reliable mechanism to perform setup and cleanup is key, and there’s nothing more reliable than using an object’s constructor and destructor functions, which are called when the object’s created and when the object goes out of scope, respectively.

A technique called resource acquisition is initialization (RAII) uses these functions to keep code clean. A sensitive resource (such as a mutex) is acquired in the constructor and released in the destructor. Defining such an object at the top of a scope means the resource is held until the end of the scope and automatically cleaned up. This procedure guarantees cleanup, thereby freeing you from additional checks.

Listing Two describes a User object, which encapsulates a user’s uid, primary gid, and supplementary groups. Listing Three details a UserMarker object, which performs the ID swaps to and from the privileged user. User and UserMaker are used in Listing Four, which is an example of how these objects implement RIIA for least privilege.

In Listing Four, Line 11 defines a User object named privileged to represent the special setuid user. (We assume this code is called early in the program, before any other ID-changing routines.) The open curly brace at line 19 defines a new scope. This could be the start of a function or an arbitrary block of code. The privileged object is passed to a UserMarker constructor to create the object marker.

marker‘s constructor saves the current user state as _oldUser and receives the new user state _newUser as a constructor argument. It then calls _newUser.become() to change to the new user. By line 21, the process is running as the privileged user.

At line 23, the work from this block of code is complete, so the scope ends at the curly brace. At that point marker‘s destructor is invoked, which calls _oldUser.become() to revert back to the old, unprivileged user. It’s important to point out that marker is not created as a pointer, because that would defeat the purpose: pointers are not automatically destroyed at the end of the scope in which they are initialized.

The RAII technique, as used in the sample code, requires special care when creating new processes (via fork()), replacing the current process (via exec()), and spawning new threads. A fork()‘d process is a clone, so it makes sense that it retains the same real, saved, and effective uids as its parent. Calls to exec() maintain only the real and effective uids. The saved set-user ID is set to the effective uid. If the real and effective uids are different when exec() is called, a process will retain its ability to change the effective uid.

Finally, thread behavior differs from one thread library to another. In most implementations, all threads within the same process share the user identity (uids and gids): if one thread calls, say, seteuid(), that change is immediately reflected across all threads.

Linux is unique in that each thread maintains its own user identity. This means you get the best (or worst) of both worlds: the ease of resource sharing of threads, and the uid/gid autonomy of separate processes.

Listing Two: The User class

class User {
uid_t _uid;
gid_t _gid;

std::vector< uid_t > _groups;
void setup(uid_t uid , gid_t gid , int
numGroups , const gid_t* groups);

User() {
int totalExtraGroups = getgroups(0 , NULL);
gid_t extraGroups[ totalExtraGroups ];

getgroups(totalExtraGroups , extraGroups);
setup(getuid() , getgid() , totalExtraGroups

User(uid_t uid , gid_t gid , int numGroups ,
uid_t* groups) {
setup(uid , gid , numGroups , groups);

void become() const throw(std::runtime_error) {
if (0 == getegid() && 0 != _groups.size()) {
gid_t groupTmp[ _groups.size() ];
std::copy(_groups.begin() , _groups.end()
, &groupTmp[0]);
if (0 != setgroups(_groups.size() ,
groupTmp)) {
throw(std::runtime_error(“Unable to
change supplementary groups”));

if (0 != seteuid(_uid)) {
throw(std::runtime_error(“Unable to change

if (0 != setegid(_gid)) {
throw(std::runtime_error(“Unable to change
primary gid”));


void print(std::ostream& s) const throw() {
s << “[User: u=" << _uid
<< "/g=" << _gid << "+"
<< _groups.size() << "]”
<< std::flush;

Listing Three: The UserMarker class

class UserMarker {
UserMarker(const UserMarker&);
UserMarker& operator=(const UserMarker&);
User _oldUser;
User _newUser;
UserMarker(const User& toBecome)
throw(std::runtime_error) :
_oldUser(User()) , _newUser(toBecome)
std::cout << “switching from ” << _oldUser
<< ” -> ”
<< _newUser << std::endl;

try {
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;


~UserMarker() throw() {
std::cout << “switching back: ” << _newUser
<< ” -> ”
<< _oldUser << std::endl;

try {
} catch(std::exception& e) {
std::cerr << e.what() << std::endl;


Listing Four: A sample program using RAII for least privilege

1 void status() {
2 std::cout << ‘\t’ << “euid is ” << geteuid() << std::endl;
3 std::cout << ‘\t’ << “egid is ” << getegid() << std::endl;
4 std::cout << ‘\t’ << “total extra groups: ” << getgroups(0 ,
5 << std::endl;
6 return;
7 }
9 int main(int argc , char** argv) {
10 User normal;
11 User privileged(geteuid() , getegid() , 0 , NULL);
13 std::cout << “forcing effective ID to normal, unprivileged
user \”"
14 << normal << “\”" << std::endl;
15 normal.become();
16 status();
18 std::cout << “About to perform a privileged action” <<
19 {
20 UserMarker marker(privileged);
21 status();
22 // do something that requires special setuid privileges
23 }
25 std::cout << “Now back to normal.” << std::endl;
26 status();
27 }

Ethan McCallum is a freelance technology consultant. He can be reached at ethanqm@penguinmail.com. The sample code for this month’s column is available at http://www.linux-mag.com/downloads/2004-04/compile.

Comments are closed.