Avoiding LD_LIBRARY_PATH: The Options

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.

LD_LIBRARY_PATH Seems Useful. What's the Problem?

The problem is that LD_LIBRARY_PATH is a crude tool, and cannot be easily targeted at a problem program without also hitting other innocent programs. Sometimes this overspray is harmless (it costs some time, but doesn't break anything). Other times, it causes a program to link to the wrong version of something, and that program dies in mysterious ways.

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 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.

How to Use, and How To Avoid Using LD_LIBRARY_PATH

The best way to use LD_LIBRARY_PATH is interactively, as a short term aid for testing or development. A developer might use it to point his test program at an alternative version of a library. Beyond that, the less you use it, the better off you'll be. With that in mind, here is a list of ways to avoid LD_LIBRARY_PATH. The items are ordered from best to worst, with the best option right at the top:
  • Explicitly set the correct runpath for the objects you build. If you have the ability to relink the object, you can always do this, and no other workaround is needed. To set a runpath in an object, use the -R compiler/linker option.

    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:

    -R '$ORIGIN/../lib:/lib:/usr/lib'
    This allows myapp and mylib.so to be installed anywhere, as long as they are kept in the same positions relative to each other.

    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:

    % elfdump -d /usr/bin/ld | grep RUNPATH
           [7]  RUNPATH           0x2e6               $ORIGIN/../../lib
    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.

    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.

  • Many times, the problem comes in the form of open source software that explicitly sets the runpath to an incorrect value for Solaris. Can you fix the configuration script and contribute the change back to the package maintainer? You'll be doing lots of people a favor if you do.

  • If you have an object with a bad runpath (or no runpath) and the object cannot be rebuilt, it may be possible to alter its runpath using the elfedit command. Using the myapp example from the previous item:
    elfedit -e 'dyn:runpath $ORIGIN/../lib:/lib:/usr/lib' myapp
    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:
    • 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

  • If your application was linked with the -c option to the linker, then you can use the crle command to alter the configuration file associated with the application and change the settings for LD_LIBRARY_PATH that are applied for that application. This is a pretty good solution, but is limited by its complexity, and by the fact that the person who linked the object needs to have thought ahead far enough to provide for this option. Odds are that they didn't. If they had, they might just as well have set the runpath correctly in the first place, eliminating the need for anything else.

    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.

  • If none of the above are possible, then you are indeed stuck with LD_LIBRARY_PATH. In this case, the goal should be to minimize the number of applications that see this environment variable. You should never set it in your interactive shell environment (via whatever dot file your shell supports: .profile, .login, .cshrc, .basrc, etc...). Instead, put it in a wrapper shell script that you use to run the specific program.

    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.

Technorati Tag: OpenSolaris
Technorati Tag: Solaris


"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?

Posted by UX-admin on November 03, 2007 at 05:33 PM MDT #

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!

Posted by Ali Bahrami on November 04, 2007 at 12:37 AM MDT #

Post a Comment:
Comments are closed for this entry.

I work in the core Solaris OS group on the Solaris linkers. These blogs discuss various aspects of linking and the ELF object file format.


« December 2016