在 chroot 里跑 ASan:LeakSanitizer 的 Die() 调用需要二进制补丁才能关掉

【时间】2026 年 9 月 23 号,搭 CVE 复现管线

Alpine 3.20.3 chroot 环境(Docker Hub 拉 rootfs 层),clang 17 + compiler-rt ASan 已验证可触发 heap-buffer-overflow。每例四步:chroot 构建 → 运行 PoC → Agent 判定 → 记录结果。

md4c 的 PoC 在 chroot 里构建成功,但 ASan 二进制退出时 rc=134,TimeoutExpired 把整个管线崩了。

坑:LeakSanitizer 的 Die() 调用

ASAN_OPTIONS=detect_leaks=0 无效——LSan 的 fatal 是退出期噪声,ASan 报告本身正常。问题出在 LSan 在进程启动期就读 /proc/<pid>/task(数字 PID 路径),chroot 内不存在。

试过的路线:

  1. detect_leaks=0 — 无效,LSan preinit 的 atexit 检查受 LSAN_OPTIONS 控制,不是 ASAN_OPTIONS
  2. LD_PRELOAD 重定向 shim — 无效,LSan 用的是裸 syscall,不是 libc 函数
  3. proot(ptrace 模拟 chroot + 真实 /proc 透传)— Alpine 无 proot 包,宿主有 gcc+make 但构建 proot 太重
  4. bind mount 宿主 /proc 进 chroot — 被拒,容器无 CAP_SYS_ADMIN

修复:二进制补丁

定位 lsan_common.cpp.o 里 CheckForLeaks 的 Die() 调用。字节确认:file offset 0x1674 处 6 字节 ff 15 00 00 00 00 = call Die。

补丁:31 c0 c3 90 90 90(xor eax,eax; ret → CheckForLeaks 返回 false)。

还有一层坑:.rela 里 0x386 处有个过期重定位项会覆盖 ret 字节。修复:把该项设为 R_X86_64_NONE。

补丁固化进 base rootfs,所有后续用例继承。

其他踩的坑

  • test -x X && X --version 在 set -e 下,当 X 不存在时“侥幸通过”(&& 列表中的失败命令不会触发 exit)。IM6 的 bin/convert 不存在,真正二进制是 utilities/magick,但 build_check 判通过。拆成两行修复。
  • JSON 抽取用 rfind("{") 取到最后一个花括号,嵌套对象被截断。改为 find("{") + rfind("}")(首个 { 到末个 })。
  • grep -v 空输出退出码 1,mv 没执行,清理根本没生效。

经验

  1. chroot 里跑 ASan,detect_leaks=0 不一定有效——LSan 的 fatal 是 /proc 读取失败触发的 Die(),不是 leak 检测本身。
  2. LD_PRELOAD 拦不住裸 syscall,LSan 直接 syscall(SYS_...)。
  3. 对编译器生成的 .o 文件做定点 NOP 补丁是最后手段,但要先确认 .rela 里没有过期重定位项会覆盖补丁字节。

🎵 Listening companion