Avoiding LD_LIBRARY_PATH: The Options |
Ali Bahrami Friday November 02, 2007
With the introduction of the elfedit utility into Solaris, we have a new answer to the age old question of how to avoid everyones favorite way to get into trouble, the LD_LIBRARY_PATH environment variable. This seems like an appropriate time to revisit this topic.
Historically, inappropriate use of LD_LIBRARY_PATH might be the #1 one way to get yourself into trouble in an ELF environment. In particular, people who redistribute binaries with instructions for their users to set LD_LIBRARY_PATH in their shell startup scripts are unleashing forces beyond their control. Experience tells us that such use is destined to end badly.
This subject has been written about many times by many people. My colleague Rod Evans wrote about this ( LD_LIBRARY_PATH - just say no) for one of his first blog entries.
If you need additional convincing on this point, here are some suggested Google searches you might want to try:
LD_LIBRARY_PATH problem LD_LIBRARY_PATH bad LD_LIBRARY_PATH evil LD_LIBRARY_PATH darkest hell
If LD_LIBRARY_PATH is so bad, why does its use persist? Simply because it is the option of last resort, used when everything else has failed. We probably can't eliminate it, but we should strive to reduce its use to the bare minimum.
One common problem that people run into with a built in runpath is the use of an absolute path (e.g. /usr/local/lib). Absolute paths are no problem for the well known system libraries, because their location is fixed by convention as well as by standards. However, they can be trouble for libraries supplied by third parties and installed onto the system. Usually the user has a choice of where such applications are installed, their home directory, or /usr/local being two of the more popular places. An application that hard wires the location of user installed libraries cannot handle this. The solution in this case is to use the $ORIGIN token in those runpaths. The $ORIGIN token, which refers to the directory in which the using object resides, can be used to set a non-absolute runpath that will work in any location, as long as the desired libraries reside at a known location relative to the using program. Fortunately, this is often the case.
For example, consider the case of a 32-bit application named myapp, which relies on a sharable library named mylib.so, as well as on the standard system libraries found in /lib and /usr/lib. The -R option to put the runpath into myapp that will look in these places would be:
This allows myapp and mylib.so to be installed anywhere, as long as they are kept in the same positions relative to each other.-R '$ORIGIN/../lib:/lib:/usr/lib'
Even for system libraries, the use of $ORIGIN can be useful. We use it for all of the linker components in the system. For instance:
By setting the runpath using $ORIGIN instead of simply hardwiring the well known location /lib, we make it easier to test a tree of alternative linker components, such as results when we do a full build of the Solaris ON consolidation. We know that when we run a test copy of ld, that it will use the related libraries that were built with it, instead of binding to the installed system libraries.% elfdump -d /usr/bin/ld | grep RUNPATH [7] RUNPATH 0x2e6 $ORIGIN/../../lib
There is one exception to the advice to make heavy use of $ORIGIN. The runtime linker will not expand tokens like $ORIGIN for secure (setuid) applications. This should not be a problem in the vast majority of cases.
For this option to be possible, you need to be running a recent version of Solaris that has elfedit, and your object has to have been linked by a version of Solaris that has the necessary extra room. Quoting from the elfedit manpage:elfedit -e 'dyn:runpath $ORIGIN/../lib:/lib:/usr/lib' myapp
The desired string must already exist in the dynamic string table, or there must be enough reserved space within this section for the new string to be added. If your object has a string table reservation area, the value of the .dynamic DT_SUNW_STRPAD element indicates the size of the area. The following elfedit command can be used to check this:
% elfedit -r -e 'dyn:tag DT_SUNW_STRPAD' file
The dynamic section must already have a runpath element, or there must be an unused dynamic slot available where one can be inserted. To test for the presence of an existing runpath:
% elfedit -r -e 'dyn:runpath' file
A dynamic section uses an element of type DT_NULL to terminate the array found in that section. The final DT_NULL cannot be changed, but if there are more than one of these, elfedit can convert one of them into a runpath element. To test for extra dynamic slots:
% elfedit -r -e 'dyn:tag DT_NULL' file
You can use crle with an application that was not linked with -c, either by setting the LD_CONFIG environment variable, or by modifying the global system configuration file. However, both of these options suffer from the same issues as the LD_LIBRARY_PATH environment variable: They are too coarse grained to be applied to a single application in a targeted way.
The use of a wrapper script is a pretty safe way to use LD_LIBRARY_PATH, but you should be aware of one limitation of this approach: If the program being wrapped starts any other programs, then those programs will see the LD_LIBRARY_PATH environment variable. Since programs starting other programs is a common Unix technique, this form of leakage can be more common that you might realize.
"If LD_LIBRARY_PATH is so bad, why does its use persist?"
The problem persists because very few people know how to compile and link binaries and .so libraries properly (i.e. through the front end driver, `cc`, with "-R/opt/abcd/lib" resp. "-R/opt/abcd/lib/64" set).
Linkers and libraries require very good understanding of the link editor, of the Executable and Linking Format (ELF) and of how the whole process works; this is some highly specialized knowledge, and most "developers" today don't have a clue about it. (I don't have a very high opinion of contemporary "developers", can you tell?)
I'm just so happy to have to recompile tons of binaries just so I can clean up somebody's else mess, and link the binary to have the correct link search paths hardcoded in.
Thankfully, `elfedit` should help a lot fixing these broken binaries. It should prove a highly useful tool. Any timelines as to when it will be backported to Solaris 10?
There's no plan to backport elfedit to Solaris 10 at this moment, but that could change if there was enough pull. There are 2 issues: (1) There were lots of supporting changes, so this is a big move, and (2) The other linker changes that leave the extra room for runpaths would also need to be present, and the entire system rebuilt with that room. I appreciate how useful that would be though. We'll see what happens... Thanks!
[9] Introducing elfedit | [11] Cross Link-Editor |