
技术博客发布 Linux 树外缓解措施实用指南
来源标题:Practical guide on out of tree mitigations on Linux
一篇技术博客发布了在 Linux 上实施树外(out of tree)安全缓解措施的实用指南,回应读者对具体实现方法的询问。文章以 systemd 管理服务为基础,说明具体做法及如何调整配置,并指出部分防御措施也可用 OpenRC 或 SysV init 实现。作者强调安全并非单一手段,缓冲区溢出等不同攻击需采用不同的缓解措施。
以下正文由 AI 翻译自 Hacker News 最新(原文版权归原站所有),可能存在偏差,重要内容请以原文为准。
我上一篇博客写得比较简略,有位来自 Gemini 的读者发邮件问我,实现这些缓解措施究竟做了些什么。感谢你的提问,也很抱歉让你不得不问。这篇文章应该能说明具体做了什么,以及如何调整这套方案。核心原则可以在我更早的一篇文章里找到:
我假设你使用 systemd 来管理服务,因为 systemd 已经是 Ubuntu 十多年来的默认选择。有些防御措施用 OpenRC 或 SysV init 也能轻松实现。当 systemd 不可用时,也可以用 firejail 这类第三方工具,不过我对它们经验很少;或者使用足够新的 Linux 内核(指几年以内的版本)。另外,我个人对 Docker 非常不熟悉。
还有一点很关键:安全不是单一的事情。你的程序可能以各种不同方式受到攻击。缓冲区溢出、服务器代码忘了检查身份认证、糟糕的路径解析导致目录遍历,等等等等。针对缓冲区溢出的缓解措施,并不能阻止你的服务器提供本不该提供的文件。它们需要不同的缓解措施来防御。不过,缓解措施本质上是一种从外部对程序说“据我所知,你永远不应该遇到 X。所以我要让你无法做 X。这样当攻击者诱骗你去做 X 时,你也做不到。具体做法是:一旦检测到 X,就拒绝访问或者让你崩溃”。过度严格的缓解措施会让程序无法完成它的本职工作。
添加缓解措施时的核心原则——假设你的程序是恶意的,然后通过移除它能触及的东西,来限制它能作恶的程度。
按复杂度与影响力的比例排序(同时考虑实现难易程度和缓解能力):
如果必须让我只选一个,我会说使用加固分配器(hardened allocator)是最重要的缓解措施。它在所有合理的 Linux 内核版本上都能工作,不依赖高级内核特性,并且直接针对旧软件中最常见的失效模式之一——堆溢出和释放后使用(use-after-free)。我之前的文章推荐了 hardened_malloc,我仍然推荐它,也是我的默认选择。但 hardened_malloc 比一些替代方案慢,而那些方案对你来说可能已经足够好,或者有更好的取舍。你可选的方案有:
大多数高级攻击都要求程序以非常特定的方式被破坏。加固分配器会介入这个过程,通过让进入损坏状态的程序更快死亡来提升安全性,而不是让它们继续运行可能受攻击者控制的行为。如果你的程序在加固分配器下不稳定,那它本来就是坏的,你需要修复它(或者属于某种非常特殊的无法工作的情况,我后面会提到)。
hardened_malloc 是我的默认选择,它由 GrapheneOS 开发,是 OpenBSD 分配器的分支。在 Arch 上可以通过 AUR 获取。在其他发行版上,你需要从源码安装,步骤非常简单:
git clone https://github.com/GrapheneOS/hardened_malloc cd hardened_malloc make sudo make install
mimalloc 是微软的发明,自带安全模式。你可以用 -DMI_SECURE=OFF|MINIMAL|ON|FULL 选择安全级别——在我的测试中,即使 FULL 模式也比 hardened_malloc 快。虽然 hardened_malloc 的性能从来没让我困扰,但如果你的服务器一直很忙,或者有其他原因,可以试试 mimalloc,看看它的某个模式是否足够快。我个人在程序于 hardened_malloc 下直接崩溃时使用 mimalloc。不过,无法在 hardened_malloc 下运行通常意味着程序已经损坏,需要修复。
https://github.com/microsoft/mimalloc cd mimalloc mkdir build cd build cmake .. -DMI_SECURE=ON make -j4 sudo make install
Scudo 是 LLVM 的安全分配器,设计目标是速度快,同时提供大部分安全收益。这够不够安全,取决于你自己的判断。不需要构建步骤,因为它随 LLVM 工具链一起安装。不过,由于它是 LLVM 工具链的一部分,路径会随 LLVM 版本变化。在滚动发行版上,你需要留意具体的 LLVM 版本。
在我的 Arch 机器上,该文件位于 /usr/lib/clang/23/lib/x86_64-pc-linux-gnu/libclang_rt.scudo_standalone.so。那个 23 会随 clang 主版本发布而变化。
使用加固分配器很简单。对于动态链接的程序(大多数程序都是,包括 Rust 和 Go 二进制),设置环境变量 LD_PRELOAD=/path/to/your/installed/liballocator.so,它就会替换 glibc 提供的默认分配器。在我的 Arch 系统上,要使用 hardened_malloc:
LD_PRELOAD=/usr/lib/libhardened_malloc.so my_program
为了演示加固分配器的作用,看下面这个有缺陷的 C 程序:
/* bad.c */ #include <stdio.h> #include <stdlib.h> int main(void) { char buffer = malloc(24); / FIXME: one byte past the 24-byte allocation This is analogous to real vulnerabilities The attacker can make the program write into addresses that it shouldn't */ volatile char *bytes = buffer; bytes[24] = 'X'; free(buffer); puts("finished"); return 0;
用默认的 glibc 分配器编译,程序在错误写入之后仍能存活。但在 hardened_malloc 下不行。程序在 free() 时死亡。当然,AddressSanitizer 用 -fsanitize=address 也能捕获这个问题。但你不会在生产环境使用 AddressSanitizer,而加固分配器试图在生产环境阻止不良程序状态。
❯ cc -std=c11 -O0 -o bad bad.c ❯ ./bad finished ❯ LD_PRELOAD=/usr/lib/libhardened_malloc.so ./bad fatal allocator error: canary corrupted [1] 127032 abort (core dumped) LD_PRELOAD=/usr/lib/libhardened_malloc.so ./bad
注意,对于这个特定的轻微错误(分配区域之后的一个字节),只有 hardened_malloc 和 mimalloc 在 ON 或 FULL 模式下能捕获。Arch 自带的 Scudo 没有捕获。
(原文较长,此处译出前 6000 字,完整内容请点「打开原文」。)
正文由 探How 从来源站点 RSS 同步,内容未经改写;遇到排版缺失或需要图片、视频时请以原文为准。