The POSIX model, which Linux implements, is more complicated. Truth be told, you can't blame this on POSIX alone. The concept we'll discuss here was first introduced in System V and the POSIX standard merely adopted it.
POSIX adds a third uid variabe, the saved uid. This variable serves as a sort of post-it note for the kernel where it records the previous value of the effective uid when the process changes it.
This can be used in the following way: Consider a set-user uucp program, invoked by user joe. First, the process sets both the real and the effective uid to the user uid. The previous effective uid (i.e. the privileged uid, uucp) is stored in the saved uid. Subsequently, the process will behave just like it is just an ordinary process owned by user joe. However, the kernel will allow it to change the effective uid back to uucp by calling seteuid(priv_uid).
This is where the POSIX model differs greatly from the BSD model. On BSD, once you've changed both real and effective uid to the user uid, there's no way back because to the kernel, the process looks like any other process owned by joe. On a POSIX system, however, the content of the saved uid variable proves that the process previously ran with an effective uid of uucp, and thus the kernel allows it to assign that saved uid value to the effective or real uid variable again.
There's one exception from this rule, which is how the root uid 0 is treated. When a set-user root process calls setuid, the kernel does not copy the previous effective uid 0 to the saved uid variable. Instead, it sets all three uid variables to the uid value given as the setuid argument.
In other words, in a set-user root application, calling setuid(user_uid) will drop privilege permanently, while in all other set-user applications, you're able to regain the previous privilege later.
If you want to drop privege temporarily in a set-user root program, you can do that either by using BSD-style uid swapping with setreuid(priv_uid, user_uid). Alternatively, some systems offer setresuid that lets you set all three uid variables in one call, so you can call setresuid(user_uid, user_uid, 0).3.5
What remains is how can you permanently drop privilege in a program that's set-user to a uid other than root? The usual solution is to call setreuid(user_uid, user_uid). This will set the all three uids to the user uid.
This entire concept is quite ugly in my opinion. It's awfully convoluted, and the semantics of apparently straightforward functions such as setreuid become mind-boggling. If you want a good headache, try the Linux manpages for the set*uid functions and try to figure out all the fine print.
Saved IDs aren't just terribly complicated, it's also too bad the functions names used by both models are exactly the same. If you're not 100 percent sure what semantics a given Unix OS supports, there's nothing you can do short of writing a test program.
In addition, POSIX saved uid semantics are a dangerous trap when porting
setuid applications from BSD to the POSIX world. I've repeatedly
seen BSD programs that were installed say set-group tty do
a setuid(getuid()) in some place, and assume they could get
away with much sloppier coding after that because they had dropped all
privilege. Unfortunately, when you compile such a program on a POSIX
system without taking a closer look, you may have accidentally opened a
security hole: as we've seen above, the kernel will keep the tty
group ID in the saved gid. Assume an attacker finds a buffer overflow
in the application; as described in chapter
, he
can execute arbitrary machine code within the context of the attacked
process. So retrieving the saved gid is just a matter of including a
setgid(tty) call in the exploit.