链接程序和库指南

运行时链接程序审计接口

进程可以使用 rtld-audit 接口访问与其自身相关的运行时链接信息。配置共享库中介绍对共享库的运行时配置即是使用此机制的一个示例。

rtld-audit 接口实现为提供一个或多个审计接口例程的审计库。如果将该库作为进程的一部分装入,则运行时链接程序会在进程执行的不同阶段调用审计例程。审计库可以使用这些接口访问以下各项:

通过预装入专用的共享库可以实现其中的部分功能。但是,预装入的目标文件与进程目标文件存在于同一名称空间内。这种预装入通常会限制预装入的共享库的实现或者使实现变得更为复杂。 rtld-audit 接口会为用户提供唯一的名称空间,用于在其中执行其审计库。此名称空间可确保在进程内进行正常绑定时审计库不会侵入。

建立名称空间

运行时链接程序将动态可执行文件与其依赖项绑定时,会生成链接映射的链接列表,用于对此进程进行说明。/usr/include/sys/link.h 中定义的链接映射结构说明了进程内的每个目标文件。绑定应用程序的目标文件所需的符号搜索机制会遍历此链接映射的列表。此链接映射列表用于提供进程符号解析的名称空间

运行时链接程序也通过链接映射来进行说明。此链接映射以不同于应用程序目标文件列表的列表中进行维护。因此,运行时链接程序驻留在其自己唯一的名称空间中,从而可防止以任何方式将应用程序直接绑定到运行时链接程序内的服务。应用程序只能通过过滤器 libc.so.1libdl.so.1 调用运行时链接程序的公共服务。

rtld-audit 接口使用自己的用于维护审计库的链接映射列表。因此,在应用程序的的符号绑定要求中,不涉及审计库。通过 dlmopen(3C) 可检查应用程序链接映射列表。将 RTLD_NOLOAD 标志用于 dlmopen(3C) 时,审计库可以在不装入目标文件的情况下查询此目标文件是否存在。

/usr/include/link.h 中定义了两个标识符,用于定义应用程序和运行时链接程序的链接映射列表:

#define LM_ID_BASE      0     /* application link-map list */

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

针对每个 rtld-audit 支持库会指定一个唯一的新链接映射标识符。

创建审计库

审计库的生成方式与其他任何共享库的生成方式相同。但是,必须注意进程内审计库名称空间的唯一性。

如果审计库调用 printf(3C),则审计库必须定义与 libc 之间的依赖性。请参见生成共享库输出文件。由于审计库具有唯一的名称空间,因此,所审计的应用程序中提供的 libc 无法满足符号引用。如果审计库依赖于 libc,则会向进程中装入两种版本的 libc.so.1。一种版本用于满足应用程序链接映射列表的绑定要求,另一种版本用于满足审计链接映射列表的绑定要求。

要确保生成的审计库会记录所有的依赖项,请使用链接编辑器的 -z defs 选项。

部分系统接口会假定其是进程内实现的唯一实例,例如信号和 malloc(3C)。审计库应该避免使用此类接口,因为这样做可能会无意中更改应用程序的行为。


注 –

审计库可以使用 mapmalloc(3MALLOC) 来分配内存,因为此分配方法可以与应用程序通常使用的任何分配方案同时存在。


调用审计接口

rtld-audit 接口可通过以下两种方法之一来启用。每种方法都会指示一个所审计的目标文件的范围。

任一调用方法都包含一个字符串,其中包含通过 dlopen(3C) 装入的以冒号分隔的共享库列表。每个目标文件都装入各自的审计链接映射列表中。使用 dlsym(3C) 可搜索每个目标文件中的审计例程。在应用程序执行过程中的不同阶段会调用找到的审计例程。

通过 rtld-audit 接口可以提供多个审计库。希望以此方式使用的审计库不应更改通常由运行时链接程序返回的绑定。更改这些绑定会在后面的审计库中产生意外结果。

安全应用程序只能从受信任的目录中获取审计库。缺省情况下,用于 32 位目标文件的运行时链接程序可识别的受信任的目录仅有 /lib/secure/usr/lib/secure。对于 64 位目标文件,受信任的目录是 /lib/secure/64/usr/lib/secure/64

记录局部审计程序

使用链接编辑器选项 -p-P 生成目标文件时,可以确定局部审计要求。例如,要使用审计库 audit.so.1 审计 libfoo.so.1,请在链接编辑时使用 -p 选项记录要求:


$ cc -G -o libfoo.so.1 -Wl,-paudit.so.1 -K pic foo.c

$ dump -Lv libfoo.so.1 | fgrep AUDIT

[3]    AUDIT      audit.so.1

在运行时,如果存在此审计标识符,则会装入审计库并将与标识目标文件相关的信息传递到此审计库。

如果单独使用此机制,则在装入审计库之前会显示搜索标识目标文件之类的信息。要提供尽可能多的审计信息,需要将存在的要求局部审计的目标文件传播给此目标文件的用户。例如,如果生成的应用程序依赖于 libfoo.so.1,则会对此应用程序进行标识,指明其依赖项需要审计:


$ cc -o main main.c libfoo.so.1

$ dump -Lv main | fgrep AUDIT

[5]    DEPAUDIT   audit.so.1

通过此机制启用的审计会导致向审计库中传递与所有应用程序显式依赖项有关的信息。使用链接编辑器的 -P 选项,还可以在创建目标文件时直接记录此依赖项审计:


$ cc -o main main.c -Wl,-Paudit.so.1

$ dump -Lv main | fgrep AUDIT

[5]    DEPAUDIT   audit.so.1

注 –

通过将环境变量 LD_NOAUDIT 设置为非空值,可以在运行时禁用审计。


审计接口函数

rtld-audit 接口提供了以下函数。这些函数按照其预期的使用顺序进行说明。


注 –

为了简化讨论,对体系结构或目标文件类特定接口的引用会缩减为其通用名称。例如,对 la_symbind32()la_symbind64() 的引用会指定为 la_symbind()


la_version()

此函数可提供运行时链接程序与审计库之间的初次握手。必须提供此接口才能装入审计库。

uint_t la_version(uint_t version);

运行时链接程序通过其可以支持的 version 最高的 rtld-audit 接口来调用此接口。审计库可以检验此版本是否足以供其使用,并返回审计库预期使用的版本。此版本通常为 /usr/include/link.h 中定义的 LAV_CURRENT

如果审计库返回零,或者返回的版本高于运行时链接程序所支持的 rtld-audit 接口的版本,则会废弃该审计库。

la_activity()

此函数可通知审计程序正在进行链接映射活动。

void la_activity(uintptr_t * cookie, uint_t flags);

cookie 标识作为链接映射标题的目标文件。flags 表示活动类型,如 /usr/include/link.h 中所定义:

  • LA_ACT_ADD-正在向链接映射列表中添加目标文件。

  • LA_ACT_DELETE-正在从链接映射列表中删除目标文件。

  • LA_ACT_CONSISTENT-已经完成目标文件活动。

la_objsearch()

此函数可通知审计程序将要搜索目标文件。

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

name 表示所搜索的文件名或路径名。cookie 标识启动搜索的目标文件。flags 标识 name 的来源和创建方式,如 /usr/include/link.h 中所定义:

  • LA_SER_ORIG-初始搜索名称。通常,此名称表示记录为 DT_NEEDED 项的文件名或者提供给 dlopen(3C) 的参数。

  • LA_SER_LIBPATH-已经通过 LD_LIBRARY_PATH 组件创建了路径名。

  • LA_SER_RUNPATH-已经通过 运行路径 组件创建了路径名。

  • LA_SER_DEFAULT-已经通过缺省搜索路径组件创建了路径名。

  • LA_SER_CONFIG-路径组件源自配置文件。请参见crle(1)

  • LA_SER_SECURE-路径组件特定于安全目标文件。

返回值会指明运行时链接程序应该继续处理的搜索路径名。值为零表示应该忽略此路径。监视搜索路径的审计库会返回 name

la_objopen()

此函数在运行时链接程序装入新目标文件时调用。

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

lmp 提供说明新目标文件的链接映射结构。lmid 标识添加了目标文件的链接映射列表。cookie 提供指向某个标识符的指针。此标识符会初始化为目标文件 lmp。审计库可以修改此标识符,以便更好地标识其他 rtld-audit 接口例程的目标文件。

la_objopen() 函数会返回表示与此目标文件相关的符号绑定的值。返回值是 /usr/include/link.h 中定义的以下值的掩码:

  • LA_FLG_BINDTO-审计此目标文件的符号绑定。

  • LA_FLG_BINDFROM-审计来自此目标文件的符号绑定。

通过这些值,审计程序可以选择要使用 la_symbind() 监视的目标文件。返回值为零表示绑定信息与此目标文件无关。

例如,审计程序可以监视从 libfoo.solibbar.so 的绑定。将 la_objopen() 用于 libfoo.so 会返回 LA_FLG_BINDFROM。将 la_objopen()用于 libbar.so 会返回 LA_FLG_BINDTO

审计程序可以监视 libfoo.solibbar.so 之间的所有绑定。将 la_objopen() 用于这两个目标文件会返回 LA_FLG_BINDFROMLA_FLG_BINDTO

审计程序可以监视到 libbar.so 的所有绑定。将 la_objopen() 用于 libbar.so 会返回 LA_FLG_BINDTO。所有 la_objopen() 调用都会返回 LA_FLG_BINDFROM

la_objfilter()

此函数在过滤器装入新的 filtee 时调用。请参见作为过滤器的共享库

int la_objfilter(uintptr_t * fltrcook, const char * fltestr,

        uintptr_t * fltecook, uint_t flags);

fltrcook 标识过滤器。fltestr 指向 filtee 字符串。fltecook 标识 filtee。flags 当前未使用。对于过滤器和 filtee,la_objfilter()la_objopen() 之后调用。

值为零表示应该忽略此 filtee。监视过滤器使用情况的审计库会返回非零值。

la_preinit()

为应用程序装入所有目标文件之后但在将控制权转交给应用程序之前,会调用一次此函数。

void la_preinit(uintptr_t * cookie);

cookie 标识启动进程的主目标文件,通常为动态可执行文件。

la_symbind()

在已经通过 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 是构造的符号结构,其 sym->st_value 表示所绑定的符号定义的地址。 请参见 /usr/include/sys/elf.hla_symbind32() 可将 sym->st_name 调整为指向实际符号名称。la_symbind64() 可保留 sym->st_name 作为绑定目标文件字符串表的索引。

ndx 表示绑定目标文件的动态符号表内的符号索引。refcook 标识引用此符号的目标文件。此标识符与传递给 la_objopen() 函数的标识符相同,此函数会返回 LA_FLG_BINDFROMdefcook 标识定义此符号的目标文件。此标识符与传递给 la_objopen() 的标识符相同,此函数会返回 LA_FLG_BINDTO

flags 指向可以传送与绑定相关的信息的数据项。此数据项还可用于修改对此过程链接表项的继续审计。该值是 /usr/include/link.h 中定义的符号绑定标志的掩码。

可以为 la_symbind() 提供以下标志:

  • LA_SYMB_DLSYM-由于调用 dlsym(3C) 而发生的符号绑定。

  • LA_SYMB_ALTVALUE (LAV_VERSION2)-通过先前调用 la_symbind() 为符号值返回替换值。

如果 la_pltenter()la_pltexit() 函数存在,则对于过程链接表的各项,这些函数在 la_symbind() 之后调用。每次引用符号时都会调用这些函数。 另请参见审计接口限制

la_symbind() 可以提供以下标志来更改此缺省行为。这些标志可应用于按位或运算(包含边界值),并通过 flags 参数来指示其值。

  • LA_SYMB_NOPLTENTER请勿针对此符号调用 la_pltenter() 函数。

  • LA_SYMB_NOPLTEXIT请勿针对此符号调用 la_pltexit() 函数。

返回值表示在此调用后将控制权传递到的地址。监视符号绑定的审计库应该返回值 sym->st_value,以便将控制权传递给绑定符号定义。审计库可以通过返回不同的值对符号绑定进行专门重定向。

sym_name 仅适用于 la_symbind64(),其中包含所处理的符号的名称。对于 32 位接口,可在 sym->st_name 字段中使用此名称。

la_pltenter()

这些函数是系统特定的。调用过程链接表中位于已经标记用于绑定通知的两个目标文件之间的一项时,会调用这些函数。

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);

symndxrefcookdefcooksym_name 提供的信息与传递给 la_symbind() 的信息相同。

对于 la_sparcv8_pltenter()la_sparcv9_pltenter()regs 指向输出寄存器。对于 la_i86_pltenter()regs 指向栈寄存器和帧寄存器。对于 la_amd64_pltenter()regs 指向栈寄存器、帧寄存器以及用于传递整数参数的寄存器。regs/usr/include/link.h 中定义。

flags 指向可以传送与绑定相关的信息的数据项。此数据项可用于修改对此过程链接表项的继续审计。此数据项与 la_symbind() 中的 flags 指向的数据项相同。

la_pltenter() 可以提供以下标志来更改当前的审计行为。这些标志可应用于按位或运算(包含边界值),并通过 flags 参数来指示其值。

  • LA_SYMB_NOPLTENTER不能针对此符号再次调用 la_pltenter()

  • LA_SYMB_NOPLTEXIT不能针对此符号再次调用 la_pltexit()

返回值表示在此调用后将控制权传递到的地址。监视符号绑定的审计库应该返回值 sym->st_value,以便将控制权传递给绑定符号定义。审计库可以通过返回不同的值对符号绑定进行专门重定向。

la_pltexit()

返回过程链接表中位于已经标记用于绑定通知的两个目标文件之间的一项时,会调用此函数。此函数在调用方获取控制权之前调用。

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);

symndxrefcookdefcooksym_name 提供的信息与传递给 la_symbind() 的信息相同。retval 是绑定函数的返回代码。监视符号绑定的审计库应该返回 retval。审计库可以专门返回不同的值。


注 –

la_pltexit() 接口是实验接口。请参见审计接口限制


la_objclose()

此函数在执行目标文件的任何终止代码之后和卸载目标文件之前调用。

uint_t la_objclose(uintptr_t * cookie);

cookie 标识目标文件,并从先前的 la_objopen() 中获取。当前会忽略任何返回值。

审计接口示例

以下简单示例创建了一个审计库,其中列显了动态可执行文件 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

审计接口演示

/usr/demo/link_audit 下的 SUNWosdem 软件包中提供了许多使用 rtld-audit 接口的演示应用程序:

sotruss

此演示跟踪指定的应用程序的动态库之间的过程调用。

whocalls

此演示跟踪指定的应用程序每次调用指定函数时所用栈。

perfcnt

此演示跟踪指定的应用程序的每个函数执行时间。

symbindrep

此演示报告为装入指定的应用程序而执行的所有符号绑定。

sotruss(1)whocalls(1) 包括在 SUNWtoo 软件包中。perfcntsymbindrep 是程序示例。这些应用程序不适用于生产环境。

审计接口限制

使用 la_pltexit() 系列存在一些限制。这些限制是由于需要在调用方和被调用方之间插入额外栈帧,以提供 la_pltexit() 返回值。此要求在仅调用 la_pltenter() 例程时不会产生问题。在这种情况下,可以在将控制权转交给目标函数之前清除任何干预栈。

由于存在这些限制,因此应该将 la_pltexit() 视为实验接口。在不确定的情况下,请避免使用 la_pltexit() 例程。

直接检查栈的函数

有少量函数可以直接检查栈或对其状态做出假设。这些函数的一些示例包括 setjmp(3C) 系列、vfork(2) 以及返回结构而不是指向结构的指针的任何函数。为支持 la_pltexit() 而创建的额外栈会破坏这些函数。

由于运行时链接程序无法检测此类型的函数,因此,审计库创建者会负责针对此类例程禁用 la_pltexit()