Two glibc vulnerabilities
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:
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.
Index entries for this article | |
---|---|
Security | Glibc |
Security | Vulnerabilities/Privilege escalation |
Posted Oct 28, 2010 8:22 UTC (Thu)
by mjthayer (guest, #39183)
[Link] (1 responses)
I have to say that I am rather naive in this respect, and don't have much idea about how to search for vulnerabilities in code (other than grepping for known dodgy functions I suppose and looking for known ways of misusing them). What about an article on that subject? Or does anyone knowledgeable feel like doing a guest article on the subject?
Posted Oct 28, 2010 23:30 UTC (Thu)
by vonbrand (subscriber, #4458)
[Link]
Look at David A. Wheeler's Secure Programming HOWTO for starters.
Posted Oct 28, 2010 12:08 UTC (Thu)
by epa (subscriber, #39769)
[Link] (29 responses)
Or, alternatively, every environment variable X could be renamed .suid.X so the process can get at them if it really wants, but code written without thinking about suid conditions wouldn't be tripped up.
It wouldn't be POSIX, but so what?
Posted Oct 28, 2010 12:35 UTC (Thu)
by mjthayer (guest, #39183)
[Link] (28 responses)
It might not have helped here, but many exploits would surely be prevented by using some sort of suid loader, something like sudo, to load binaries which have to run setuid root, rather than marking the binaries themselves. That way the loader could take care of most the little things you have to think of when playing with suid and the binary would have less things (though still enough) it could get wrong.
Posted Oct 28, 2010 12:58 UTC (Thu)
by cesarb (subscriber, #6266)
[Link] (27 responses)
For instance, imagine if ping, instead of being setuid, called into dbus to load a helper daemon, and that helper daemon did all the actions which need root (in this example, sending pings). The part controlled by the user would limit itself to the user interface (showing one line per packet, doing the average calculations, and so on).
This way, the daemon is always run in a clean environment (the one dbus uses to launch daemons).
In the ping example, this also has the advantage that it makes it harder to avoid the restrictions on the minimum ping interval by running several instances of ping in parallel (no matter how many you run, they will talk to a single daemon, which can limit the ping rate per user or even globally).
Posted Oct 28, 2010 13:31 UTC (Thu)
by mjthayer (guest, #39183)
[Link]
Actually a clean environment was the main point of what I suggested above - i.e. using a setuid loader which can clean the environment (not just the environment as in setenv of course) before it loads your privileged binary. My worry with using dbus for this is that it requires a sizeable piece of infrastructure to be present and running properly in order to start your binary, which is fine for desktop use, but may not be appropriate for all situations.
Posted Oct 28, 2010 14:22 UTC (Thu)
by marcH (subscriber, #57642)
[Link] (13 responses)
Indeed setuid looks very much like a kludge
> For instance, imagine if ping, instead of being setuid, called into dbus to load a helper daemon,...
This looks like PolicyKit. Another setuid killer is setcap:
chmod u-s /bin/ping
Et voilà, all these LD_AUDIT holes are plugged without even upgrading glibc. Correct? If yes why isn't ping installed like this by default?
Posted Oct 28, 2010 14:58 UTC (Thu)
by RobSeace (subscriber, #4435)
[Link] (12 responses)
But, yeah, it'd still be better than giving them root, so yeah, why isn't this
Posted Oct 28, 2010 15:21 UTC (Thu)
by ccurtis (guest, #49713)
[Link] (8 responses)
Posted Oct 28, 2010 19:44 UTC (Thu)
by dlang (guest, #313)
[Link] (1 responses)
Posted Oct 29, 2010 11:00 UTC (Fri)
by marcH (subscriber, #57642)
[Link]
Posted Oct 28, 2010 22:04 UTC (Thu)
by kees (subscriber, #27264)
[Link] (5 responses)
[kees@fedora-13-i686 ~]$ ls -la /bin/su
Posted Oct 29, 2010 10:27 UTC (Fri)
by marcH (subscriber, #57642)
[Link] (4 responses)
Posted Oct 29, 2010 11:49 UTC (Fri)
by kees (subscriber, #27264)
[Link] (3 responses)
Posted Oct 29, 2010 11:52 UTC (Fri)
by rahulsundaram (subscriber, #21946)
[Link]
Precisely, the goal.
Posted Oct 29, 2010 13:41 UTC (Fri)
by marcH (subscriber, #57642)
[Link] (1 responses)
"Let's not bother making the windows more secure, because the front door sucks anyway".
Actually, let's bother. Because it's progress:
> It absolutely reduces the attack surface in general,...
Agreed!
Posted Oct 29, 2010 15:14 UTC (Fri)
by kees (subscriber, #27264)
[Link]
Posted Oct 28, 2010 17:05 UTC (Thu)
by jreiser (subscriber, #11027)
[Link] (2 responses)
Posted Oct 28, 2010 18:53 UTC (Thu)
by RobSeace (subscriber, #4435)
[Link] (1 responses)
Posted Oct 28, 2010 19:55 UTC (Thu)
by spender (guest, #23067)
[Link]
-Brad
Posted Oct 28, 2010 21:24 UTC (Thu)
by jengelh (guest, #33263)
[Link]
Or just enhance the kernel to enable socket(AF_INET, SOCK_RAW, IPPROTO_ICMP) for unprivileged users, and scrutinize packets sent/received through the socket.
Posted Oct 29, 2010 7:47 UTC (Fri)
by viro (subscriber, #7872)
[Link] (10 responses)
Posted Oct 29, 2010 10:59 UTC (Fri)
by marcH (subscriber, #57642)
[Link] (9 responses)
Actually, ping using D-Bus would be such a change that you would rather have new-secure-dbus-user-ping on one hand and good-old-insecure-root-ping on the other hand. Embedded and other single user systems can just run everything as root and use the old one.
If you are serious about security you really need a good IPC on multi-user systems... what would you use instead of D-BUS?
Posted Oct 31, 2010 15:18 UTC (Sun)
by nlucas (guest, #33793)
[Link] (8 responses)
It's too simple, so you could also solve this problem by making a static build of ping that can not load any shared library.
In my "non-security guy" perspective that would be enough for most environments.
Posted Nov 1, 2010 11:17 UTC (Mon)
by mjthayer (guest, #39183)
[Link] (7 responses)
I thought that with ELF there was no such thing as a pure statically linked binary.
Posted Nov 1, 2010 23:34 UTC (Mon)
by nix (subscriber, #2304)
[Link] (6 responses)
(Also, if pure statically-linked binaries cannot exist, what do you think /lib/ld-linux.so.2 is? If ELF interpreters don't count because they are technically shared objects and relocate themselves, /sbin/sln surely does. No relocation, no PT_INTERP: static as static comes, and on pretty much every system.)
Posted Nov 1, 2010 23:52 UTC (Mon)
by anselm (subscriber, #2796)
[Link] (1 responses)
I'm on Debian sid, and I don't have an executable called »sln« – not in /sbin, not anywhere. Am I missing something?
Posted Nov 2, 2010 0:14 UTC (Tue)
by nix (subscriber, #2304)
[Link]
Posted Nov 2, 2010 0:17 UTC (Tue)
by dlang (guest, #313)
[Link] (3 responses)
Posted Nov 3, 2010 14:58 UTC (Wed)
by nix (subscriber, #2304)
[Link] (2 responses)
Posted Nov 3, 2010 23:29 UTC (Wed)
by cesarb (subscriber, #6266)
[Link] (1 responses)
Posted Nov 4, 2010 0:18 UTC (Thu)
by foom (subscriber, #14868)
[Link]
Posted Oct 29, 2010 14:50 UTC (Fri)
by jzbiciak (guest, #5246)
[Link]
Ok, maybe my coffee hasn't kicked in yet. Why is it necessary to remove / replace the attacker-controlled directory in this case? For example, suppose /var/tmp and /bin are (perhaps unwisely) on the same partition: Or is it that the linker only expands LD_AUDIT if it is precisely '$ORIGIN', and so you have to replace the directory itself with a library file? Also, am I correct in thinking that if you keep your untrusted-user-writeable directories on different partitions from your setuid executables, you'll thwart this attack?
Two glibc vulnerabilities
References for program security
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
>
> For instance, imagine if ping, instead of being setuid, called into dbus to load a helper daemon, and that helper daemon did all the actions which need root (in this example, sending pings).
suid-binary vulnerabilities
setcap 'CAP_NET_RAW+ep' /bin/ping
suid-binary vulnerabilities
"plugged", really... Now, successfully exploitation just gets you the ability
to create raw sockets instead of full root... But, raw sockets aren't really
something you want to give to an untrusted user to play with, either...
done if it's possible?
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
-rwxr-xr-x. 1 root root 29292 Feb 12 2010 /bin/su
[kees@fedora-13-i686 ~]$ getcap /bin/su
/bin/su = cap_setuid+ep
[kees@fedora-13-i686 ~]$ ./glibc-ld_audit-origin.sh
[root@fedora-13-i686 ~]# id
uid=0(root) gid=500(kees) groups=0(root),500(kees) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
- progress towards the entire perimeter being finally secured.
- some malware knows only about windows. Being hacked once a month is progress compared to twice.
suid-binary vulnerabilities
why isn't this done if it's possible? There are [have been] bigger fish to fry.
suid-binary vulnerabilities
suid-binary vulnerabilities
then it would be very important for the linker to treat these "enhanced capability"
programs just as it would setuid/setgid programs... Ie: don't allow $LD_PRELOAD
and such... Otherwise, of course, it would be trivial for anyone to gain their
enhanced capabilities... Which, while not as bad as gaining root, is still not
something you want to make trivially easy to do...
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
>
> It's too simple, so you could also solve this problem by making a static build of ping that can not load any shared library.
suid-binary vulnerabilities
suid-binary vulnerabilities
/sbin/sln surely does. No relocation, no PT_INTERP: static as static comes, and on pretty much every system
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
suid-binary vulnerabilities
Two glibc vulnerabilities
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.
$ mkdir /var/tmp/evil
$ ln /bin/ping /var/tmp/evil/ping
$ cp $HOME/libevil.so /var/tmp/evil/.
$ export LD_AUDIT='$ORIGIN/libevil.so'
$ /var/tmp/evil/ping
# whoami
root