Tuesday, January 29, 2008
The evilness of setuid(getuid())
We recently had a discussion after a code review that a setuid(getuid()) inside a suid without error checking
and program execution afterwards should be fixed. A lot of people think that this could
never fail. getuid() indeed can never fail, but setuid() can. Lets put aside theoretical issues such
as missing CAP_SETUID or signals and lets have a look how the kernel is executing a setuid()
in the first picture. CAP_SETUID should be ok since we talk about a setuid root program which is
executing setuid(getuid()). Obviously we can trigger an error return of EAGAIN if set_user() fails
which is only called if the real UID is changed during the call. That may only happen if some of the set*uid() functions with a different UID than at startup time of the program has been called already.
For instance a setuid root program runs at startup with the real UID of the user and calls setuid(0)
in order to to obtain full privileges. It then calls setuid(getuid()) to drop the privileges again.
How can this fail? Lets have a look at set_user() in the second picture. Obviously if the
RLIMIT_NPROC limit is exceeded and its not setuid'ing to root (which is the case) then
an error is returned. Huh! Lowering limits is always allowed ;-)
The sample program in picture three demonstrates how a setuid root program dropping
its privileges in this way can be tricked into executing other programs as root.
I apologize if you already knew this trick. I also apologize for the madness of this' blog
editing program which always places the pictures as it wants to and which makes me nuts.