[Introduction]

Unix Incompatibility Notes:
UID Setting Functions

Jan Wolter

In all Unix systems, a UID, or "user ID number", is an integer (actually an unsigned short on older systems) that identifies particular user. Every running process has at least two UID numbers associated with it, the real UID number, which identifies the user who launched the process, and the effective UID number, which is used to determine what resources the process can access. Normally these are the same, but if a program with a set-uid bit set is run, then while the real UID remains that of the user who ran it, the effective UID is that of the user who owns the file.

POSIX and System V Unixes have a third UID number associated with every process, the saved UID number. This is a the effective UID number that the process had when it started. It is saved so that that UID can be taken back after it is given up. If the unistd.h header file defines _POSIX_SAVED_IDS, or if sysconf(_SC_SAVED_IDS) is true, then you have saved UIDs. However, not all Unixes have unistd.h or sysconf().

The effective UID is used when checking whether the process can open a file. If the process creates a file, the ownership is determined by the effective UID.

It is always possible for a process running as root to signal any process. For non-root processes the sending process's UIDs must match up with the receiving process's uid, but if the manual pages can be trusted, the exact rules vary. A signal can be set if:

Sending Process's... matches  Recieving Process's...     OS
effectivereal or saved RedHat Linux
real or effectivereal or effective FreeBSD, OpenBSD
real or effectivereal or saved SunOS 4.1, Solaris, SuSE Linux/i386 7.3
effectiveeffective old BSD, NextStep, Slackware Linux 3.1, ULTRIX 4.2

Some of this variation may be manual bugs. Some man pages aren't even internally consistant on this. It makes sense that recieving signals should depend on the real UID, so if a user runs an set-uid-root program, they can still send it signals, even though it is running as root. OpenBSD restricts the types of signals that can be sent to set-uid processes.

Linux also has a filesystem UID, which is normally the same as the effective user id. Whenever the effective UID is changed, the filesystem UID changes to the same value. The Linux man page says applications like NFS servers set the filesystem UID to a different value than the effective UID so they can read/write files as a user, without that user being able to send signals to the server process. This doesn't entirely make sense, because whether or not you can recieve signals on Linux depends on the real and saved UIDs, not the effective UID.

The functions for setting UID values vary widely between versions of Unix. In an attempt to clarify the situation, I'm going to try to fit the versions into an historical context. However, one should be cautioned that this historical context is mainly inferred from reading a lot of manual pages. I'm sure it is a huge oversimplification of the actual sequence of events, and may bare little resemblence to it.

No-Saved-UID Functions

In systems without saved-UIDs, the functions to set UIDs were pretty simple. Originally, there was just the setuid() function, but somewhere around 2.10 BSD three more were added:

setuid(uid) Set both real and effective UID to uid
setruid(uid) Set real UID to uid
seteuid(uid) Set effective UID to uid
setreuid(ruid,euid) Set real UID to ruid and effective UID to euid

These calls succeed only if either (1) the caller's effective userid is root, or (2) the uid's requested match either the caller's current real or effective UID numbers.

Of course, you can also change your effective UID by calling execve() on a file with a set-uid bit set.

Systems without saved UIDs are pretty much extinct. (NextStep was the last one I saw).

Early Saved-UID Functions

In 4.4BSD the saved-UID appeared and these four functions were modified. Setuid() changes all three UIDs, but works for non-root users only if the new UID matches the real UID. Seteuid() works if new UID matches the real or saved UID. The other two functions, setruid() and setreuid() were depreciated.

The idea was presumably that non-privileged processes should never be changing their real UID. Using the setuid() call, they could permanently surrender their set-uid priveleges. Using the seteuid() they could temporarily surrender them, or take them back. Oddly, there is no obvious way for a root process to set it's real and effective UIDs to different values (except execve()). This may be why attempts to depreciate setreuid() never quite stuck. While setruid() disappeared, setreuid() kept hanging around, often with stern instructions not to use it because it would be eliminated any decade now. In 2001 POSIX brought it back.

POSIX-ish Functions

The 1990 POSIX standards continued this direction of evolution, but some new complications were added, mainly in that the functions act quite differently for root than they do for other users.

The 2001 version of the POSIX made further changes, including restoration of the prodigal setreuid() to respectibility.

I need to put some more work into this to figure out exactly what the differences between the 1990 and 2001 POSIX versions are.

setuid(uid)
If your effective UID is root, the POSIX version still works as described above, setting all three UIDs to the given values, thus transforming you permanently into the give user.

If you are not root however, it sets only the effective UID, not the real or saved UID. You can set it to your effective, real or saved UID. In other words, it behaves identically to seteuid().

The FreeBSD manual seems to say that root can only use this to set to the real UID, not the saved UID. But that's too dumb to be true, isn't it?

setruid(uid)
POSIX did not include setruid() and it has vanished from most modern Unixes. I don't know if it was added to the standard when setreuid() was. AIX has it, but it always fails, because you aren't supposed to change just your real UID.

Where it still exists, it is usually, but not always, extended so that you can also set your real UID to your saved UID.

seteuid(uid)
For non-root users, this is equivalent to setuid(). It sets the effective UID only, and can only set to the effective, saved or real UID.

If the caller is root, and the effective UID is set to something other than it's real UID or its saved UID, then it's saved UID is changed also. It's real UID is never changed.

Versions of Linux before 1.1.37 have saved uids, but you can't use seteuid() to set the effective uid to them.

setreuid(ruid,euid)
For a while this was officially depreciated, like setruid(), but it never quite went away and has now belatedly made it into the standards. There is a risk that it will be missing in some systems that conformed to earlier versions of POSIX. In systems with saved uids, it is generally extended so that non-root users can set either the real or effective UID to their saved UID as well as to their real or effective UID.

What exactly happens to the saved UID when this is used seems to vary a lot. I think the new standard is to set the saved UID to the new value of the effective UID if either the real uid is changed (to anything) or the effective UID is changed to something other than the real UID. In older versions the saved uid was only changed to the new effective UID if the effective UID is changed from root to something other than the real or saved UID.

Versions of Linux before 1.1.37 have saved uids, but you can't use setreuid() to set the effective uid to them.

In AIX, setreuid(a,b) (where a!=b) is equivalent to seteuid(b) and setreuid(b,b) is equivalent to setuid(b). In other words, you can't set the real UID without setting the effective UID.

Other Functions

Some other related functions:
getuid()
Return the processes real UID. Works the same on all Unixes.

geteuid()
Return the processes effective UID. Works the same on all Unixes.

setresuid(ruid,euid,suid)
If your real, effective or saved UID is root, you can set all three arbitarily.

Otherwise, you can set them to your real, effective, or saved UID in any permutation.

This was evidentally introduced to cut the Gordian Knot of setuid() functions. No standard supports this, but it's been increasingly popular. It appears have originated in HPUX and exists in Linux after 2.1.44 and in OpenBSD.

setfsuid(uid)
Sets the process's filesystem UID to a new value. If the caller is not root, then the filesystem UID can only be set to the real, effective, or saved UID.

Linux only.

Discussion

Let's consider a little example of a set-uid program. Suppose we want to let users to append messages to a log file, but not edit the file. Each message would have a header identifying the user. So we'd like to let users on the list run a program like applog myfile which would append the file myfile to the log, with a header.

The simplest way to do this is to make the applog program an set-uid-root program, and permit the log file so only root can write it. To get the user id to put in the log entry header, we'll call getuid() which returns our real UID. We never need to call any set*uid() functions. This works fine, but any security hole in this program becomes a root exploit, which is highly undesirable.

So a better solution is to create a new user specially for this application, maybe with user name logger. We permit the log file so only logger can write to it, and we make the program so it runs set-uid-logger. Now if a security hole appears in the program, then only the logger account is compromised, a much smaller problem.

However, a new problem appears. Suppose user ralph runs the program, submitting a file that is not read permitted to others. Since the process is running with the effective UID of logger, it will not be able to read ralph's file. Luckily all Unixes provide ways to toggle between your two identities - we can have the program first open the log file while it is logger then switch it's effective UID to ralph to open his file. In more complex applications, the program may have to regularly toggle two identities back and forth. This is quite common.

In systems without saved UIDs, you do this swapping with setreuid(). In systems with saved UIDs, you can do this swapping with seteuid(). You don't want to use setuid() in this case, because it wouldn't work right if the original user was root. If the original real UID was root, then the first setuid() changes all UIDs to logger making it impossible to switch back again.

Some other interesting cases come up if we start as root. It is often useful for root programs to become other users, to ensure that nothing is done that couldn't be done by that user. Normally seteuid() is used for this. If the root process user wants to become first user andy and then user beth, then you can't just do:

   seteuid(uid_of_andy);
   :
   seteuid(uid_of_beth);
The second call won't work, because although your real and saved UIDs are still root, your effective UID isn't. So you have to do:
   seteuid(uid_of_andy);
   :
   seteuid(0);
   seteuid(uid_of_beth);
That is, you need to become effectively root again before you can become a new user. The fact that priviledged status depends only on the effective UID and not the real or saved UID is kind of odd, given that you can easily set your effective UID to your real or saved UID.

If you are root and want to set your effective UID to andy and your real UID to beth, then you have a problem. Your choices are (1) call the (no longer) depreciated setreuid() functions, (2) call the non-portable setresuid() function, or (3) do a setuid(uid_of_beth) and then use execve to run a program which is set-uid to andy. I guess this is why they un-depreciated setreuid().


Jan Wolter (E-Mail)
Sun Sep 22 20:24:22 EDT 2002 - Original Release.
Tue Aug 10 23:03:01 EDT 2004 - setreuid() updates.