使用dlopen,我如何处理我加载的库文件的更改?

14

我有一个用C ++编写的程序,使用dlopen加载动态库(Linux、i386、.so)。当库文件随后被修改时,我的程序往往会崩溃。这可以理解,因为文件可能只是映射到内存中。

我的问题是:除了简单地创建文件的副本并dlopen它之外,是否有一种安全的方式来加载共享对象,以防止随后的修改,或者有没有办法从我已经加载的共享对象的修改中恢复过来?

澄清:问题不是“如何安装新库而不使程序崩溃”,而是“如果我无法控制某些人复制库文件,是否有可能对此进行防御?”

5个回答

19
如果在安装新库之前使用rm命令删除旧库,那么系统会保留已分配的inode、打开的文件以及正在运行的程序。(当程序最终退出时,大多数隐藏但仍存在的文件资源将被释放。)更新:好的,在澄清之后。动态链接器实际上通过向mmap(2)传递MAP_COPY标志(如果可用)完全“解决”了这个问题。然而,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攻击,而只是本地的攻击。


7
如果您安装了一个新版本的库,正确的做法是在相同的目录下创建一个新文件,然后将其重命名为旧文件。旧文件在打开时会保留,并继续被使用。
像RPM这样的软件包管理器会自动执行此操作 - 因此您可以在运行共享库和可执行文件时进行更新 - 但旧版本仍然会继续运行。
在需要使用新版本的情况下,重新启动进程或重新加载库 - 重新启动进程听起来更好 - 您的程序可以执行自身。即使init也可以做到这一点。

4

如果某人拥有文件写入权限,那么防止其覆盖您的库是不可能的。

由于 dlopen 函数将内存映射到库文件中,因此对文件进行的所有更改都会在打开它的每个进程中可见。

dlopen 函数使用内存映射,因为这是使用共享库最节省内存的方式。私有副本会浪费内存。

正如其他人所说,在Unix中替换共享库的正确方法是使用 unlink 或 rename,而不是直接用新副本覆盖库文件。相应命令install可以正确地执行此操作。


2
这是一个有趣的问题。我讨厌在Linux中发现这样的漏洞,但喜欢找到解决它们的方法。
我的建议受到@Paul Tomblin对关于Linux上临时文件的问题的回答的启发。这里的一些其他答案已经提出了这种机制的存在,但没有描述如何像您请求的那样从客户端应用程序利用它的方法。
我没有测试过这个方法,所以不知道它的效果如何。此外,可能存在与创建临时文件和取消链接之间短暂时间内的竞争条件相关的轻微安全问题。此外,您已经注意到可能会创建库的副本,这就是我提出的建议。我的想法是,您的临时副本只存在于文件系统中的瞬间,而不管您实际打开库的时间长短。
当您想要加载一个库时,请按照以下步骤进行:
将文件复制到临时位置,可能从mkstemp()开始。使用dlopen()加载库的临时副本。unlink() 临时文件。然后按照正常方式继续进行,当您dlclose()时,文件的资源将自动删除。
如果有一种真正简单的方法可以在不需要实际复制文件的情况下实现“复制文件”步骤,那就太好了。硬链接是一个选择,但我认为它在这些目的上不起作用。如果Linux有一个像link()一样易于使用的写时复制机制,那就太理想了,但我不知道是否有这样的设施。
编辑:@Zan Lynx的答案指出,如果动态库被复制到多个进程中,则创建自定义副本可能会浪费资源。因此,我的建议可能只有在谨慎应用时才有意义 - 只针对那些有被覆盖风险的库(假定该子集不包括/lib或/usr/lib中的文件)。

哼,为什么这个在列表中排得这么靠后。其他回复都是处理由系统管理的共享库,而不是通过dlopen()管理的。正如这个回复指出的那样,如果你想要两次重新加载相同的文件名,你就有点没戏了,至少从我的测试结果来看是这样的… - Leif Hedstrom

1
如果你能够确定库被映射到了内存的哪个位置,那么你就可以使用mprotect将其设置为可写,并对每个页面进行简单的写入(例如读取并写回每个页面的第一个字节)。这样应该会得到每个页面的私有副本。
如果'mprotect'不起作用(可能不起作用,因为原始文件可能是只读打开的),那么您可以将该区域复制到另一个位置,将该区域重新映射(使用mmap)到私有的可写区域,然后将该区域复制回来。
我希望操作系统有一个“将此只读区域转换为按需复制区域”的功能。不过我认为这样的东西并不存在。
在所有这些情况中,仍然存在一个漏洞窗口-某人可以在dlopen调用初始化程序之前或您的重映射调用发生之前修改库。除非您可以像@DigitalRoss所描述的那样修复动态链接器,否则您并不真正安全。
谁在篡改你的库?找到那个人,用平底锅打他的头。

1
谢谢您的建议 - 不幸的是,如果我经常煎炒别人,我会陷入很多麻烦 :-) - kdt

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接