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.

The search for dependencies. Search paths can be substituted by the audit library.

Information regarding loaded objects.

Symbol bindings that occur between loaded objects. These bindings can be altered by the audit library.

The lazy binding mechanism that is provided by procedure linkage table entries, allow the auditing of function calls and their return values. See Procedure Linkage Table (Processor-Specific). The arguments to a function and return value of a function can be modified by the audit library.

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.

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.

The library must provide all dependency requirements.

The library should not use system interfaces that do not provide for multiple instances of the interface within a process.

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.

Local auditing is enabled by defining one or more auditors at the time the object is built. See Recording Local Auditors. The audit libraries that are made available at runtime by this method are provided with information regarding the dynamic objects that have requested local auditing.

Global auditing is enabled by defining one or more auditors using the environment variable LD_AUDIT. Global auditing can also be enabled for an application by combining a local auditing definition with the -z globalaudit option. See Recording Global Auditors. The audit libraries that are made available at runtime by these methods are provided with information regarding all dynamic objects used by the application.

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.

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.

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.

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.

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.

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.

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.

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

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.

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_NOPLTENTER – la_pltenter() is not be called again for this symbol.

LA_SYMB_NOPLTEXIT – la_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.

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.

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.