By Jake Edge
December 2, 2009
Just days after FreeBSD 8.0 was released,
the FreeBSD developers were undoubtedly unhappy to see a "zero day" exploit posted on the Bugtraq
mailing list. The exploit is for a local privilege escalation
vulnerability in the runtime loader (rtld) that allows unprivileged users
to become root. The vulnerability and patch highlight
the need for code—particularly security enforcing code—to check
the return values of functions that get called.
The exploit essentially creates a broken environment, such that
unsetenv() cannot delete variables from that environment. Because
unsetenv() is unable to remove variables like LD_PRELOAD
from the environment, rtld fails to do so when running a
setuid(0) binary such as ping. But, as the patch shows,
rtld could have recognized the situation by checking the return value from
unsetenv(). By not doing so, a security feature can easily be
circumvented.
LD_PRELOAD allows users to specify libraries they want
loaded before the executable. This is typically used to load previous
versions, debugging aids (like malloc()/free() tracking),
and things of that sort.
Clearly setuid() binaries should not be linked to arbitrary,
user-controlled
libraries at runtime. In the case of the exploit, the shared library used
simply spawns a shell from the _init() call. That shell has the
effective user id of root because the loader kernel has already called
setuid() for the ping binary.
It is common for programmers to ignore return values for functions that
"can't fail", but that is a dangerous practice. It is worse when it
happens in code that runs with privileges. Something similar occurred with
the (badly named) "sendmail
capabilities bug", which was really a problem with the Linux kernel
capabilities implementation. But, had sendmail been more defensive and
checked the return code from setuid() when it was dropping
privileges—something that "can't fail"—a much bigger problem
would have been averted.
If the person writing the system or library call believed that the call
can't fail, they would presumably have made it a void function.
That's not to say that those programmers—or committees like
POSIX—are immune from bugs or bad
decisions, but callers should heed their intent. It's a difficult problem,
though, as it is sometimes unclear what the program should do if something
that can't fail does fail. Worse yet, without some kind of
comprehensive fault-injection framework, those error paths are difficult to
test. But, at least for privileged code, the problem can't be ignored.
This particular problem has existed in FreeBSD since version 7.0, released
in February 2008. A pre-advisory with the
patch was released by FreeBSD within a few hours of the Bugtraq posting.
A full advisory and update is expected soon. In the meantime, this should
serve as something of an object lesson for others; hopefully that will lead
to developers scrutinizing existing code for similar issues, while also
helping to remind programmers not to make that kind of mistake in any
future code they write.
(
Log in to post comments)