我有一个用 C++ 编写的程序,它使用 dlopen 加载动态库(Linux、i386、.so)。随后修改库文件时,我的程序往往会崩溃。这是可以理解的,因为大概文件只是映射到内存中。
我的问题是:除了简单地为自己创建文件的副本并将其删除之外,我是否有办法加载对后续修改安全的共享对象,或者从对我已加载的共享对象的修改中恢复的任何方法?
澄清:问题不是“我怎样才能安装一个新的库而不使程序崩溃”,而是“如果我无法控制的人正在复制库,我可以防御吗?”
如果您rm
在安装新库之前使用该库,我认为您的系统将保持分配的 inode、打开文件和运行程序。(当你的程序最终退出时,大部分隐藏但仍然存在的文件资源被释放。)
更新:好的,澄清后。动态链接器实际上通过将MAP_COPY
标志(如果可用)传递给mmap(2)
. 但是,MAP_COPY
在 Linux 上不存在,也不是计划中的未来功能。第二好的是MAP_DENYWRITE
,我相信加载器确实使用了它,它在 Linux API 中,以及 Linux 曾经做过的事情。它会在映射区域时出错。它仍然应该允许 rm 和替换。这里的问题是任何对文件具有读取权限的人都可以映射它并阻止写入,这会打开一个本地 DoS 漏洞。(考虑一下/etc/utmp
。有一个建议使用执行权限位来解决这个问题。)
你不会喜欢这个,但是有一个微不足道的内核补丁可以恢复MAP_DENYWRITE
功能。Linux 仍然具有该功能,它只是在mmap(2)
. 您必须在每个架构重复的代码中对其进行修补,对于 ia32,我相信该文件是arch/x86/ia32/sys_ia32.c
.
asmlinkage long sys32_mmap2(unsigned long addr, unsigned long len,
unsigned long prot, unsigned long flags,
unsigned long fd, unsigned long pgoff)
{
struct mm_struct *mm = current->mm;
unsigned long error;
struct file *file = NULL;
flags &= ~(MAP_EXECUTABLE | MAP_DENYWRITE); // fix this line to not clear MAP_DENYWRITE
只要您没有任何具有凭据的恶意本地用户,这应该没问题。它不是远程 DoS,只是本地 DoS。
如果您安装新版本的库,正确的过程是在同一目录中创建一个新文件,然后将其重命名为旧文件。旧文件将在打开时保留,并继续使用。
像 RPM 这样的包管理器会自动执行此操作 - 因此您可以在共享库和可执行文件运行时更新它们 - 但旧版本会继续运行。
如果您需要获取新版本,请重新启动进程或重新加载库 - 重新启动进程听起来更好 - 您的程序可以自行执行。甚至 init 也可以做到这一点。
如果他们具有文件写入权限,则无法防止有人覆盖您的库。
因为dlopen
内存映射库文件,所以对文件的所有更改在打开它的每个进程中都是可见的。
该dlopen
函数使用内存映射,因为它是使用共享库的最有效的内存方式。私有副本会浪费内存。
正如其他人所说,在 Unix 中替换共享库的正确方法是使用取消链接或重命名,而不是用新副本覆盖库。该install
命令将正确执行此操作。
如果您可以确定您的库映射到内存的位置,那么您可能能够将mprotect
其写入内存并对每一页进行简单的写入(例如读取和写回每页的第一个字节)。这应该让您获得每一页的私人副本。
如果 'mprotect' 不起作用(它可能不起作用,原始文件可能以只读方式打开),那么您可以将该区域复制到另一个位置,将该区域(使用mmap
)重新映射到一个私有的可写区域,然后复制该地区回来。
我希望操作系统具有“将此只读区域转换为写入时复制区域”。不过,我认为不存在这样的东西。
在任何这些情况下,仍然存在一个漏洞窗口 - 有人可以在 dlopen 调用初始化程序时或在您的重映射调用发生之前修改库。除非你能像@DigitalRoss 描述的那样修复动态链接器,否则你并不安全。
无论如何,谁在编辑你的图书馆?找到那个人,用煎锅打他的头。
这是一个有趣的问题。我讨厌在 Linux 中发现这样的漏洞,并且喜欢寻找修复它们的方法。
我的建议受到@Paul Tomblin 对这个关于 Linux 上临时文件的问题的回答的启发。这里的其他一些答案表明存在这种机制,但没有描述根据您的要求从客户端应用程序中利用它的方法。
我没有测试过这个,所以我不知道它会有多好。此外,可能存在与临时文件创建和取消链接之间的短暂时间段相关的竞争条件相关的轻微安全问题。此外,您已经注意到创建库副本的可能性,这就是我的提议。我对此的看法是,无论您实际将库打开多长时间,您的临时副本仅作为文件系统中的一个条目存在。
当您要加载库时,请执行以下步骤:
如果有一种非常简单的方法可以实现“复制文件”步骤,而无需您实际复制文件,那就太好了。我想到了硬链接,但我认为它不适用于这些目的。如果 Linux 有一个与 link() 一样易于使用的写时复制机制,那将是理想的,但我不知道有这样的工具。
编辑:@Zan Lynx 回答指出,如果将动态库复制到多个进程中,创建自定义副本可能会很浪费。因此,我的建议可能只有在明智地应用时才有意义——仅适用于那些有被踩踏风险的库(可能是所有库的一小部分,不包括 /lib 或 /usr/lib 中的文件)。