Linker and Libraries Guide

Runtime Linker Auditing Interface

The rtld-audit interface enables you to access information pertaining to the runtime linking of a process. The rtld-audit interface is implemented as an audit library that offers one or more auditing interface routines. If this library is loaded as part of a process, the audit routines are called by the runtime linker at various stages of process execution. These interfaces enable the audit library to access the following information.

Some of this information can be obtained by preloading specialized shared objects. However, a preloaded object exists within the same namespace as the objects of a application. This preloading often restricts, or complicates the implementation of the preloaded shared object. The rtld-audit interface offers you a unique namespace in which to execute audit libraries. This namespace ensures that the audit library does not intrude upon the normal bindings that occur within the application.

An example of using the rtld-audit interface is the runtime profiling of shared objects that is described in Profiling Shared Objects.

Establishing a Namespace

When the runtime linker binds a dynamic executable with its dependencies, a linked list of link-maps is generated to describe the application. The link-map structure describes each object within the application. The link-map structure is defined in /usr/include/sys/link.h. The symbol search mechanism that is required to bind together the objects of an application traverse this list of link-maps. This link-map list is said to provide the namespace for the applications symbol resolution.

The runtime linker is also described by a link-map. This link-map is maintained on a different list from the list of application objects. The runtime linker therefore resides in its own unique namespace, which prevents the application from seeing, or being able to directly access, any services within the runtime linker. An application can therefore only access the runtime linker through the filters provided by libc.so.1, or libdl.so.1.

Two identifiers are defined in /usr/include/link.h to define the application and runtime linker link-map lists.

#define LM_ID_BASE      0     /* application link-map list */
#define LM_ID_LDSO      1     /* runtime linker link-map list */

In addition to these two standard link-map lists, the runtime linker allows the creation of an arbitrary number of additional link-map lists. Each of these additional link-map lists provides a unique namespace. The rtld-audit interface employs its own link-map list on which the audit libraries are maintained. The audit libraries are therefore isolated from the symbol binding requirements of the application. Every rtld-audit support library is assigned a unique new link-map identifier.

An audit library can inspect the application link-map list using dlmopen(3C). When dlmopen() is used with the RTLD_NOLOAD flag, the audit library can query the existence of an object without causing the object to be loaded.

Creating an Audit Library

An audit library is built like any other shared object. However, the audit libraries unique namespace within a process requires some additional care.

If an audit library references external interfaces, then the audit library must define the dependency that provides the interface definition. For example, if the audit library calls printf(3C), then the audit library must define a dependency on libc. See Generating a Shared Object Output File. Because the audit library has a unique namespace, symbol references cannot be satisfied by the libc that is present in the application being audited. If an audit library has a dependency on libc, then two versions of libc.so.1 are loaded into the process. One version satisfies the binding requirements of the application link-map list. The other version satisfies the binding requirements of the audit link-map list.

To ensure that audit libraries are built with all dependencies recorded, use the link-editors -z defs option.

Some system interfaces assume that the interfaces are the only instance of their implementation within a process. Examples of such implementations are signals and malloc(3C). Audit libraries should avoid using such interfaces, as doing so can inadvertently alter the behavior of the application.


Note –

An audit library can allocate memory using mapmalloc(3MALLOC), as this allocation method can exist with any allocation scheme normally employed by the application.


Invoking the Auditing Interface

The rtld-audit interface is enabled by one of two means. Each method implies a scope to the objects that are audited.

Both methods of defining auditors employ a string consisting of a colon-separated list of shared objects that are loaded by dlmopen(3C). Each object is loaded onto its own audit link-map list. Each object is searched for audit routines using dlsym(3C). Audit routines that are found are called at various stages during the applications execution.

Secure applications can only obtain audit libraries from trusted directories. By default, the only trusted directories that are known to the runtime linker for 32–bit objects are/lib/secure and /usr/lib/secure. For 64–bit objects, the trusted directories are /lib/secure/64 and /usr/lib/secure/64.


Note –

Auditing can be disabled at runtime by setting the environment variable LD_NOAUDIT to a non-null value.


Recording Local Auditors

Local auditing requirements can be established when an object is built using the link-editor options -p or -P. For example, to audit libfoo.so.1, with the audit library audit.so.1, record the requirement at link-edit time using the -p option.


$ cc -G -o libfoo.so.1 -Wl,-paudit.so.1 -K pic foo.c
$ elfdump -d libfoo.so.1 | grep AUDIT
       [2]  AUDIT             0x96                audit.so.1

At runtime, the existence of this audit identifier results in the audit library being loaded. Information is then passed to the audit library regarding the identifying object.

With this mechanism alone, information such as searching for the identifying object occurs prior to the audit library being loaded. To provide as much auditing information as possible, the existence of an object requiring local auditing is propagated to users of that object. For example, if an application is built with a dependency on libfoo.so.1, then the application is identified to indicate its dependencies require auditing.


$ cc -o main main.c libfoo.so.1
$ elfdump -d main | grep AUDIT
       [4]  DEPAUDIT          0x1be               audit.so.1

The auditing enabled through this mechanism results in the audit library being passed information regarding all of the applications explicit dependencies. This dependency auditing can also be recorded directly when creating an object by using the link-editor's -P option.


$ cc -o main main.c -Wl,-Paudit.so.1
$ elfdump -d main | grep AUDIT
       [3]  DEPAUDIT          0x1b2               audit.so.1

Recording Global Auditors

Global auditing requirements can be established by setting the environment variable LD_AUDIT. For example, this environment variable can be used to audit the application main together with all the dependencies of the application, with the audit library audit.so.1.


$ LD_AUDIT=audit.so.1 main

Global auditing can also be achieved by recording a local auditor in the application, together with the -z globalaudit option. For example, the application main can be built to enable global auditing by using the link-editor's -P option and -z globalaudit option.


$ cc -o main main.c -Wl,-Paudit.so.1 -z globalaudit
$ elfdump -d main | grep AUDIT
       [3]  DEPAUDIT          0x1b2               audit.so.1
      [26]  FLAGS_1           0x1000000           [ GLOBAL-AUDITING ]

The auditing enabled through either of these mechanisms results in the audit library being passed information regarding all of the dynamic objects of the application.

Audit Interface Interactions

Audit routines are provided one or more cookies. A cookie is a data item that describes an individual dynamic object. An initial cookie is provided to the la_objopen() routine when a dynamic object is initially loaded. This cookie is a pointer to the associated Link_map of the loaded dynamic object. However, the la_objopen() routine is free to allocate, and return to the runtime linker, an alternative cookie. This mechanism provides the auditor a means of maintaining their own data with each dynamic object, and receiving this data with all subsequent audit routine calls.

The rtld-audit interface enables multiple audit libraries to be supplied. In this case, the return information from one auditor is passed to the same audit routine of the next auditor. Similarly, a cookie that is established by one auditor is passed to the next auditor. Care should be taken when designing an audit library that expects to coexist with other audit libraries. A safe approach should not alter the bindings, or cookies, that would normally be returned by the runtime linker. Alteration of these data can produce unexpected results from audit libraries that follow. Otherwise, all auditors should be designed to cooperate in safely changing any binding or cookie information.

Audit Interface Functions

The following routines are provided by the rtld-audit interface. The routines are described in their expected order of use.


Note –

References to architecture, or object class specific interfaces are reduced to their generic name to simplify the discussions. For example, a reference to la_symbind32() and la_symbind64() is specified as la_symbind().


la_version()

This routine provides the initial handshake between the runtime linker and the audit library. This interface must be provided for the audit library to be loaded.

uint_t la_version(uint_t version);

The runtime linker calls this interface with the highest version of the rtld-audit interface the runtime linker is capable of supporting. The audit library can verify this version is sufficient for its use, and return the version the audit library expects to use. This version is normally LAV_CURRENT, which is defined in /usr/include/link.h.

If the audit library return is zero, or a version that is greater than the rtld-audit interface the runtime linker supports, the audit library is discarded.

The remaining audit routines are provided one or more cookies. See Audit Interface Interactions.

Following the la_version() call, two calls are made to the la_objopen() routine. The first call provides link-map information for the dynamic executable, and the second call provides link-map information for the runtime linker.

la_objopen()

This routine is called when a new object is loaded by the runtime linker.

uint_t la_objopen(Link_map *lmp, Lmid_t lmid, uintptr_t *cookie);

lmp provides the link-map structure that describes the new object. lmid identifies the link-map list to which the object has been added. cookie provides a pointer to an identifier. This identifier is initialized to the objects lmp. This identifier can be reassigned by the audit library to better identify the object to other rtld-audit interface routines.

The la_objopen() routine returns a value that indicates the symbol bindings of interest for this object. The return value is a mask of the following values that are defined in/usr/include/link.h.

  • LA_FLG_BINDTO – Audit symbol bindings to this object.

  • LA_FLG_BINDFROM – Audit symbol bindings from this object.

These values allow an auditor to select the objects to monitor with la_symbind(). A return value of zero indicates that binding information is of no interest for this object.

For example, an auditor can monitor the bindings from libfoo.so to libbar.so. la_objopen() for libfoo.so should return LA_FLG_BINDFROM. la_objopen() for libbar.so should return LA_FLG_BINDTO.

An auditor can monitor all bindings between libfoo.so and libbar.so. la_objopen() for both objects should return LA_FLG_BINDFROM and LA_FLG_BINDTO.

An auditor can monitor all bindings to libbar.so. la_objopen() for libbar.so should return LA_FLG_BINDTO. All la_objopen() calls should return LA_FLG_BINDFROM.

With the auditing version LAV_VERSION5, an la_objopen() call that represents the dynamic executable is provided to a local auditor. In this case, the auditor should not return a symbol binding flag, as the auditor may have been loaded too late to monitor any symbol bindings associated with the dynamic executable. Any flags that are returned by the auditor are ignored. The la_objopen() call provides the local auditor an initial cookie which is required for any subsequent la_preinit() or la_activity() calls.

la_activity()

This routine informs an auditor that link-map activity is occurring.

void la_activity(uintptr_t *cookie, uint_t flags);

cookie identifies the object heading the link-map. flags indicates the type of activity as defined in /usr/include/link.h.

  • LA_ACT_ADD – Objects are being added to the link-map list.

  • LA_ACT_DELETE – Objects are being deleted from the link-map list.

  • LA_ACT_CONSISTENT – Object activity has been completed.

An LA_ACT_ADD activity is called on process start up, following the la_objopen() calls for the dynamic executable and runtime linker, to indicate that new dependencies are being added. This activity is also called for lazy loading and dlopen(3C) events. An LA_ACT_DELETE activity is also called when objects are deleted with dlclose(3C).

Both the LA_ACT_ADD and LA_ACT_DELETE activities are a hint of the events that are expected to follow. There are a number of scenarios where the events that unfold might be different. For example, the addition of new objects can result in some of the new objects being deleted should the objects fail to relocate fully. The deletion of objects can also result in new objects being added should .fini executions result in lazy loading new objects. An LA_ACT_CONSISTENT activity follows any object additions or object deletions, and can be relied upon to indicate that the application link-map list is consistent. Auditors should be careful to verify actual results rather than blindly trusting the LA_ACT_ADD and LA_ACT_DELETE hints.

For auditing versions LAV_VERSION1 through LAV_VERSION4, la_activity() was only called for global auditors. With the auditing version LAV_VERSION5, activity events can be obtained by local auditors. An activity event provides a cookie that represents the application link-map. To prepare for this activity, and allow the auditor to control the content of this cookie, an la_objopen() call is first made to the local auditor. The la_objopen() call provides an initial cookie representing the application link-map. See Audit Interface Interactions.

la_objsearch()

This routine informs an auditor that an object is about to be searched for.

char *la_objsearch(const char *name, uintptr_t *cookie, uint_t flags);

name indicates the file or path name being searched for. cookie identifies the object initiating the search. flags identifies the origin and creation of name as defined in /usr/include/link.h.

  • LA_SER_ORIG – The initial search name. Typically, this name indicates the file name that is recorded as a DT_NEEDED entry, or the argument supplied to dlopen(3C).

  • LA_SER_LIBPATH – The path name has been created from a LD_LIBRARY_PATH component.

  • LA_SER_RUNPATH – The path name has been created from a runpath component.

  • LA_SER_DEFAULT – The path name has been created from a default search path component.

  • LA_SER_CONFIG – The path component originated from a configuration file. See crle(1).

  • LA_SER_SECURE – The path component is specific to secure objects.

The return value indicates the search path name that the runtime linker should continue to process. A value of zero indicates that this path should be ignored. An audit library that monitors search paths should return name.

la_objfilter()

This routine is called when a filter loads a new filtee. See Shared Objects as Filters.

int la_objfilter(uintptr_t *fltrcook, const char *fltestr,
        uintptr_t *fltecook, uint_t flags);

fltrcook identifies the filter. fltestr points to the filtee string. fltecook identifies the filtee. flags is presently unused. la_objfilter() is called after la_objopen() for both the filter and filtee.

A return value of zero indicates that this filtee should be ignored. An audit library that monitors the use of filters should return a non-zero value.

la_preinit()

This routine is called once after all objects have been loaded for the application, but before transfer of control to the application occurs.

void la_preinit(uintptr_t *cookie);

cookie identifies the primary object that started the process, normally the dynamic executable.

For auditing versions LAV_VERSION1 through LAV_VERSION4, la_preinit() was only called for global auditors. With the auditing version LAV_VERSION5, a preinit event can be obtained by local auditors. A preinit event provides a cookie that represents the application link-map. To prepare for this preinit, and allow the auditor to control the content of this cookie, an la_objopen() call is first made to the local auditor. The la_objopen() call provides an initial cookie representing the application link-map. See Audit Interface Interactions.

la_symbind()

This routine is called when a binding occurs between two objects that have been tagged for binding notification from la_objopen().

uintptr_t la_symbind32(Elf32_Sym *sym, uint_t ndx,
        uintptr_t *refcook, uintptr_t *defcook, uint_t *flags);
 
uintptr_t la_symbind64(Elf64_Sym *sym, uint_t ndx,
        uintptr_t *refcook, uintptr_t *defcook, uint_t *flags,
        const char *sym_name);

sym is a constructed symbol structure, whose sym->st_value indicates the address of the symbol definition being bound. See /usr/include/sys/elf.h. la_symbind32() adjusts the sym->st_name to point to the actual symbol name. la_symbind64() leaves sym->st_name to be the index into the bound objects string table.

ndx indicates the symbol index within the bound object's dynamic symbol table. refcook identifies the object making reference to this symbol. This identifier is the same identifier as passed to the la_objopen() routine that returned LA_FLG_BINDFROM. defcook identifies the object defining this symbol. This identifier is the same as passed to the la_objopen() that returned LA_FLG_BINDTO.

flags points to a data item that can convey information regarding the binding. This data item can also be used to modify the continued auditing of this procedure linkage table entry. This value is a mask of the symbol binding flags that are defined in /usr/include/link.h.

The following flags can be supplied to la_symbind().

  • LA_SYMB_DLSYM – The symbol binding occurred as a result of calling dlsym(3C).

  • LA_SYMB_ALTVALUE (LAV_VERSION2) – An alternate value was returned for the symbol value by a previous call to la_symbind().

If la_pltenter() or la_pltexit() routines exist, these routines are called after la_symbind() for procedure linkage table entries. These routines are called each time that the symbol is referenced. See also Audit Interface Limitations.

The following flags can be supplied from la_symbind() to alter this default behavior. These flags are applied as a bitwise-inclusive OR with the value pointed to by the flags argument.

  • LA_SYMB_NOPLTENTER – Do not call the la_pltenter() routine for this symbol.

  • LA_SYMB_NOPLTEXIT – Do not call the la_pltexit() routine for this symbol.

The return value indicates the address to which control should be passed following this call. An audit library that monitors symbol binding should return the value of sym->st_value so that control is passed to the bound symbol definition. An audit library can intentionally redirect a symbol binding by returning a different value.

sym_name, which is applicable for la_symbind64() only, contains the name of the symbol being processed. This name is available in the sym->st_name field for the 32–bit interface.

la_pltenter()

These routines are system specific. These routines are called when a procedure linkage table entry, between two objects that have been tagged for binding notification, is called.

uintptr_t la_sparcv8_pltenter(Elf32_Sym *sym, uint_t ndx,
        uintptr_t *refcook, uintptr_t *defcook,
        La_sparcv8_regs *regs, uint_t *flags);
 
uintptr_t la_sparcv9_pltenter(Elf64_Sym *sym, uint_t ndx,
        uintptr_t *refcook, uintptr_t *defcook,
        La_sparcv9_regs *regs, uint_t *flags,
        const char *sym_name);
 
uintptr_t la_i86_pltenter(Elf32_Sym *sym, uint_t ndx,
        uintptr_t *refcook, uintptr_t *defcook,
        La_i86_regs *regs, uint_t *flags); 
uintptr_t la_amd64_pltenter(Elf64_Sym *sym, uint_t ndx,
        uintptr_t *refcook, uintptr_t *defcook,
        La_amd64_regs *regs, uint_t *flags, const char *sym_name);

sym, ndx, refcook, defcook and sym_name provide the same information as passed to la_symbind().

For la_sparcv8_pltenter() and la_sparcv9_pltenter(), regs points to the out registers. For la_i86_pltenter(), regs points to the stack and frame registers. For la_amd64_pltenter(), regs points to the stack and frame registers, and the registers used in passing integer arguments. regs are defined in /usr/include/link.h.

flags points to a data item that can convey information regarding the binding. This data item can be used to modify the continued auditing of this procedure linkage table entry. This data item is the same as pointed to by the flags from la_symbind()

The following flags can be supplied from la_pltenter() to alter the present auditing behavior. These flags are applied as a bitwise-inclusive OR with the value pointed to by the flags argument.

  • LA_SYMB_NOPLTENTERla_pltenter() is not be called again for this symbol.

  • LA_SYMB_NOPLTEXITla_pltexit() is not be called for this symbol.

The return value indicates the address to which control should be passed following this call. An audit library that monitors symbol binding should return the value of sym->st_value so that control is passed to the bound symbol definition. An audit library can intentionally redirect a symbol binding by returning a different value.

la_pltexit()

This routine is called when a procedure linkage table entry, between two objects that have been tagged for binding notification, returns. This routine is called before control reaches the caller.

uintptr_t la_pltexit(Elf32_Sym *sym, uint_t ndx, uintptr_t *refcook,
        uintptr_t *defcook, uintptr_t retval);

uintptr_t la_pltexit64(Elf64_Sym *sym, uint_t ndx, uintptr_t *refcook,
        uintptr_t *defcook, uintptr_t retval, const char *sym_name);

sym, ndx, refcook, defcook and sym_name provide the same information as passed to la_symbind(). retval is the return code from the bound function. An audit library that monitors symbol binding should return retval. An audit library can intentionally return a different value.


Note –

The la_pltexit() interface is experimental. See Audit Interface Limitations.


la_objclose()

This routine is called after any termination code for an object has been executed and prior to the object being unloaded.

uint_t la_objclose(uintptr_t *cookie);

cookie identifies the object, and was obtained from a previous la_objopen(). Any return value is presently ignored.

Audit Interface Example

The following simple example creates an audit library that prints the name of each shared object dependency loaded by the dynamic executable date(1).


$ cat audit.c
#include        <link.h>
#include        <stdio.h>
 
uint_t
la_version(uint_t version)
{
        return (LAV_CURRENT);
}
 
uint_t
la_objopen(Link_map *lmp, Lmid_t lmid, uintptr_t *cookie)
{
        if (lmid == LM_ID_BASE)
                (void) printf("file: %s loaded\n", lmp->l_name);
        return (0);
}
$ cc -o audit.so.1 -G -K pic -z defs audit.c -lmapmalloc -lc
$ LD_AUDIT=./audit.so.1 date
file: date loaded
file: /lib/libc.so.1 loaded
file: /lib/libm.so.2 loaded
file: /usr/lib/locale/en_US/en_US.so.2 loaded
Thur Aug  10 17:03:55 PST 2000

Audit Interface Demonstrations

A number of demonstration applications that use the rtld-audit interface are provided in the SUNWosdem package under /usr/demo/link_audit.

sotruss

This demo provides tracing of procedure calls between the dynamic objects of a named application.

whocalls

This demo provides a stack trace for a specified function whenever called by a named application.

perfcnt

This demo traces the amount of time spent in each function for a named application.

symbindrep

This demo reports all symbol bindings performed to load a named application.

sotruss(1) and whocalls(1) are included in the SUNWtoo package. perfcnt and symbindrep are example programs. These applications are not intended for use in a production environment.

Audit Interface Limitations

Limitations exist within the rtld-audit implementation. Take care to understand these limitation when designing an auditing library.

Exercising Application Code

An audit library receives information as objects are added to a process. At the time the audit library receives such information, the object being monitored might not be ready to execute. For example, an auditor can receive an la_objopen() call for a loaded object. However, the object must load its own dependencies and be relocated before any code within the object can be exercised. An audit library might want to inspect the loaded object by obtaining a handle using dlopen(3C). This handle can then be used to search for interfaces using dlsym(3C). However, interfaces obtained in this manner should not be called unless it is known that the initialization of the destination object has completed.

Use of la_pltexit()

There are some limitations to the use of the la_pltexit() family. These limitations stem from the need to insert an extra stack frame between the caller and callee to provide a la_pltexit() return value. This requirement is not a problem when calling just the la_pltenter() routines, as. In this case, any intervening stack can be cleaned up prior to transferring control to the destination function.

Because of these limitations, la_pltexit() should be considered an experimental interface. When in doubt, avoid the use of the la_pltexit() routines.

Functions That Directly Inspect the Stack

A small number of functions exist that directly inspect the stack or make assumptions of its state. Some examples of these functions are the setjmp(3C) family, vfork(2), and any function that returns a structure, not a pointer to a structure. These functions are compromised by the extra stack that is created to support la_pltexit().

The runtime linker cannot detect functions of this type, and thus the audit library creator is responsible for disabling la_pltexit() for such routines.