Environmental contamination is a constant threat with conservatives in power (just kidding… seriously). Over the next few articles I will attempt to clarify the impact the environment has on the execution of a program. The environment is a fairly complicated subject so this article will be broken into several parts (I’m guessing about three). So without further ado…
We’re all familiar with the environment on a typical Unix system. The environment is full of “stuff” like variables, command line arguments, parent process ID, etc. Many of these are things you can look at by typing things such as export in a bash or env in csh or by just reviewing the arguments you pass to a program on the command line. Briefly all this information is stored in a section of memory that is persistent across execv()s. Something you may or may not be aware of is the impact that this can have on the execution path of a program. Perhaps, through experience, use, or instinct, you know that an application can read and utilize the information from the environment. A trivial example would be DISPLAY environment variable that we have all had to tweak at one time or another. When you run xterm that variable is used to determine which display device should be used. However, the environment is not only capable of changing the execution path in some trivial, often benign fashion, but is also quite capable of changing the entire application. If we view a running applications as a state machine, a candy vending machine for example, then we typically imagine the most someone can accomplish by tampering with the environment is perhaps getting a free candybar by kicking it hard enough. However, the environment offers so much more to a opportunistic and astute nare-do-well. By carefully contaminating the environment it is quite possible to turn that candy vending machine into an ATM, a complete change of the entire state machine.
How can the environment have such a drastic impact?
The typical Linux distribution heavily leverages executable formats such as ELF and a.out which take advantage of something called dynamic or shared libraries. Without delving into the details this means that a single application is usually made up of many components linked together when the application needs them. These components, code segments, reside in various places on the filesystem in files called “dynamic libraries”. Like all application code, after the code segments pulled from these libraries they can be impacted by the environment. However, the environments impact on libraries after they’re loaded is not the biggest threat; the biggest threat comes from the thing that stitches all of these code segments together into a usable application, the dynamic loader. The dynamic loader (ld-linux.so.* for the ELF format and ld.so for the a.out format) needs to know where the different required code segments are located. It doesn’t just randomly search the filesystem for the right part, there are some common places that it looks, like /usr/lib and /lib, and it is possible for the application itself to give the dynamic loader hints (when the application is compiled to take advantage of DT_RUNPATH or DT_RPATH). In addition to the common places it is also possible to have “shared” libraries reside in an atypical location. When this is the case you must give the linker a hint, well you have to give it more than a hint, you have to explicitly tell it the directory to look in to find the library. And, you guessed it, this is done through environment variables such as LD_LIBRARY_PATH, LD_PRELOAD, and LD_ORIGIN_PATH. These environment variables allow you to alter the behavior of the dynamic linker. Do you see where this is headed?
So a ne’re-do-well comes along, drops some of his own libraries, say libevil.so in his/her home directory. They then go ahead and alter the environment so that their home directory is searched for libraries containing the appropriate code segments before the system libraries. As a result they are but a hop, skip, and a jump from dumping your password file.
So why don’t we see everyone doing this and leveraging suid apps like password to become root? The secret is in glibc: __libc_enable_secure.Spencer Shimko 06 April 2006