LWN:如何确定采用dlopen()方式引入了哪些依赖!

文章讨论了systemd项目考虑使用dlopen()加载可选依赖以减少攻击面,但此举可能导致依赖追踪困难。LennartPoettering的提议引发了关于类型安全、依赖关系透明度和新信息格式的讨论。虽然存在优点,如简化磁盘映像构建,但也面临潜在的安全风险和工具兼容性挑战。
摘要由CSDN通过智能技术生成

关注了就能看到更多这么棒的文章哦~

Identifying dependencies used via dlopen()

By Daroc Alden
April 16, 2024
ChatGLM translation
https://lwn.net/Articles/969908/

最近的XZ后门事件 在开源社区中引发了很多关于如何链接和打包软件的讨论。正在讨论的一个可能的安全改进是更改 systemd 这类项目链接仅用于可选功能的动态库的方式:使用dlopen()仅在需要时加载这些库。这可能会减小依赖关系导致的攻击面暴露,但该方法并非没有缺点 — 最突出的一个缺点是,它会让弄清楚程序依赖的动态库变得更加困难。4月 11 日,Lennart Poettering 在 GitHub 上的 systemd RFC 中提出了解决这个问题的一种方法。

实际上,systemd 项目已经开始消灭直接链接可选依赖项的行为 — 但不是出于安全原因。在 Poettering 在 Mastodon 上对他的提案的解释中,他指出:“使用 dlopen() 的主要原因是为了更容易地构建没有可选组件的小型磁盘映像,特别是用于 initrds 或容器部署的目的。”一些人推测,这个改动是导致"Jia Tan"在四月初就发起攻击的原因,而不是选择等到攻击方案更加健壮时再发起攻击。

然而,使用 dlopen() 有几个问题。其中一个是,与普通的动态链接不同,使用 dlopen() 会将依赖项提供的函数暴露为 void 指针,必须将其转换为正确的类型。如果依赖项中的类型与依赖程序中的类型不匹配,这可能会为类型混淆攻击打开大门。对 Poettering 在 Mastodon 上解释有担心的一些人认为,推广使用 dlopen() 会因此对安全性构成威胁。James Henstridge 说:“我想你可以通过不完全兼容的函数签名隐藏一些有趣的错误(例如,使参数截断为 32 位)。”Poettering 回答:

在当前的 systemd git 中,我们系统地使用了一些 typeof()宏魔法,确保我们总是将 dlopen()返回的函数指针转换为库头文件中列出的实际原型。因此,我们应该获得与进行常规动态库链接时相同的类型安全性保证。我们花了一些时间想出 typeof()可以用于此的想法,但这太神奇了,因为我们不再需要在我们的代码中重复其他库的原型。

Henstridge 在查看代码后同意这是“相当优雅的做法。它也巧妙地解决了将一个符号(symbol)分配给错误函数指针的问题。”然而,并非所有问题都如此轻松解决。根据 Poettering 的公告,真正的问题在于使用 dlopen() 从程序的 ELF 头部删除了有关其依赖项的信息。

现在,我认为这种方法有很多优点,但也有缺点。我个人认为优点远远大于缺点,但确实存在缺陷。最突出的一点是,将共享库依赖关系转换为 dlopen()依赖关系在某种程度上将它们隐藏在用户和工具的视图之外,因为上述工具将不再显示它们。关心此信息的工具包含软件包管理器,如 rpm/dpkg(它们喜欢基于 ELF 依赖关系生成自动软件包依赖关系),以及 initrd 生成器,如 dracut。

他提出的解决方案是采用一种新的约定,将可选依赖项明确列为程序本身的一部分。在 systemd RFC 中,他给出了一个示例,该示例使用宏将可选依赖项的名称嵌入到二进制文件的名为".note.uapi.dlopen"的特殊节中。 "UAPI"代表用户空间 API — 指的是Linux用户空间API组,这是一个相对较新的合作项目,其中包括分发、软件包管理器和大型软件项目,旨在为用户空间软件定义标准化接口。关于要在注释部分编码记录的内容的初始提议相对来说相当简单 — 原本只打算设置一个类型字段,字符串 "uapi" 表示 ELF section "vendor",以及所讨论的依赖项的名称。

Poettering 还明确表示,单独为 systemd 实现这一点是没有用的;只有当其他工具决定读取它并且其他项目也在选择实现它时,这个注释才有用。Mike Yuan 迅速积极评论了添加支持到 mkinitcpio 的可能性,mkinitcpio 是 Arch Linux 的 initramfs 生成工具,还有pacman,Arch 软件包管理器。

Luca Boccassi 同意他可以“研究添加一个 debhelper 附件工具”,但想知道是否应该有一种方式来指示依赖关系是真正可选的,还是说假如依赖项丢失那么程序将失败。Poettering 回答:“如果它是硬依赖关系,那么它不应该是 dlopen()的应用场景。毕竟,使用 dlopen()而不是常规 ELF 共享库依赖项的全部原因就是它们可以是弱依赖关系”,尽管他指出类型字段意味着“这打开了以后扩展的可能性。”

Antonio Álvarez Feijoo 提出了另一个问题,指出:“有些人对 initrd 的 size 非常挑剔,不喜欢包含那些不是真正必要的东西。[…]因此,知道哪些库是必需的,那很好,但如何知道哪个 systemd 组件需要它们?”Boccassi 回答说,这是一个示例,说明了是否需要依赖项的信息可能会有用。Poettering 不同意,断言“哪些库实际上包含在 initrd 中是由本地配置或发行版政策决定的。”最终,新注释部分的使用者可以根据自己的需要对信息做任何事情,包括自动生成依赖关系,或仅将它们用作“linter”,以抱怨尚未知道的新的弱依赖关系。

我认为所有这些方法都比现状要好:我们将添加一个弱依赖关系或将常规依赖关系转换为弱依赖关系,除非下游真的仔细阅读 NEWS(好吧,他们并不一定擅长),否则他们只会重新构建他们的软件包/initrd,现在一切都混乱了。

这一点得到了 Feijoo 的赞同,他认为将信息用作包定义的合理性检查是有道理的。

Carlos O'Donell 问道,Poettering 是否关心暴露 systemd 使用的特定符号和符号版本,指出现有的 ELF 头部包含了这些信息。他断言,在打包程序时,RPM 使用这些信息。Poettering 说道这是一个很好的问题,但回答道:

对于 systemd 的用例来说:尽管我们近来用到了相当多的库(实际上有 21 个),并且我们确实为我们自己的库提供了符号版本,但我们不会费心去列出我们用到的东西的符号版本。我们对所有这些都使用普通的 dlsym(),而不是 dlvsym()。

他接着指出,要求人们确定符号版本将是“一个重大的额外要求”。

Poettering 似乎认为将这一新提议整合到 GNU C 库(glibc)的现有动态链接实现中会有一些好处。他询问了O'Donell和Florian Weimer — 他们两个都参与了 glibc 项目 — “我们是否应该继续这个独立规范的过程,其中只是说 '.note.uapi.dlopen 包含这些数据,使用它或不使用它,bla bla bla'。还是说弱泄漏的概念让你感兴趣,你认为这应该在 glibc、binutils 等中本地实现?”一些其他操作系统 — 尤其是 macOS — 对于可选依赖项有一个本地的“弱链接”概念,所以将这些信息整合到构建系统和标准库中的想法并不新鲜。

Zbigniew Jędrzejewski-Szmek 提出了一个额外的问题 是关于新部分的格式的,问是否使用“紧凑的 JSON 表示”会有意义。Jędrzejewski-Szmek 说,这可以让人们轻松地添加一个人类可理解的描述依赖项用途的描述。有了这个补充,"将这个整合到 rpm 构建系统中应该是相当容易的。" Boccassi 同意这里的内容应该是 JSON 格式。Poettering 回答道:“我对使用 JSON 并没有意见,但关键是我们应该能够从一个简单的无依赖关系的 C 宏中来合理地生成。”

最终,对于可选依赖项有一个标准编码的想法似乎受到了良好的认可,有几个软件包管理器可能有兴趣添加支持。然而,随着讨论仍在进行中,新增信息的最终格式尚未确定,因此现在还为时过早。然而,任何旨在帮助缓解移除传统动态链接依赖项的痛苦的东西似乎都是一个好主意,因为它们减少了暴露给类似XZ后门的攻击的机会。

全文完
LWN 文章遵循 CC BY-SA 4.0 许可协议。

欢迎分享、转载及基于现有协议再创作~

长按下面二维码关注,关注 LWN 深度文章以及开源社区的各种新近言论~

5238ab8a7d0bac8484f771240081c3e2.jpeg

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值