自行卸载的 Linux 共享库

Posted

技术标签:

【中文标题】自行卸载的 Linux 共享库【英文标题】:Linux shared library that unloads itself 【发布时间】:2019-05-15 12:37:46 【问题描述】:

我的目标是在位于特定路径的特定进程组中挂钩几个 glibc 函数。有多种方法可以做到这一点(gdb、strace 等)。但我需要在最早阶段自动可靠地制作它,这样我就不会错过任何一个电话。所以我决定使用LD_PRELOAD 方法。而且我需要它在没有任何用户干预的情况下自动完成,因此将LD_PRELOAD 注入环境有点脆弱,可能会被用户覆盖。所以我决定在 /etc/ld.so.preload 中指定我的库 - 这很好用。

在我的库 ctor 代码中,我检查我所在的进程是否是我需要的进程并进行必要的挂钩,否则它是无操作的,库只是一个死重。

__attribute__((constructor)) void my_lib_ctor()

  if (is_relevant_process())
  
    do_the_wiring();
  

该库不导出任何符号,并且默认情况下隐藏其中的所有内容(-fvisibility=hidden 编译器标志),因此任何进程对我的库没有任何真正的依赖关系。因此,如果注入到无关的进程中,它可以安全地卸载。

更新过程需要卸载 - 如果要更新库,则不应将其加载到长时间运行的进程中 - 否则它们将在库替换时崩溃(这是预期的)。感兴趣的过程是短暂的并且是由用户启动的,因此对它们的影响可以忽略不计。

问题是 - 我不知道如何安全卸载它。我在考虑dlclose,但是从库本身调用它会导致它从调用返回到已经卸载的库代码。

如果有其他方法可以自动且可靠地在早期(在应用程序的main() 执行之前)挂钩 glibc 调用而不触及感兴趣的应用程序本身,我会很高兴如果你让我知道它们(操作系统配置的轻微修改,例如预加载,是可以的)。谢谢!

【问题讨论】:

两阶段方法?为 LD_PRELOAD 使用一个小的、稳定的库,然后从那里加载大的。 另外,为什么更新库会崩溃,无论它加载了什么?运行代码保持旧文件打开,新代码获取新文件。 否则它们将在库替换时崩溃(这是预期的) 仅当您的更新过程覆盖现有库时。先删除吧。 Arkadiy,Andrew,我尝试先删除它,但它还是会导致进程崩溃 将新的库版本写入临时文件(在同一个磁盘上)并将其移动到正确的位置。应该没有崩溃的风险(或任何人无法加载它)。 【参考方案1】:

我认为你有一些误解,你所要求的只是没有意义:

在我的库 ctor 代码中,我检查我所在的进程是否是我需要的进程并进行必要的挂钩,否则它是无操作的,库只是一个死重。

还有卸载它的工作,而不是让它无所事事,它更多重量。

更新过程需要卸载 - 如果要更新库,则不应将其加载到长时间运行的进程中 - 否则它们将在库替换时崩溃(这是预期的)。

只有当您错误地覆盖库文件而不是替换文件时,才会发生这种情况。后者可以通过将临时文件安装到同一目录并以旧文件为目标执行rename 函数(等效于mv 命令)来执行。

如果还有其他方法可以在早期自动可靠地挂钩 glibc 调用

您对“可靠”的关注听起来非常接近想要使用这种机制以潜在的恶意应用程序无法绕过的方式实施策略/访问控制。这在图书馆级别根本不可能。您需要在某种真正的沙箱中运行它们才能实现这一点(例如,通过将文件替换为在适当的沙箱中调用它们的包装脚本)。

【讨论】:

我尝试在放置更新之前取消链接/删除原始库 - 仍然使进程崩溃。我可以诊断崩溃的实际原因是什么 - 访问已删除的映射文件或其他原因? gdb 应该能够看到它,只要进程仍在运行。您还可以保留指向旧文件的硬链接,甚至只是一个副本。 @hydrechan 所有 linux 发行版上的正常升级过程都涉及替换进程正在使用的库,并且它从未崩溃过(旧的库被文件系统驱动程序作为匿名 inode 保留,直到它不再使用)。你的库中发生了一些可疑的事情,你应该调试它。 我试图创建一个测试循环,它会一遍又一遍地更新,并且使用任何一种方法(rm+cp、cp+mv)都不会崩溃。但这是人为的情况,因为我正在用相同的副本替换旧文件。我将构建该库的多个版本,并尝试在循环中以高速随机替换版本。

以上是关于自行卸载的 Linux 共享库的主要内容,如果未能解决你的问题,请参考以下文章

干净地卸载共享库并使用 Python CFFI 重新开始

Linux,共享库使用主程序中的函数而不是其他共享库

在 linux 中使用另一个共享库构建共享库

linux 静态库共享库

如何将共享库链接到Linux中的其他共享库?

Linux之静态库共享库