加载任意可执行文件作为内核模块

作者:Zack Brown

Alexei Starovoitov 发布了一些补丁,允许内核加载常规 ELF 二进制文件(也称为普通可执行文件)作为内核模块。这些模块将能够运行用户模式辅助例程,而不是完全局限于内核空间。

Alexei 列举了这样做的一些好处。首先,作为一个用户进程,基于 ELF 的模块崩溃不会导致内核其余部分崩溃。尽管 ELF 模块将以 root 权限运行,但他表示,安全漏洞不会直接导致访问内核的内部运作,但至少最初会局限于用户空间。ELF 模块还可以被内存不足 (OOM) 杀手终止(如果需要),或者由人工管理员直接结束。此外,使用大量可用的工具,对基于 ELF 的模块进行常规用户空间调试和性能分析也是可行的。

最初,出现了一些技术问题和批评,但没有人立即公开反对。Linus Torvalds 表示他喜欢这个功能,但他想要一个更改:使模块类型在系统日志中可见。他说:

当我们加载一个常规模块时,至少之后会在 lsmod 中显示,尽管我也有几次希望真正在日志中看到模块加载作为一个事件。当我们加载一个只执行用户程序的模块,并且模块列表中没有任何迹象表明它存在时,我认为我们*真的*需要以某种方式向管理员显示该事件。

他特别指出,“我*不*希望这成为一种隐藏事物的神奇方式。”

Andy Lutomirski 提出了一个相关的问题:为什么不直接改造 modprobe 程序来按需处理 ELF 二进制文件,而不是对内核代码做任何事情?换句话说,为什么这个功能不能完全在内核外部实现呢?

但 Linus 回复说:

我们越少干预用户模式工具,情况就越好。

我们将大部分模块加载逻辑转移到内核后,情况已经变得*好得多*,我们不应该回到旧的、有缺陷的方向。

我*不*希望 kmod 项目被 systemd 接管,并以他们破坏固件加载的相同方式破坏它。

让 modprobe 只做一件事,而且只做一件事:跟踪依赖关系,并机械地加载模块。*不要*要求它做任何其他事情。

现在 kmod 是一个非常简单的好项目。有很多测试套件的东西,以及非常明确的目标。让我们保持 kmod 只做一件事,甚至不必关心内核内部的决定,例如“哦,这个模块可能不是模块,而是可执行文件”。

如果可以的话,我认为我们希望保持选择的开放性,以防我们需要或想要考虑短路处理,并允许直接加载简单的情况,完全绕过 modprobe。

在某个时候,Kees Cook 确实对该补丁的基本目标提出了一些更严肃的批评。主要是,他注意到 Alexei 的补丁可以用于——并且是故意设计用于——自动执行用户空间中的任意代码。这与内核模块的常规方法不同,在常规方法中,模块可以加载但不能自动执行。

Kees 说,“这只是将我们在定义模块安全边界方面遇到的所有问题扩展到了 umh [用户模式辅助代码]。我需要一些强有力的理由来说服我,这可以做到安全。”

他指出,最近过去普遍存在的一类内核错误可能会将模块加载重定向到虚拟机(即容器)之外,并进入主内核本身。由于容器可以触发加载任意模块,这意味着恶意用户可能加载一个 ELF 模块,将其重定向回主内核,并立即以完全权限执行其攻击代码。

Kees 拒绝让该补丁按原样进入内核。他说:

至少,您需要解决此处的执行环境问题:ELF 应该以不高于加载模块的权限运行,并且非常重要的是,绝不允许通过自动加载绕过这些检查。*触发*自动加载的必须是环境,而不是“modprobe”,因为 “modprobe” 是以完全权限运行的。

然而,另一方面,Kees 承认 Alexei 的补丁是一个“有趣的想法。我认为它*可以*工作,只是需要更加谨慎的安全边界,并解决我们的自动加载暴露问题。”

然而,Alexei 将 Kees 的回应描述为“没有单个具体安全问题例子的安全偏执。”

Andy 也不同意 Kees 的评估。他指出,Kees 的问题取决于攻击者找到并利用额外的漏洞,该漏洞将允许容器将模块重定向到自身之外——这不是内核功能,如果被发现,将被视为错误。

Kees 同意 Andy 的观点,即问题不在于 Alexei 的代码,而在于内核其他地方的潜在漏洞。他说,“我只是不想进一步扩展这个问题。” 他补充说,他并不反对 Alexei 的补丁,但他的担忧并非偏执,“在这个模型中,存在非常真实的安全边界违规行为。”

在某个时候,为了为 Alexei 的方法辩护,Andy 说,“我不明白这比任何其他 init_module() 更容易被利用在哪里。” Linus 回复说:

绝对是这样。如果 Kees 不信任要加载的文件,那么可执行文件——即使它以 root 权限并在 initns 中运行——在根本上仍然比内核模块弱。

所以我完全不理解安全论点。这简直是无稽之谈。可执行文件加载执行与模块加载相同的安全检查,包括签名检查。

Kees 承认,他的担忧不在于 Alexei 的代码本身,甚至不在于该功能的设计。但他认为,如果某些其他错误确实出现在内核中——就像以前那样——那么有人将能够利用该功能以 root 级别运行任意代码。

然而,Linus 已经发话了,Kees 对潜在未来错误的担忧显然不是一个障碍。为了强调这一点,David S. Miller 重申了 Linus 的观点,即内核模块比可执行代码危险得多,因为它们可以访问它们喜欢的任何容器和命名空间。

但这还不是故事的结尾!

在对话的这一部分附近,Linus 对 Kees 说:

我个人的担忧实际上是不同的——我们确实检查了我们正在加载的文件的签名,但随后我们将其传递给 execve(),不是作为我们加载的镜像,而是作为文件指针。因此,execve() 最终不会使用我们检查签名的实际缓冲区,而是重新读取文件。

Linus 说,除其他外,“有人可能会尝试计时并在签名检查后修改文件,然后我们执行其他内容。”

他继续说:

最初,我认为这不是问题,因为任何控制模块子目录到足以重写文件的人都可以直接执行文件本身。但事实证明,这是不需要的。一些不良行为者可以只使用他们刚从模块目录*复制*的文件执行 finit_module()

Linus 说,这个问题必须在补丁进入内核之前解决。

Andy 还注意到其他一些可能是致命的问题。他说:

这个补丁可能是一个严重的 ABI 破坏。目前,加载模块*复制*它到内存中,并且不保留对底层文件系统的引用。应用该补丁后,各种用例都可能以令人讨厌的方式崩溃。Initramfs 可能还可以,但 initrd 可能会被搞砸。如果您从 initrd 加载一个 ET_EXEC 模块,然后卸载它,然后清除 ramdisk,那么事情会变得非常糟糕。具体会发生什么取决于用户空间是否注意到 umount() 失败。同样,如果您通过网络加载其中一个模块,然后丢失连接,您就会遇到问题。

他进一步解释说,“没有您的补丁,init_module 不会继续使用该文件,因此加载模块然后删除或卸载它是常见的做法。使用您的补丁,卸载的情况会崩溃。这很可能会破坏现有的用户空间,因此,用 Linux 的话说,这是一个 ABI 破坏。”

在这一点上——关于 Linus 的安全漏洞——Andy 认为 Kees 的赞成票比他最初认为的更重要。Kees 的职责是模块安全,Andy 之前认为这不是问题。现在它成为了问题,获得 Kees 对此补丁的祝福变得更加重要。Andy 向 Alexei 指出,“Kees 非常理性,当提出有效的技术论据时,他会改变主意并承认他否决的补丁。”

然而,他也说:

我的 ABI 破坏观察也是一个主要问题,如果这个问题在审查期间被提出,而它最终进入 Linus 的树并导致系统崩溃,Linus 会非常生气。所以我认为您需要要么修改补丁,要么认真调查所有发行版如何处理模块(dracut、initramfs-tools、所有较旧的东西,可能还有更多),并确保它们都可以处理您的补丁。

Alexei 回复说,这两个问题都不是真正的问题。ABI(应用程序二进制接口)破坏并没有真正破坏内核 ABI,安全问题也不是真正令人担忧的问题。他说,“我认为您需要停止对非问题反应过度。”

他们之间进行了一些来回辩论。事实证明,Alexei 认为不存在 ABI 破坏,因为在他的预期用例中,一切都将以与现在相同的方式完成,因此不会有任何破坏。但 Greg Kroah-Hartman 回复说,“对于*您的*用例,是的。对于我和 Andy 以及未来其他人的用例,可能会有。” 他补充说,“您正在创建一个非常通用的、新的、用户/内核 API,很多人都会想要使用它。请不要从一开始就妨碍我们所有人使用它的能力。”

在具体用例方面,Greg 说:

我们今天有用于 USB 的用户空间驱动程序,能够将该树外代码库拖入内核是一个*巨大*的优势,我非常希望出于很多原因这样做。我也可以看到将我们现有的一些内核驱动程序移出内核,通过使用这种类型的功能来提供“开箱即用”的功能。

包括 Linus 在内的一群人开始讨论解决迄今为止已确定的问题的方法。Alexei 过了一会儿也加入了,并开始实施该小组确定的更改。

似乎很明显,这个功能将进入内核。它提供了 Linus 和其他人非常渴望的酷炫功能。但是,代码进入内核的时间将取决于 Alexei 如何很好地修复各种问题,以及是否会出现新的安全或 ABI 问题。

整个讨论在很多层面上都很有趣。我特别喜欢 Alexei 补丁的批评者和捍卫者改变立场的速度,他们不考虑自我,也不害怕被视为“错误”或任何类似的事情。最初对新功能的完全接受,变成了对其可能存在问题的担忧,以及以有用的方式解决这些问题的追求。

我也特别喜欢 Alexei 最初轻微的防御性没有被视为欺凌的理由,每个人都只是试图保持讨论的建设性。在内核开发中,情况并不总是这样。

当然,我也很高兴能够关注潜在内核弱点的讨论,恶意行为者如何利用它们,以及可以采取哪些措施来阻止它们。在早期,这正是内核人员过去谈论微软的方式——公开地!微软可能会做什么来摧毁开源?它可能如何摧毁 GPL?它可能如何摧毁 Linux?一直以来,内核人员都知道微软的人正在阅读他们的每一篇文章,就像他们现在知道恶意攻击者正在热切地窥探和探测邮件列表讨论和每个补丁提交,寻找可用的漏洞。

注意:如果您在上面被提及并希望在评论区上方发布回复,请将您的回复文本发送至 ljeditor@linuxjournal.com。

Zack Brown 是 Linux JournalLinux Magazine 的科技记者,并且是“Kernel Traffic”每周新闻通讯和“Learn Plover”速记打字教程的前作者。他于 1993 年在他的 386 上首次安装了 Slackware Linux,配备 8MB 内存,并被开源社区彻底震撼。他是 Crumble 纯策略棋盘游戏的发明者,您可以使用几块纸板自己制作。他还喜欢写小说、尝试动画、改革拉班舞谱、设计和缝制自己的衣服、学习法语以及与朋友和家人共度时光。

加载 Disqus 评论