In shortConsider the pulseaudio case, which can be reduced to:
- file is setuid
- readlink("/proc/self/exe", buf, sizeof(buf))
- later, execve(buf, .., ..)
This race condition can be exploited if you are able to change the value pointed by the symlink /proc/self/exeright after the program execution and before readlink(). You can do that by creating a hardlink to the program in a directory you own, then delete it and place your program within the race window. Indeed, if you use a symlink, execve() will resolve it before execution and /proc/self/exe would point to the actual program (that you cannot/do not want to delete).
Proof of conceptConsider the following vulnerable program:
Exploit 1: classic race exploitationFor those where sh is linked to bash, we will need a small wrapper to give us euid (geteuid) as uid (setuid). Also, this wrapper will help in the classical exploitation of the race condition by exiting if it has not reached the target uid. Here is the wrapper:
To exploit this race reliably, we need to find a way to stop the execution of the process before its main(). That's the challenge Julien Tinnes gave to his readers, and I will repeat the two known solutions here.
Exploit 2: fill blocking pipesI will just quote the answer comment by Julien Tinnes:
"So yeah, this is it, the LD_DEBUG trick still works. You set LD_DEBUG to a bogus value and then you exhaust a pipe like usual and dup2 it to the child's stderr. It solves the part where the parent has to wait for the child to be ready.
And to guarantee that the parent will not perform a certain action before the child is inside execve(), I used vfork().
So the LD_DEBUG trick + vfork() was our "old school" solution and Dividead was the first to find and report to us this solution (at least the first part)."
Indeed there is a very good work by Dividead on his blog, with exploit code.
Exploit 3: use /proc file descriptorsCreate the hardlink, then open a file descriptor in the current shell to it:
Now we delete our hardlink to the setuid program:
Then just place the program you want at this destination. Here I will just use a setuid(geteuid)+execve(/bin/sh) wrapper.
The final step is to execute the program. We do that by using shell built-in exec on the file descriptor and it has the effect of calling execve() on this file descriptor. But remember, this file descriptor has root owner and setuid bit, so it executes the vulnerable program (still on disk because it was a hardlink) with these properties. The vulnerable program then executes itself via /proc/self/exe which now points to our program, and we eventually get the euid root:
Update: on newer kernels, this exploitation technique is no longer usable because file is renamed as (deleted) /path/to/file.
ConclusionI very much liked the last technique using /proc and file descriptor. I hope I did not say anything wrong, if so feel free to correct me.
About the mitigations, in addition to protecting your /tmp with nosuid or noexec mount options, you can consider having your setuid binaries on another filesystem, thus disallowing hardlinking - because hardlinks can only be on the same filesystem by definition.