37.4.2 Audit API Callbacks

Each auditor provides one or more callbacks in the form of global functions that the dynamic linker may call. la_version is required of all auditors, but every other callback is optional. The auditor defines the callback if it wants those events.

Each auditor may associate private data with each shared object in the system via a word of storage in ld.so (as a uintptr_t, so type casting is always needed when storing pointers). There is one word of private data per shared object, per auditor.

This word may be an index into an array, a bitmask, or a pointer, and the address of it is passed to most callbacks. This word is initialized just before calling la_objopen (or, for the first object in a new namespace, la_activity), to be a pointer to the link map for the shared object. The auditor may choose to initialize it to something else (typically in la_objopen()) in order to store more or different data per object. There is no guaranteed way to free or clean up allocated data used for this purpose, but a reasonable option is to clean it up in la_objclose() and set the word to zero at that time. With that, all callbacks would need to check for a zero word and assume the related object has been or is being closed.

Function: unsigned int la_version (unsigned int version)

This mandatory callback is called when the auditor is loaded. It is passed the API version the dynamic linker is using. It should return the API version the module is using, or zero to silently unload the module. If the module and the dynamic linker are using incompatible API versions, the auditor is unloaded and an error is printed. The expectation is that the module will return the LAV_CURRENT value from link.h.

Note that la_version() may return values other than LAV_CURRENT if it has knowledge of what the various versions provide and can adapt to the version given, but doing this is outside the scope of this document. This may be complicated by LAV_CURRENT being different for different architectures.

Function: void la_activity (uintptr_t *privdata, unsigned int flag)

This callback is used to signal activity in a namespace. privdata points to the private data for a shared object in that namespace. The flag is one of the following:

LA_ACT_ADD

A shared object is being added to the namespace. Further additions are not signalled until the namespace is marked consistent again.

LA_ACT_DELETE

A shared object is being removed from the namespace. Further deletions will not be signalled until the namespace is marked consistent again.

LA_ACT_CONSISTENT

All adds or deletes are complete for this namespace. After this callback, the namespace is considered consistent, and further additions or deletions will cause another activity callback. Note that this callback will not happen if the namespace is empty.

Function: char * la_objsearch (const char *name, uintptr_t *privdata, unsigned int flag)

Called when the dynamic linker tries to locate a shared object to load it. name is the name of the object to be loaded. The callback must return name if it does not want to change the loader’s behavior. Otherwise, it may return a different string to change the object to be searched for. For LA_SER_ORIG, name is the name as requested, such as a DT_NEEDED entry (like libfoo.so.1) or the string passed to dlopen. If it contains a slash, no search is done and there are no further calls. For the other flags, it is the path of the file about to be tried (like /usr/lib/libfoo.so.1).

If the auditor returns an alternate string for this callback, the string must remain valid for the duration of the application. There is no mechanism for freeing any allocated memory. The alternate string will be passed to the remaining auditors in the audit chain, unless an auditor returns NULL, which terminates the chain. A NULL returned for LA_SER_ORIG causes that object to not be loaded at all, while a NULL for other flags causes that path to be skipped.

The privdata points to the private data for the object doing the search. The flag is one of the following:

LA_SER_ORIG

The dynamic linker is starting a search for name, usually via a call to dlopen() or a DT_NEEDED entry. A callback with this flag will happen before a callback with any of the other flags, which indicate progress through the search.

LA_SER_LIBPATH

The dynamic loader is searching for name in one of the paths specified by LD_LIBRARY_PATH.

LA_SER_RUNPATH

The dynamic loader is searching for name in one of the paths specified by DT_RPATH or DT_RUNPATH.

LA_SER_CONFIG

The dynamic loader is trying a candidate for name in the paths stored in ld.so.cache.

LA_SER_DEFAULT

The dynamic loader is searching for name in one of the default paths hardcoded in the dynamic linker itself.

LA_SER_SECURE

Unused.

Function: unsigned int la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *privdata)

Called when a shared object is loaded, including the main, ld.so, and vDSO objects. Unlike la_activity this callback is called once for each object. map is the link map for the object. The privdata points to the private data for the object being loaded, is initialized to map before this callback7, and may be initialized to point to auditor-specific data by this callback. lmid is the namespace the object was loaded into, either LM_ID_BASE for the base namespace, or some other value for namespaces created with dlmopen().

The value returned by this callback is a bitmask of zero or more of these flags:

LA_FLG_BINDTO

Audit symbols bound to this object.

LA_FLG_BINDFROM

Audit symbols bound from this object.

Function: unsigned int la_objclose (uintptr_t *privdata)

Called when a shared object is unloaded by dlclose, and at process exit, after its destructors have run, for every non-auditor object that is still loaded (except the vDSO). This is called after la_activity signals its deletion. The return value is ignored.

Function: void la_preinit (uintptr_t *privdata)

This callback is called after ELF constructors, just before main is called. The privdata always points to the private data for the main application.

Function: uintptr_t la_symbind32 (Elf32_Sym *sym, unsigned int ndx, uintptr_t *refdata, uintptr_t *defdata, unsigned int *flags, const char *symname)
Function: uintptr_t la_symbind64 (Elf64_Sym *sym, unsigned int ndx, uintptr_t *refdata, uintptr_t *defdata, unsigned int *flags, const char *symname)

Called when a binding occurs between the reference to a symbol symname and its definition sym. refdata points to the private data for the object referencing the symbol, and defdata points to the private data for the object providing the definition of the symbol. ndx is the index of the symbol in the symbol table of the shared object in which it is defined.

This callback will be called only for symbols in objects where la_objopen returned a nonzero value. It will also only be called for symbols that represent a function call and use the PLT for that call, and for queries from dlsym/dlvsym. For dlsym/dlvsym, either LA_FLG_BINDFROM must have been returned for the referencing object, or LA_FLG_BINDTO for the defining object. For all other cases, both flags must be set.

This callback is actually one of two callbacks, either la_symbind32 or la_symbind64, with corresponding Elf32_Sym or Elf64_Sym parameters. Both callbacks function the same other than the symbol size. The auditor may define either or both, but if both, only one is ever called—the one that corresponds to the application’s architecture.

flags points to a bitmask which may include any of the following:

LA_SYMB_NOPLTENTER
LA_SYMB_NOPLTEXIT

The auditor may add these flags to *flags to request skipping callbacks for the architecture-specific la_pltenter and/or la_pltexit events. Note that to avoid the performance overhead associated with the PLT callback hooks, all auditors must have neither an la_pltenter nor an la_pltexit callback.

LA_SYMB_STRUCTCALL

Unused.

LA_SYMB_DLSYM

The symbol is being bound due to a dlsym() or dlvsym() call.

LA_SYMB_ALTVALUE

The symbol’s value has been altered by an earlier auditor’s la_symbind callback.

The value returned by la_symbind() is used as the address of the symbol. The auditor should return sym->st_value if it does not wish to modify the application’s behavior.

Function: uintptr_t la_pltenter (…)

This “callback” represents one of many architecture-defined callbacks listed in link.h, refer to that for specifics. The parameters are similar to la_symbind, with the addition of one or more parameters for passing register contents. For example, the x86-64 prototype is:

Elf64_Addr la_x86_64_gnu_pltenter (Elf64_Sym *sym,
    unsigned int ndx, uintptr_t *refdata, uintptr_t *defdata,
    La_x86_64_regs *regs, unsigned int *flags,
    const char *symname, long int *framesizep);

Note that decoding the arguments is very dependent on the platform and compiler flags, and is outside the scope of this document.

The la_pltenter callback is called when a caller is about to call a function through a PLT entry which is lazily bound, if the caller and callee objects have been marked for auditing by la_objopen(). The callback returns the address to actually call, or sym->st_value if it does not wish to modify the call. Note that the PLT remains lazily bound for the life of the program if this callback is defined.

Calls are not traced if the PLT is bound at load time with LD_BIND_NOW=1, or if the code does not use the PLT at all (for example, when built with -fno-plt). Linking with -z now does not prevent tracing, because the dynamic linker keeps lazy binding when an auditor defines la_pltenter or la_pltexit.

*framesizep is initially -1, which means la_pltexit is not wanted. To receive la_pltexit, set it to the number of bytes of the caller’s stack (the arguments passed on the stack) that must be copied for the call. The dynamic linker calls the function from its own frame, so that the function returns to the dynamic linker, and copies that many bytes into that frame. If the value is too small, the function reads garbage for its stack arguments; 0 is fine for functions with no stack arguments. If several auditors set it, the largest value is used.

Function: unsigned int la_pltexit (…)

Architecture-specific function similar to la_pltenter. This callback is called when a function, called through the PLT, returns to the caller. It will only be called if la_pltenter specified a frame size for it. The parameters are architecture-dependent, but all include an in-registers parameter, which contains the original registers at the time of la_pltenter, and an out-registers parameter, which contains the return value of the function, which the auditor may modify. For example, the x86-64 function is:

unsigned int la_x86_64_gnu_pltexit (Elf64_Sym *sym,
    unsigned int ndx, uintptr_t *refdata, uintptr_t *defdata,
    const La_x86_64_regs *inregs, La_x86_64_retval *outregs,
    const char *symname);

Footnotes

(7)

As if this callback did *privdata = (uintptr_t) map