实际上,几乎所有程序都依赖库来执行。在大多数现代类 Unix 系统(包括 Linux)中,程序默认编译为使用动态链接库 (DLL)。这样,您可以更新一个库,并且所有使用该库的程序都将使用新的(希望是改进的)版本(如果它们可以的话)。
动态链接库通常放置在一个或几个特殊目录中。常用的目录包括/lib, /usr/lib, /lib/security用于 PAM 模块,/usr/X11R6/lib用于 X-windows,以及/usr/local/lib。您应该在程序中使用这些标准约定,特别是在调试期间之外,您不应使用从当前目录计算的值作为动态链接库的来源(攻击者可能能够添加他们自己选择的“库”值)。
对于库的命名和为它们设置符号链接,有一些特殊的约定,这样做的结果是,您可以更新库,并且仍然支持想要使用这些库的旧的、不向后兼容的版本的程序。还有一些方法可以在执行特定程序时覆盖特定的库,甚至只是库中的特定函数。这是类 Unix 系统相对于类 Windows 系统的真正优势;我认为类 Unix 系统在处理库更新方面拥有更好的系统,这也是 Unix 和 Linux 系统被认为比基于 Windows 的系统更稳定的原因之一。
在基于 GNU glibc 的系统(包括所有 Linux 系统)上,程序启动期间自动搜索的目录列表存储在文件 /etc/ld.so.conf 中。许多基于 Red Hat 的发行版通常不包含/usr/local/lib在文件/etc/ld.so.conf中。我认为这是一个错误,并且将/usr/local/lib添加到/etc/ld.so.conf是运行许多基于 Red Hat 的系统上的程序所需的常见“修复”。如果您只想覆盖库中的几个函数,但保留库的其余部分,您可以在/etc/ld.so.preload中输入覆盖库(.o 文件)的名称;这些“预加载”库将优先于标准集。此预加载文件通常用于紧急补丁;发行版在交付时通常不会包含此类文件。在程序启动时搜索所有这些目录会非常耗时,因此实际上使用了缓存安排。程序 ldconfig(8) 默认读取文件 /etc/ld.so.conf,在动态链接目录中设置适当的符号链接(以便它们遵循标准约定),然后将缓存写入 /etc/ld.so.cache,然后供其他程序使用。因此,每当添加 DLL、删除 DLL 或 DLL 目录集更改时,都必须运行 ldconfig;运行 ldconfig 通常是包管理器在安装库时执行的步骤之一。然后在启动时,程序使用动态加载器读取文件 /etc/ld.so.cache,然后加载它需要的库。
各种环境变量可以控制此过程,实际上存在一些环境变量允许您覆盖此过程(因此,例如,您可以为特定执行临时替换不同的库)。在 Linux 中,环境变量 LD_LIBRARY_PATH 是一个冒号分隔的目录集,库首先在其中搜索,然后在标准目录集中搜索;这在调试新库或为特殊目的使用非标准库时很有用,但请确保您信任那些可以控制这些目录的人。变量 LD_PRELOAD 列出了具有覆盖标准集的函数的对象文件,就像 /etc/ld.so.preload 一样。变量 LD_DEBUG 显示调试信息;如果设置为“all”,则在动态链接过程发生时会显示大量信息。
如果不采取特殊措施,允许用户控制动态链接库对于 setuid/setgid 程序来说将是灾难性的。因此,在 GNU glibc 实现中,如果程序是 setuid 或 setgid,则这些变量(和其他类似变量)将被忽略或在它们可以执行的操作中受到极大限制。GNU glibc 库通过检查程序的凭据来确定程序是否是 setuid 或 setgid;如果 UID 和 EUID 不同,或者 GID 和 EGID 不同,则库假定程序是 setuid/setgid(或源自 setuid/setgid 程序),因此大大限制了其控制链接的能力。如果您加载 GNU glibc 库,您可以看到这一点;特别是参见文件 elf/rtld.c 和 sysdeps/generic/dl-sysdep.c。这意味着如果您使 UID 和 GID 等于 EUID 和 EGID,然后调用程序,这些变量将具有完全效果。其他类 Unix 系统以不同的方式处理这种情况,但出于相同的原因:setuid/setgid 程序不应受到环境设置的环境变量的不当影响。请注意,图形用户界面工具包通常允许用户控制动态链接库,因为直接调用图形用户界面工具包的可执行文件永远不应该设置为 setuid(或具有其他特殊权限)。有关如何开发安全的 GUI 应用程序的更多信息,请参阅第 7.4.4 节。
对于 Linux 系统,您可以从我的文档 程序库 HOWTO 中获取更多信息。