By Jake Edge
October 27, 2010
Tavis Ormandy has been busy of late, poking around in the guts of GNU
libc. Out of that have come two separate local privilege escalations that
exploit an obscure corner (the dynamic linker auditing API) of glibc, while
the exploits themselves use—abuse—some Linux features that many
probably aren't aware of. These vulnerabilities and exploits provide good
examples of the way that security researchers look at code and
systems—a way of looking that more developers would do well to emulate.
The runtime library auditing API is a way for developers to intercept the
actions of the dynamic linker to see the steps that it is taking while
searching for .so files and resolving symbols from them. When a
program is executed with the LD_AUDIT environment variable
pointing to one or more shared libraries, the linker will make callbacks
into functions in those libraries for various events that happen in the
linking process. There are various events specified in the rtld-audit
man page, including searching for an object, opening an object, binding to
a symbol, and so on. It seems like a useful facility, but one that is
likely not in the toolbox of many Linux developers.
The simpler of the two problems that Ormandy found was that setuid
programs will open whatever arbitrary library a user specifies in
LD_AUDIT, as long as that library lives on the trusted library path. The more well-known LD_PRELOAD environment
variable, which preloads the specified libraries before the linker searches
for others, is specifically prohibited from operating on setuid
programs unless the library is on the trusted path and has the
setuid bit set. Exploiting ping (or some other setuid program)
with LD_PRELOAD would be trivial—a user-provided library could
remap any call ping made to anything the attacker wanted—so
it was an
obvious restriction. LD_AUDIT using non-setuid libraries was evidently not so obvious.
The problem with allowing user-provided libraries to be used for auditing
setuid programs is not anywhere in the auditing API, but is
instead inherent in the way the runtime linker processes libraries. When
the library is opened with dlopen() to determine whether the
auditing callback symbols are present, any library initialization routines
must be
run. So, an exploit is done by finding a vulnerable system library
(it must be on the trusted path) that was not written with setuid
execution in mind (and thus does not have that bit set in the filesystem).
In his description of the
flaw, Ormandy gives an example of using the libpcprofile.so
library, which writes an output file to the path specified by the
PCPROFILE_OUTPUT environment variable. Using ping for
its setuid nature, he sets LD_AUDIT to the library,
points PCPROFILE_OUTPUT where he wants, and
ping ends up putting a user-writable file in
/etc/cron.d. The details will vary depending on the distribution,
but most will be vulnerable to the flaw.
There is nothing particularly special about libpcprofile.so, as
Ormandy describes ways to find other vulnerable system libraries, which are
likely to be numerous—those libraries weren't meant to be used by
privileged programs.
The other vulnerability is
more difficult to exploit, but stems from a similar laxness in
LD_AUDIT handling. In the Linux executable file format, ELF,
library search paths can be specified in the executable itself using
DT_RPATH or DT_RUNPATH tags. Those tags can contain a
$ORIGIN value, which is replaced with location of the executable
in the filesystem. That way, a library used by a single executable can be
located in a program-specific location rather than in the system library
directories.
The ELF specification recommends that $ORIGIN be disallowed for
setuid executables, but glibc ignores that recommendation.
Ormandy doesn't really see a problem with that:
It is tough to form a thorough complaint about this glibc behaviour however,
as any developer who believes they're smart enough to safely create suid
programs should be smart enough to understand the implications of $ORIGIN
and hard links on load behaviour. The glibc maintainers are some of the
smartest guys in free software, and well known for having a "no hand-holding"
stance on various issues, so I suspect they wanted a better argument than this
for modifying the behaviour (I pointed it out a few years ago, but there was
little interest).
Unfortunately, the $ORIGIN substitution code was reused in the
LD_AUDIT path. There was seemingly an attempt to restrict the use
of $ORIGIN in LD_AUDIT for privileged programs, but it
was insufficient. $ORIGIN will be expanded if it is the
only entry in LD_AUDIT. Since $ORIGIN expands to the
directory that contained the program, it isn't necessarily obvious that
there is anything there to exploit. But, there are known ways to exploit this
kind of situation.
If the directory that contains the executable can be replaced with an
exploit library object between the time $ORIGIN is expanded and
when the
value is used, the library will be loaded and the attacker can do what they
like. It is essentially a
race condition, but one that can be reliably won by the attacker.
Ormandy's example basically pauses the execution of a ping that
has been hardlinked into an attacker-controlled directory after the
expansion of $ORIGIN has been done. He then removes the directory
and its contents, and puts a library that has exploit code in its
initialization function in the place of the directory.
That particular exploit mechanism is fairly modern, using relatively recent
Linux kernel features, but there are others. Ormandy describes several
other ways to exploit the flaw, with differing requirements (e.g. a C
compiler or winning an easily winnable race) that might serve different
attack strategies. While both are local privilege escalations, they very
well might be used in conjunction with a web application or other flaw to
turn them into a remote root vulnerability.
Both of these vulnerabilities are quite serious for systems that allow
untrusted users to log in. Their impact on other systems depends on
whether there are other vulnerable, network-facing programs. While it is a
bit ironic that it was an audit of LD_AUDIT behavior that found
these bugs, it seems clear that there isn't enough of that kind of auditing
being done for Linux systems. It's always a bit worrisome to think of how
many of these kinds of flaws are still lingering out there.
(
Log in to post comments)