xz-utils 后门隐藏的引擎:为什么 GNU IFUNC 是安全隐患

CVE-2024-3094,即 xz-utils 后门的发现,在网络安全社区引发了巨大震动。虽然大部分事后分析都集中在社会工程学和软件供应链的破坏上,但允许后门劫持 OpenSSH 的技术机制往往被忽视。要理解像 xz-utils 这样的库如何能授予 SSH 服务器 root 权限,我们必须超越恶意代码本身,去审视 Linux 生态系统底层的架构决策。

这一漏洞的核心在于特定发行版的补丁与 GNU C Library (glibc) 中一个强大但晦涩的功能——GNU IFUNC (Indirect Functions) 的结合。通过允许链接器在 main 函数开始之前执行任意代码,IFUNC 为攻击者提供了完美的隐蔽切入点。

依赖链

要理解 xz-utils 是如何进入 OpenSSH 的地址空间的,我们必须追踪因 Linux 发行版而异的复杂依赖网络。

  1. OpenSSH 分离: OpenSSH 主要为 OpenBSD 开发。为了让它在 Linux 上运行,"Portable OpenSSH" 项目提供了一套补丁。
  2. 发行版定制: 一些发行版(尤其是 Fedora 和 Debian)对 OpenSSH 应用了进一步的补丁,以便将其与 systemd 集成,从而解决 sshd 重启期间特定的竞态条件。
  3. 依赖泄漏: 由于这些经过补丁的 OpenSSH 版本依赖于 libsystemd,而 libsystemd 又依赖于 xz-utils,因此 xz-utils 库被加载到了 SSH 守护进程的内存空间中。

这条链条制造了一个关键漏洞:一个高权限进程 (OpenSSH) 正在加载一个包含能够修改进程自身执行流机制 (IFUNC) 的库 (xz-utils)。

理解 GNU IFUNC

GNU IFUNC 旨在允许程序在运行时确定使用哪个版本的函数。这最常用于 CPU 优化。例如,如果一个程序需要执行计算,它可以利用 IFUNC 解析器来检查当前 CPU 是否支持 AVX2 指令;如果支持,它将返回一个指向高度优化的 AVX2 版本函数的指针;否则,它将返回一个指向通用实现的指针。

虽然这听起来像是性能优化,但技术现实是,IFUNC 允许在动态链接过程中执行任意代码。正如研究中 tty_demo.c 示例所示,IFUNC 解析器可以在程序到达其入口点之前,用来探测进程环境——例如检查 STDOUT 是否为终端。

为什么 IFUNC 是安全风险

从安全角度来看,IFUNC 引入了几个关键失效点:

1. 破坏 RELRO

RELRO (Relocation Read-Only) 是一种旨在保护全局偏移表 (GOT) 不被攻击者篡改的安全特性。然而,IFUNC 要求 GOT 是可写的,以便解析器可以写入所选函数实现的地址。通过允许任意代码在 GOT 仍处于可写状态时运行,IFUNC 实际上在解析阶段使 RELRO 失效了。

2. 违反最小惊讶原则

大多数开发者假设加载共享库仅仅是一个被动的行为。IFUNC 通过将加载库的行为变成一个主动的执行事件,违反了这一假设。正如一位研究人员指出的,没有一个合理的人会期望加载动态库会破坏旨在保护这些库本身的安全性特征。

3. 复杂性与脆弱性

IFUNC 的实现和文档编写极其困难。GCC 开发人员此前曾将该接口描述为一种“错误”,并指出为了使其稳健而所需的 glibc 解决方案并非对其他用户普遍可用,从而导致了脆弱性和意外的崩溃。

是否有更好的替代方案?

IFUNC 的批评者认为,其性能收益微乎其微,且存在更安全的替代方案:

  • 全局函数指针: 开发者可以在 main 开始时命令式地解析出正确的函数实现,并将其存储在一个全局指针中。虽然这些指针是可写的,但可以在初始化后使用 mprotect(2) 将其设为只读。

  • LD_PRELOAD: 对于特定的硬件目标,可以通过使用 LD_PRELOAD 环境变量的包装脚本来选择正确的库版本。

  • 独立二进制文件: 提供针对不同 CPU 特性集优化的多个二进制文件是一个可行的策略,因为大多数现代 CPU 遵循可预测的特性层级(例如,任何支持 AVX-512 的 CPU 几乎肯定也支持 SSE4.2)。

性能基准测试表明,IFUNC 并不比使用函数指针快多少。在某些测试中,IFUNC 实际上比普通的函数指针调用产生了更多的开销,这反驳了其存在的主要理由。

反方观点与社区辩论

并非所有人都认为 IFUNC 是“罪魁祸首”。技术社区的一些成员认为,过度关注 IFUNC 是对主要失败点的分散注意力:供应链的破坏。

"攻击者一旦在常用库中安装了任意代码,游戏就结束了... IFUNC、systemd 和经过补丁的 openssh 都与问题无关,那仅仅是攻击者利用其在 libxz 中的立足点所采取的路径。"

这种观点认为,如果攻击者没有使用 IFUNC,他们也会找到其他方法——也许是通过 C++ 全局构造函数,或者通过对磁盘上的二进制文件进行补丁。

结论

CVE-2024-3094 是一次“险些发生”的教训,凸显了软件供应链中隐式信任的危险。虽然社会工程学是主要催化剂,,但 GNU IFUNC 为执行攻击提供了必要的、优雅且隐蔽的技术手段。通过允许链接器在进程受到充分保护之前运行任意代码,IFUNC 破坏了 Linux 运行时的基本安全假设。为了加固生态系统,社区应该考虑将 IFUNC 视为 glibc 的内部接口,并限制或禁用其在通用应用程序库中的使用。

Sources