Linker and Libraries Guide

Appendix C Establishing Dependencies with Dynamic String Tokens

Overview

A dynamic object can establish dependencies explicitly (see "Locating Shared Object Dependencies") or via filters (see "Shared Objects as Filters"). Each of these mechanisms can be augmented with a runpath, which directs the runtime linker to search for and load the required dependency. As outlined in "Dynamic String Tokens", the string names used to record dependency and runpath information can be augmented with the reserved dynamic string tokens:

The following sections provide specific examples of how each of these tokens may be employed.

Instruction Set Specific Shared Objects

The dynamic token $ISALIST is expanded at runtime to reflect the native instruction sets executable on this platform, as displayed by the utility isalist(1).

Any string name that incorporates the $ISALIST token is effectively duplicated into multiple strings each assigned one of the available instruction sets. This token is only available for filter or runpath specifications.

The following example shows how the auxiliary filter libfoo.so.1 can be designed to access an instruction set specific filtee libbar.so.1:


$ LD_OPTIONS='-f /opt/ISV/lib/$ISALIST/libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"
[1]     SONAME    libfoo.so.1
[2]     AUXILIARY /opt/ISV/lib/$ISALIST/libbar.so.1

Or alternatively the runpath can be used:


$ LD_OPTIONS='-f libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R'/ot/ISV/lib/$ISALIST' foo.c
$ dump -Lv libfoo.so.1 | egrep "RPATH|AUXILIARY"
[1]     RPATH     /opt/ISV/lib/$ISALIST
[2]     AUXILIARY libbar.so.1

In either case the runtime linker will use the platform available instruction list to construct multiple search paths. For example, the following application has a dependency on libfoo.so.1 and is executed on a SUNW,Ultra-2:


$ ldd -ls prog
.....
 find object=libbar.so.1; required by ./libfoo.so.1
   search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)
     trying path=/opt/ISV/lib/sparcv9+vis/libbar.so.1
     trying path=/opt/ISV/lib/sparcv9/libbar.so.1
     trying path=/opt/ISV/lib/sparcv8plus+vis/libbar.so.1
     trying path=/opt/ISV/lib/sparcv8plus/libbar.so.1
     trying path=/opt/ISV/lib/sparcv8/libbar.so.1
     trying path=/opt/ISV/lib/sparcv8-fsmuld/libbar.so.1
     trying path=/opt/ISV/lib/sparcv7/libbar.so.1
     trying path=/opt/ISV/lib/sparc/libbar.so.1

Or an application with similar dependencies is executed on an MMX configured Pentium Pro:


$ ldd -ls prog
.....
 find object=libbar.so.1; required by ./libfoo.so.1
   search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)
     trying path=/opt/ISV/lib/pentium_pro+mmx/libbar.so.1
     trying path=/opt/ISV/lib/pentium_pro/libbar.so.1
     trying path=/opt/ISV/lib/pentium+mmx/libbar.so.1
     trying path=/opt/ISV/lib/pentium/libbar.so.1
     trying path=/opt/ISV/lib/i486/libbar.so.1
     trying path=/opt/ISV/lib/i386/libbar.so.1
     trying path=/opt/ISV/lib/i86/libbar.so.1

Reducing Auxiliary Searches

The use of $ISALIST within an auxiliary filter allows for one, or more filtees to provide alternative implementations of interfaces defined within the filter.

However, any interface defined in a filter that does not have an alternative implementation defined in a filtee will result in an exhaustive search of all potential filtees in an attempt to locate the required interface. If filtees are being employed to provide performance critical functions the exhaustive filtee searching can be counter productive.

A filtee can be built with the link-editors' -zendfiltee option to indicate that it is the last of the available filtees, and thus terminates any further filtee searching for that filter. For example, from the previous SPARC example, if the sparcv9 filtee existed, and was tagged with -zendfiltee, the filtee searches would be:


$ ldd -ls prog
.....
 find object=libbar.so.1; required by ./libfoo.so.1
   search path=/opt/ISV/lib/$ISALIST  (RPATH from file ./libfoo.so.1)
     trying path=/opt/ISV/lib/sparcv9+vis/libbar.so.1
     trying path=/opt/ISV/lib/sparcv9/libbar.so.1

System Specific Shared Objects

The dynamic tokens $OSNAME, $OSREL and $PLATFORM are expanded at runtime to provide system specific information. $OSNAME expands to reflect the name of the operating system, as displayed by the utility uname(1) with the -s option. $OSREL expands to reflect the operating system release level, as displayed by uname -r. $PLATFORM expands to reflect the underlying hardware implementation, as displayed by uname -i.

The following example shows how the auxiliary filter libfoo.so.1 can be designed to access a platform specific filtee libbar.so.1:


$ LD_OPTIONS='-f /usr/platform/$PLATFORM/lib/libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ dump -Lv libfoo.so.1 | egrep "SONAME|AUXILIARY"
[1]     SONAME    libfoo.so.1
[2]     AUXILIARY /usr/platform/$PLATFORM/lib/libbar.so.1

This mechanism is used on Solaris to provide platform specific extensions to the shared object /usr/lib/libc.so.1.


Note -

The environment variable LD_NOAUXFLTR can be set to disable the runtime linkers auxiliary filter processing. As auxiliary filters are frequently employed to provide platform specific optimizations, this option can be useful in evaluating a filtees use and performance impact.


Locating Associated Dependencies

Typically an unbundled product is designed to be installed in a stand-alone, unique location. This product is composed of binaries, shared object dependencies, and associated configuration files. For example, the unbundled product ABC might have the following layout:

Figure C-1 Unbundled Dependencies

Graphic

Assume that the product is designed for installation under /opt. Normally a user will be required to augment their PATH with /opt/ABC/bin so as to locate the product's binaries. Each binary will locate their dependencies using a hard-coded runpath within the binary. For the application abc it would be:


% dump -Lv abc
 
  [1]   NEEDED  libA.so.1
  [2]   RPATH   /opt/ABC/lib

and similarly for the dependency libA.so.1 this would be:


% dump -Lv libA.so.1
 
  [1]   NEEDED  libB.so.1
  [2]   RPATH   /opt/ABC/lib

This dependency representation works fine until the product is installed in some directory other than the recommended default. If a different installation is created, users must resort to LD_LIBRARY_PATH to run the product's applications. Often this is brought about by the introduction of wrappers for each binary, and sometimes inventive souls have even tried modifying the runpath within the appropriate objects.

The dynamic token $ORIGIN expands to the directory in which an object originated. This feature keys off of an auxiliary vector provided by the kernel to the runtime linker on process start-up (see also getexecname(3C)). Using this technology, we can now redefine our unbundled application to locate its dependencies in terms of $ORIGIN:


% dump -Lv abc
 
  [1]   NEEDED  libA.so.1
  [2]   RPATH   $ORIGIN/../lib

and the dependency libA.so.1 can also be defined in terms of $ORIGIN:


% dump -Lv libA.so.1
 
  [1]   NEEDED  libB.so.1
  [2]   RPATH   $ORIGIN

Therefore, if this product is now installed under /usr/local/ABC, and the user's PATH is augmented with /usr/local/ABC/bin, invocation of the application abc will result in a pathname lookup for its dependencies as follows:


% ldd -s abc
     find object=libA.so.1; required by abc
      search path=$ORIGIN/../lib  (RPATH from file abc) 
      trying path=/usr/local/ABC/lib/libA.so.1
        libA.so.1 =>     /usr/local/ABC/lib/libA.so.1
 
     find object=libB.so.1; required by /usr/local/ABC/lib/libA.so.1
      search path=$ORIGIN  (RPATH from file /usr/local/ABC/lib/libA.so.1)
      trying path=/usr/local/ABC/lib/libB.so.1
        libB.so.1 =>     /usr/local/ABC/lib/libB.so.1

Dependencies Between Unbundled Products

The next issue to confront dependency location is how to establish a model whereby one unbundled product might have dependencies on the shared objects of another unbundled product.

For example, the unbundled product XYZ might have dependencies on the product ABC. This dependency can be established by a host package installation script that generates a symbolic link to the installation point of the ABC product, as shown in the following figure.

Figure C-2 Unbundled Co-dependencies

Graphic

The binaries and shared objects of the XYZ product can represent their dependencies on the ABC product using the symbolic link as a stable reference point. For the application xyz this would be:


% dump -Lv xyz
  [1]   NEEDED  libX.so.1
  [2]   NEEDED  libA.so.1
  [3]   RPATH   $ORIGIN/../lib:$ORIGIN/../ABC/lib

and similarly for the dependency libX.so.1 this would be:


% dump -Lv libX.so.1
  [1]   NEEDED  libY.so.1
  [2]   NEEDED  libC.so.1
  [3]   RPATH   $ORIGIN:$ORIGIN/../ABC/lib

Therefore, if this product is now installed under /usr/local/XYZ, its post-install script would be required to establish a symbolic link of:


% ln -s ../ABC /usr/local/XYZ/ABC

If the user's PATH is augmented with /usr/local/XYZ/bin, then invocation of the application xyz will result in a pathname lookup for its dependencies as follows:


% ldd -s xyz
     find object=libX.so.1; required by xyz
      search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RPATH from file xyz)
      trying path=/usr/local/XYZ/lib/libX.so.1
        libX.so.1 =>     /usr/local/XYZ/lib/libX.so.1
 
     find object=libA.so.1; required by xyz
      search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RPATH from file xyz)
      trying path=/usr/local/XYZ/lib/libA.so.1
      trying path=/usr/local/ABC/lib/libA.so.1
        libA.so.1 =>     /usr/local/ABC/lib/libA.so.1
 
     find object=libY.so.1; required by /usr/local/XYZ/lib/libX.so.1
      search path=$ORIGIN:$ORIGIN/../ABC/lib \
                (RPATH from file /usr/local/XYZ/lib/libX.so.1)
      trying path=/usr/local/XYZ/lib/libY.so.1
        libY.so.1 =>     /usr/local/XYZ/lib/libY.so.1
 
     find object=libC.so.1; required by /usr/local/XYZ/lib/libX.so.1
      search path=$ORIGIN:$ORIGIN/../ABC/lib \
                (RPATH from file /usr/local/XYZ/lib/libX.so.1)
      trying path=/usr/local/XYZ/lib/libC.so.1
      trying path=/usr/local/ABC/lib/libC.so.1
        libC.so.1 =>     /usr/local/ABC/lib/libC.so.1
 
     find object=libB.so.1; required by /usr/local/ABC/lib/libA.so.1
       search path=$ORIGIN  (RPATH from file /usr/local/ABC/lib/libA.so.1)
       trying path=/usr/local/ABC/lib/libB.so.1
        libB.so.1 =>     /usr/local/ABC/lib/libB.so.1

Security

In a secure process (see "Security"), the expansion of the $ORIGIN string is allowed only of it expands to a trusted directory. The occurrence of other relative pathnames poses a security risk.

At first glance, it would appear that a path like $ORIGIN/../lib points to a fixed location; fixed by the location of the executable. Unfortunately, this is not true, and all that is needed to exploit a secure program with $ORIGIN is a writable directory in the same file system.

The following example shows how this could be achieved if $ORIGIN was arbitrarily expanded within a secure process.


% cd /worldwritable/dir/in/same/fs
% mkdir bin lib
% ln $ORIGIN/bin/program bin/program
% cp ~/crooked-libc.so.1 lib/libc.so.1
% bin/program
..... using crooked-libc.so.1

The utility crle(1) may be used to specify trusted directories applicable for secure applications to use $ORIGIN. Administrators who use this technique should ensure that the target directories are suitably protected from malicious intrusion.