Thursday, July 16, 2009

Old school local root vulnerability in pulseaudio (CVE-2009-1894)

Today was chosen as disclosure day for CVE-2009-1894.

Tavis Ormandy and myself have recently used the fact that pulseaudio was set-uid root to bypass Linux' NULL pointer dereference prevention. This technique is relying on a limitation in the Linux kernel and not on a bug in pulseaudio. But we also found one unrelated bug in pulseaudio.

Since it's set-uid root, we thought we would give pulseaudio a quick look. In the very first lines of main(), you can find the following:

if (!getenv("LD_BIND_NOW")) {
char *rp;

pa_assert_se(rp = pa_readlink("/proc/self/exe"));
pa_assert_se(execv(rp, argv) == 0);

So, pulseaudio is re-executing itself through /proc/self/exe, so that the dynamic linker performs all relocation immediately at load-time.

There is an obvious race condition here. /proc/self/exe is a symbolic link to the actual pathname of the executed command: by creating a hard link to /usr/bin/pulseaudio, we control this pathname, and consequently the file under this pathname. Knowing this, the exploitation is trivial (Note that rename() is atomic, or alternatively note how __d_path() works with deleted entries).

It's also interesting to note that any operation performed on /proc/self/exe is guaranteed by the kernel to be performed on the same inode than the one that got executed (see proc_exe_link), something that two of my colleagues have recently pointed out to me. So if they had re-executed themselves by using /proc/self/exe directly, without going through readlink() first, they would not have been vulnerable. And actually they weren't before, if you read the Changelog, you'll find:

2007-10-29 15:33 lennart * : use real path of binary instead of /proc/self/exe to execute ourselves

Oops! (Thanks to my colleague Mike Mammarella for digging this)

Like the vulnerability in udevd, this is a very good example of a non memory corruption vulnerability which is trivial to exploit very reliably and in a cross-architecture way.

So, why does pulseaudio have the set-uid bit set, you may ask ? For real-time performances reasons it wants to keep CAP_SYS_NICE but will drop all other privileges.

This vulnerability could have been avoided if the principle of least privilege had been followed: Since privileges are not required to re-exec yourself, dropping privileges should have been the first thing pulseaudio did. Here it's only the second thing it does, and it was enough to make most Linux Desktops vulnerable.

If your distribution of choice did not patch this yet, or if you want to reduce your attack surface, you're advised to chmod u-s /usr/bin/pulseaudio. Also note that as with every setuid binary update, you should also check that your users didn't create "backup" vulnerable copies (hardlink), waiting to own your box with known vulnerabilities while you think you are safe from those.

PS: Here are two brain teasers for you:

1. Find a cool way to perform an action after execve() has succeeded in another process, but before main() executes. First, I've used a FD_CLOEXEC read descriptor in a pipe and a SIGPIPE handler, but while it gives good results in practice, there is not guarantee as to when the signal will get delivered. I've finally found (with a hint from Tavis) a 100% reliable way to do it that is always guaranteed to work at first try. Of course, such a level of sophistication is absolutely not needed for this exploit.

2. Since pulseaudio allows you to load arbitrary libraries, it allows you to run arbitrary code with CAP_SYS_NICE as a feature. In the light of NUMA coming to the desktop through QPI, can you do something more interesting than what you would first expect with this?