Linker and Libraries Guide

Appendix C Establishing Dependencies with Dynamic String Tokens

A dynamic object can establish dependencies explicitly or through filters. Each of these mechanisms can be augmented with a runpath, which directs the runtime linker to search for and load the required dependency. String names used to record filters, dependencies and runpath information can be augmented with the following reserved dynamic string tokens.

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

Capability Specific Shared Objects

The dynamic token $CAPABILITY can be used to specify a directory in which capability specific shared objects exist. This token is available for filters and dependencies. As this token can expand to multiple objects, its use with dependencies is controlled. Dependencies obtained with dlopen(3C), can use this token with the mode RTLD_FIRST. Explicit dependencies that use this token will load the first appropriate dependency found.


Note –

The original capabilities implementation was based solely on hardware capabilities. The token $HWCAP was used to select this capability processing. Capabilities have since been extended beyond hardware capabilities, and the $HWCAP token has been replaced by the $CAPABILITY token. For compatibility, the $HWCAP token is interpreted as an alias for the $CAPABILITY token.


The path name specification must consist of a full path name terminated with the $CAPABILITY token. Shared objects that exist in the directory that is specified with the $CAPABILITY token are inspected at runtime. These objects should indicate their capability requirements. See Identifying Capability Requirements. Each object is validated against the capabilities that are available to the process. Those objects that are applicable for use with the process, are sorted in descending order of their capability values. These sorted filtees are used to resolve symbols that are defined within the filter.

Filtees within the capabilities directory have no naming restrictions. The following example shows how the auxiliary filter libfoo.so.1 can be designed to access hardware capability filtees.


$ LD_OPTIONS='-f /opt/ISV/lib/cap/$CAPABILITY' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ elfdump -d libfoo.so.1 | egrep 'SONAME|AUXILIARY'
       [2]  SONAME            0x1                 libfoo.so.1
       [3]  AUXILIARY         0x96                /opt/ISV/lib/cap/$CAPABILITY
$ elfdump -H /opt/ISV/lib/cap/*

/opt/ISV/lib/cap/filtee.so.3:

Capabilities Section:  .SUNW_cap
 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_HW_1     0x1000  [ SSE2 ]

/opt/ISV/lib/cap/filtee.so.1:

Capabilities Section:  .SUNW_cap
 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_HW_1     0x40  [ MMX ]

/opt/ISV/lib/cap/filtee.so.2:

Capabilities Section:  .SUNW_cap
 Object Capabilities:
     index  tag               value
       [0]  CA_SUNW_HW_1     0x800  [ SSE ]

If the filter libfoo.so.1 is processed on a system where the MMX and SSE hardware capabilities are available, the following filtee search order occurs.


$ cc -o prog prog.c -R. -lfoo
$ LD_DEBUG=symbols prog
....
01233: symbol=foo;  lookup in file=libfoo.so.1  [ ELF ]
01233: symbol=foo;  lookup in file=cap/filtee.so.2  [ ELF ]
01233: symbol=foo;  lookup in file=cap/filtee.so.1  [ ELF ]
....

Note that the capability value for filtee.so.2 is greater than the capability value for filtee.so.1. filtee.so.3 is not a candidate for inclusion in the symbol search, as the SSE2 capability is not available.

Reducing Filtee Searches

The use of $CAPABILITY within a filter enables one or more filtees to provide implementations of interfaces that are defined within the filter.

All shared objects within the specified $CAPABILITY directory are inspected to validate their availability, and to sort those found appropriate for the process. Once sorted, all objects are loaded in preparation for use.

A filtee can be built with the link-editor's -z endfiltee option to indicate that it is the last of the available filtees. A filtee identified with this option, terminates the sorted list of filtees for that filter. No objects sorted after this filtee are loaded for the filter. From the previous example, if the filter.so.2 filtee was tagged with -z endfiltee, the filtee search would be as follows.


$ LD_DEBUG=symbols prog
....
01424: symbol=foo;  lookup in file=libfoo.so.1  [ ELF ]
01424: symbol=foo;  lookup in file=cap/filtee.so.2  [ ELF ]
....

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). This token is available for filters, runpath definitions, and dependencies. As this token can expand to multiple objects, its use with dependencies is controlled. Dependencies obtained with dlopen(3C), can use this token with the mode RTLD_FIRST. Explicit dependencies that use this token will load the first appropriate dependency found.


Note –

This token is obsolete, and might be removed in a future release of Oracle Solaris. See Capability Specific Shared Objects for the recommended technique for handling instruction set extensions.


Any string name that incorporates the $ISALIST token is effectively duplicated into multiple strings. Each string is assigned one of the available instruction sets.

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
$ elfdump -d libfoo.so.1 | egrep 'SONAME|AUXILIARY'
       [2]  SONAME            0x1                 libfoo.so.1
       [3]  AUXILIARY         0x96                /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'/opt/ISV/lib/$ISALIST' foo.c
$ elfdump -d libfoo.so.1 | egrep 'RUNPATH|AUXILIARY'
       [3]  AUXILIARY         0x96                libbar.so.1
       [4]  RUNPATH           0xa2                /opt/ISV/lib/$ISALIST

In either case the runtime linker uses the platform available instruction list to construct multiple search paths. For example, the following application is dependent on libfoo.so.1 and 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 Filtee Searches

The use of $ISALIST within a filter enables one or more filtees to provide implementations of interfaces defined within the filter.

Any interface defined in a filter can 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, this exhaustive filtee searching can be counterproductive.

A filtee can be built with the link-editor's -z endfiltee option to indicate that it is the last of the available filtees. This option terminates any further filtee searching for that filter. From the previous SPARC example, if the SPARCV9 filtee existed, and was tagged with -z endfiltee, the filtee searches would be as follows.


$ 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, $PLATFORM and $MACHINE are expanded at runtime to provide system specific information. These tokens are available for filters, runpath, or dependency definitions.

$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 platform name, as displayed by uname -i. $MACHINE expands to reflect the underlying machine hardware name, as displayed by uname -m.

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 /platform/$PLATFORM/lib/libbar.so.1' \
cc -o libfoo.so.1 -G -K pic -h libfoo.so.1 -R. foo.c
$ elfdump -d libfoo.so.1 | egrep 'SONAME|AUXILIARY'
       [2]  SONAME            0x1                 libfoo.so.1
       [3]  AUXILIARY         0x96                /platform/$PLATFORM/lib/libbar.so.1

This mechanism is used in the Oracle Solaris OS to provide platform specific extensions to the shared object /lib/libc.so.1.

Locating Associated Dependencies

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

Figure C–1 Unbundled Dependencies

Unbundled dependencies example.

Assume that the product is designed for installation under /opt. Normally, you would augment the PATH with /opt/ABC/bin to locate the product's binaries. Each binary locates their dependencies using a hard-coded runpath within the binary. For the application abc, this runpath would be as follows.


$ cc -o abc abc.c -R/opt/ABC/lib -L/opt/ABC/lib -lA
$ elfdump -d abc | egrep 'NEEDED|RUNPATH'
       [0]  NEEDED            0x1b5               libA.so.1
       ....
       [4]  RUNPATH           0x1bf               /opt/ABC/lib

Similarly, for the dependency libA.so.1 the runpath would be as follows.


$ cc -o libA.so.1 -G -Kpic A.c -R/opt/ABC/lib -L/opt/ABC/lib -lB
$ elfdump -d libA.so.1 | egrep 'NEEDED|RUNPATH'
       [0]  NEEDED            0x96                libB.so.1
       [4]  RUNPATH           0xa0                /opt/ABC/lib

This dependency representation works until the product is installed in some directory other than the recommended default.

The dynamic token $ORIGIN expands to the directory in which an object originated. This token is available for filters, runpath, or dependency definitions. Use this technology to redefine the unbundled application to locate its dependencies in terms of $ORIGIN.


$ cc -o abc abc.c '-R$ORIGIN/../lib' -L/opt/ABC/lib -lA
$ elfdump -d abc | egrep 'NEEDED|RUNPATH'
       [0]  NEEDED            0x1b5               libA.so.1
       ....
       [4]  RUNPATH           0x1bf               $ORIGIN/../lib

The dependency libA.so.1 can also be defined in terms of $ORIGIN.


$ cc -o libA.so.1 -G -Kpic A.c '-R$ORIGIN' -L/opt/ABC/lib -lB
$ elfdump -d lib/libA.so.1 | egrep 'NEEDED|RUNPATH'
       [0]  NEEDED            0x96                libB.so.1
       [4]  RUNPATH           0xa0                $ORIGIN

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 result in a path name lookup for its dependencies as follows.


$ ldd -s abc
....
  find object=libA.so.1; required by abc
    search path=$ORIGIN/../lib  (RUNPATH/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  (RUNPATH/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

Another issue related to dependency location is how to establish a model whereby unbundled products express dependencies between themselves.

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

Figure C–2 Unbundled Co-Dependencies

Unbundled co-dependencies example.

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


$ cc -o xyz xyz.c '-R$ORIGIN/../lib:$ORIGIN/../ABC/lib' \
-L/opt/ABC/lib -lX -lA
$ elfdump -d xyz | egrep 'NEEDED|RUNPATH'
       [0]  NEEDED            0x1b5               libX.so.1
       [1]  NEEDED            0x1bf               libA.so.1
       ....
       [2]  NEEDED            0x18f               libc.so.1
       [5]  RUNPATH           0x1c9               $ORIGIN/../lib:$ORIGIN/../ABC/lib

and similarly for the dependency libX.so.1 this runpath would be as follows.


$ cc -o libX.so.1 -G -Kpic X.c '-R$ORIGIN:$ORIGIN/../ABC/lib' \
-L/opt/ABC/lib -lY -lC
$ elfdump -d libX.so.1 | egrep 'NEEDED|RUNPATH'
       [0]  NEEDED            0x96                libY.so.1
       [1]  NEEDED            0xa0                libC.so.1
       [5]  RUNPATH           0xaa                $ORIGIN:$ORIGIN/../ABC/lib

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 result in a path name lookup for its dependencies as follows.


$ ldd -s xyz
....
  find object=libX.so.1; required by xyz
    search path=$ORIGIN/../lib:$ORIGIN/../ABC/lib  (RUNPATH/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  (RUNPATH/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 \
                (RUNPATH/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 \
                (RUNPATH/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  (RUNPATH/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, the expansion of the $ORIGIN string is allowed only if it expands to a trusted directory. The occurrence of other relative path names, poses a security risk.

A path like $ORIGIN/../lib apparently points to a fixed location, fixed by the location of the executable. However, the location is not actually fixed. A writable directory in the same file system could exploit a secure program that uses $ORIGIN.

The following example shows this possible security breach 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

You can use the utility crle(1) to specify trusted directories that enable secure applications to use $ORIGIN. Administrators who use this technique should ensure that the target directories are suitably protected from malicious intrusion.